• JavaScript高级编程


    JavaScript是以对象为基础、函数为模型、原型为继承的基于对象的开发模式。JavaScript不是面向对象的编程语言,在ECMAScript 6规范之前,JavaScript没有类的概念,仅允许通过构造函数模拟类,通过原型实现继承。ECMAScript 6新增类和模块功能,提升了JavaScript高级编程的能力。

    1、构造函数

    构造函数(constructor)也称类型函数或构造器,功能类似于对象模板,一个构造函数可以生成任意多个实例,实例对象拥有相同的原型属性和行为特征。

    1.1、定义构造函数

    在语法和用法上,构造函数与普通函数没有任何区别。定义构造函数的方法如下:

        function  类型名称( 配置参数 ) {
            this.属性1 = 属性值1;
            this.属性2= 属性值2;this.方法1 = function(){
                //处理代码
            };//其他代码,可以包含return语句
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    提示,建议构造函数的名称首字母大写,以便与普通函数进行区分。

    注意:构造函数有两个显著特点:

    • 函数体内可以使用this,引用将要生成的实例对象。当然,普通函数内也允许使用this,指代调用函数的对象。
    • 必须使用new命令调用函数,才能够生成实例对象。如果直接调用构造函数,则不会直接生成实例对象,此时与普通函数的功能相同。

    【示例】演示定义一个构造函数,包含两个属性和1个方法:

        function Point(x,y){                    //构造函数
            this.x = x;                         //私有属性
            this.y = y;                         //私有属性
            this.sum = function(){              //方法
                return this.x + this.y;
           }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在上面代码中,Point是构造函数,它提供模板,用来生成实例对象。

    1.2、调用构造函数

    使用new命令可以调用构造函数,创建实例,并返回这个对象。

    【示例】使用new命令调用构造函数,生成两个实例,然后分别读取属性,调用方法sum():

        function Point(x,y){                    //构造函数
            this.x = x;                         //私有属性
            this.y = y;                         //私有属性
            this.sum = function(){              //私有方法
                return this.x + this.y;
            }
        }
        var p1 = new Point(100,200);            //实例化对象1
        var p2 = new Point(300,400);            //实例化对象2
        console.log(p1.x);                      //100
        console.log(p2.x);                      //300
        console.log(p1.sum());                  //300
        console.log(p2.sum());                  //700
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    提示:构造函数可以接收参数,以便初始化实例对象。如果不需要传递参数,可以省略小括号,直接使用new命令调用,下面两行代码是等价的。

        var p1 = new Point();
        var p2 = new Point;
    
    • 1
    • 2

    如果不使用new命令,直接使用小括号调用构造函数,这时构造函数就是普通函数,不会生成实例对象,this就代表调用函数的对象,在客户端指代window全局对象。

    为了避免误用,最有效的方法是在函数中启用严格模式,这样在调用构造函数时,就必须使用new命令,否则将抛出异常。

        function Point(x,y){                           //构造函数
            'use strict';                              //启用严格模式
            this.x = x;                                //私有属性
            this.y = y;                                //私有属性
            this.sum = function(){                     //私有方法
                return this.x + this.y;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    或者使用if对this进行检测,如果this不是实例对象,则强迫返回实例对象。

        function Point(x,y){                                        //构造函数
            if(!(this instanceof Point)) return new Point(x, y);    //检测this是否为实例对象
            this.x = x;                                             //私有属性
            this.y = y;                                             //私有属性
            this.sum = function(){                                  //私有方法
                return this.x + this.y;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1.3、构造函数的返回值

    构造函数允许使用return语句。如果返回值为简单值,则将被忽略,直接返回this指代的实例对象;如果返回值为对象,则将覆盖this指代的实例,返回return语句后面的对象。

    【示例】在构造函数内部定义return返回一个对象直接量,当使用new命令调用构造函数时,返回的不是this指代的实例,而是这个对象直接量,因此当读取x和y属性值时,与预期的结果是不同的:

        function Point(x,y){                       //构造函数
            this.x = x;                            //私有属性
            this.y = y;                            //私有属性
            return { x : true, y : false }
        }
        var p1 = new Point(100,200);               //实例化对象1
        console.log(p1.x);                         //true
        console.log(p1.y);                         //false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1.4、引用构造函数

    在普通函数内,使用arguments.callee可以引用函数自身。如果在严格模式下,是不允许使用arguments.callee引用函数的,这时可以使用new.target访问构造函数。

    【示例】在构造函数内部使用new.target指代构造函数本身,以便对用户操作进行监测,如果没有使用new命令,则强制使用new实例化:

        function Point(x,y){                                                //构造函数
            'use strict';                                                   //启用严格模式
            if(!(this instanceof new.target)) return new new.target(x, y);  //检测this是否为实例对象
            this.x = x;                                                     //私有属性
            this.y = y                                                      //私有属性
        }
        var p1 = new Point(100,200);                                        //实例化对象1
        console.log(p1.x);                                                  //100
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意:IE浏览器对其支持不是很完善,使用时要考虑兼容性。

    1.5、使用this

    this是由JavaScript引擎在执行函数时自动生成,存在于函数内的一个动态指针,指代当前调用对象。具体用法如下:

        this[.属性]
    
    • 1

    如果this未包含属性,则传递的是当前对象。

    下面简单总结this在5种常用场景中的表现,以及应对策略:

    1.普通调用

    【示例1】演示函数引用和函数调用对this的影响:

        var obj = {                    //父对象
            name : "父对象obj",
            func : function(){
                return this;
            }
        }
        obj.sub_obj = {                //子对象
            name : "子对象sub_obj",
            func : obj.func            //引用父对象obj的方法func
        }
        var who = obj.sub_obj.func();
        console.log(who.name);         //返回子对象sub_obj,说明this代表sub_obj
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果把子对象sub_obj的func改为函数调用:

        obj.sub_obj = {
            name : "子对象sub_obj",
            func : obj.func()        //调用父对象obj的方法func
        }
    
    • 1
    • 2
    • 3
    • 4

    则函数中的this所代表的是定义函数时所在的父对象obj:

        var who = obj.sub_obj.func;
        console.log(who.name);       //返回父对象obj,说明this代表父对象obj
    
    • 1
    • 2

    2.实例化

    【示例2】使用new命令调用函数时,this总是指代实例对象:

        var obj ={};
        obj.func = function(){
            if(this == obj) console.log("this = obj");
            else if(this == window) console.log("this = window");
            else if(this.constructor == arguments.callee) console.log("this = 实例对象");
        }
        new obj.func;                    //实例化
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.动态调用

    【示例3】使用call和apply可以绑定this,使其指向参数对象:

        function func(){
            //如果this的构造函数等于当前函数,则表示this为实例对象
            if(this.constructor == arguments.callee) console.log("this = 实例对象");
            //如果this等于Window,则表示this为Window对象
            else if (this == window) console.log("this = window对象");
            //如果this为其他对象,则表示this为其他对象
            else console.log("this == 其他对象 \n this.constructor = " + this.constructor );
        }
        func();                          //this指向Window对象
        new func();                      //this指向实例对象
        func.call(1);                    //this指向数值对象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在上面示例中,直接调用函数func()时,this代表Window。当使用new命令调用函数时,将创建一个新的实例对象,this就指向这个新创建的实例对象。

    使用call方法执行函数func()时,由于call方法的参数值为数字1,则JavaScript引擎会把数字1强制封装为数值对象,此时this就会指向这个数值对象。

    4.事件处理

    【示例4】在事件处理函数中,this总是指向触发该事件的对象:

        <input type="button" value="测试按钮" />
        <script>
        var button = document.getElementsByTagName("input")[0];
        var obj ={};
        obj.func = function(){
            if(this == obj) console.log("this = obj");
            if(this == window) console.log("this = window");
            if(this == button) console.log("this = button");
        }
        button.onclick = obj.func;
        </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在上面代码中,func()所包含的this不再指向对象obj,而是指向按钮button,因为func()是被传递给按钮的事件处理函数之后才被调用执行的。

    如果使用DOM 2级标准注册事件处理函数,代码如下:

        if(window.attachEvent){                                //兼容IE模型
            button.attachEvent("onclick", obj.func);
        } else{                                                //兼容DOM标准模型
            button.addEventListener("click", obj.func, true);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在IE浏览器中,this指向Window和button,而在DOM标准的浏览器中仅指向button。因为,在IE浏览器中,attachEvent()是Window对象的方法,调用该方法时,this会指向Window。

    为了解决浏览器的兼容性问题,可以调用call或apply方法强制在对象obj身上执行方法func(),以避免不同浏览器对this的解析不同:

        if(window.attachEvent){
            button.attachEvent("onclick", function(){     //用闭包封装call方法强制执行func()
                obj.func.call(obj);
            });
        }
        else{
            button.addEventListener("click", function(){
                obj.func.call(obj);
            }, true);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    当再次执行时,func()中包含的this始终指向对象obj。

    5.定时器

    【示例5】使用定时器调用函数:

        var obj ={};
        obj.func = function(){
            if(this == obj) console.log("this = obj");
            else if(this == window) console.log("this = window");
            else if(this.constructor == arguments.callee) console.log("this = 实例对象");
            else console.log("this == 其他对象 \n this.constructor = " + this.constructor );
        }
        setTimeout(obj.func, 100);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在IE中this指向Window和Button对象,具体原因与上面讲解的attachEvent()方法相同。在符合DOM标准的浏览器中,this指向Window对象,而不是Button对象。

    因为方法setTimeout()是在全局作用域中被执行的,所以this指向Window对象。解决浏览器兼容性问题,可以使用call或apply方法来实现。

        setTimeout(function(){
            obj.func.call(obj);
        }, 100);
    
    • 1
    • 2
    • 3

    1.6、绑定函数

    绑定函数是为了纠正函数的执行上下文,把this绑定到指定对象上,避免在不同执行上下文中调用函数时,this指代的对象不断变化。

        function bind(fn, context) {                       //绑定函数
            return function() {
                return fn.apply(context, arguments);       //在指定上下文对象上动态调用函数
            };
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    bind()函数接收一个函数和一个上下文环境,返回一个在给定环境中调用给定函数的函数,并且将返回函数的所有的参数原封不动地传递给调用函数。

    注意:这里的arguments属于内部函数,而不属于bind()函数。在调用返回的函数时,会在给定的环境中执行被传入的函数,并传入所有参数。

    函数绑定可以在特定的环境中为指定的参数调用另一个函数,该特征常与回调函数、事件处理函数一起使用:

        <button id="btn">测试按钮</button>
        <script>
        var handler = {                                 //事件处理对象
            message : 'handler',                        //名称
            click : function(event) {                   //事件处理函数
                console.log(this.message);              //提示当前对象的message值
            }
        };
        var btn = document.getElementById('btn');
        btn.addEventListener('click', handler.click);   //undefined
        </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在上面示例中,为按钮绑定单击事件处理函数,设计当单击按钮时,将显示handler对象的message属性值。但是,实际测试发现,this最后指向了DOM按钮,而非handler。

    解决方法:使用闭包进行修正:

        var handler = {                             //事件处理对象
            message : 'handler',                    //名称
            click : function(event) {               //事件处理函数
                console.log(this.message);          //提示当前对象的message值
            }
        };
        var btn = document.getElementById('btn');
        btn.addEventListener('click', function(){   //使用闭包进行修正:封装事件处理函数的调用
            handler.click();
        });                                         //'handler'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    改进方法:使用闭包比较麻烦,如果创建多个闭包可能会令代码变得难于理解和调试,因此使用bind()绑定函数就很方便:

        var handler = {                                               //事件处理对象
            message : 'handler',                                      //名称
            click : function(event) {                                 //事件处理函数
                console.log(this.message);                            //提示当前对象的message值
            }
        };
        var btn = document.getElementById('btn');
        btn.addEventListener('click', bind(handler.click, handler));  //'handler'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1.7、使用bind

    ECMAScript 5为Function新增bind原型方法,用来把函数绑定到指定对象上。在绑定函数中,this对象被解析为传入的对象。具体用法如下:

        function.bind(thisArg[,arg1[,arg2[,argN]]])
    
    • 1

    参数说明:

    • function:必需参数,一个函数对象。
    • thisArg:必需参数,this可在新函数中引用的对象。
    • arg1[,arg2[,argN]]]:可选参数,要传递到新函数的参数列表。

    bind方法将返回与function函数相同的新函数,thisArg对象和初始参数除外:

    【示例1】定义原始函数check,用来检测传入的参数值是否在一个指定范围内,范围下限和上限根据当前实例对象的min和max属性决定。然后使用bind方法把check函数绑定到对象range身上。如果再次调用这个绑定后的函数check1,就可以根据该对象的属性min和max确定调用函数时传入值是否在指定的范围内:

        var check = function (value) {
            if (typeof value !== 'number')   return false;
            else  return value >= this.min && value <= this.max;
        }
        var range = { min : 10,  max : 20 };
        var check1 = check.bind(range);
        var result = check1 (12);
        console.log(result);                                  //true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    【示例2】在上面示例基础上,为obj对象定义两个上下限属性,以及一个方法check。然后,直接调用obj对象的check方法,检测10是否在指定范围,则返回值为false,因为当前min和max值分别为50和100。接着,把obj.check方法绑定到range对象,则再次传入值10,则返回值为true,说明在指定范围,因为此时min和max值分别为10和20:

        var obj = {
            min: 50,
            max: 100,
            check: function (value) {
                if (typeof value !== 'number')
                    return false;
                else
                    return value >= this.min && value <= this.max;
            }
        }
        var result = obj.check(10);
        console.log(result);                                  //false
        var range = { min: 10, max: 20 };
        var check1 = obj.check.bind(range);
        var result = check1(10);
        console.log(result);                                 //true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    【示例3】演示利用bind方法为函数两次传递参数值,以便实现连续参数求值计算:

        var func = function (val1, val2, val3, val4) {
            console.log(val1 + " " + val2 + " " + val3 + " " + val4);
        }
        var obj = {};
        var func1 = func.bind(obj, 12, "a");
        func1("b", "c");                                //12 a b c
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2、原型

    在JavaScript中,所有函数都有原型,函数实例化后,实例对象可以访问原型属性,实现继承机制。

    2.1、定义原型

    原型实际上就是一个普通对象,继承于Object类,由JavaScript自动创建并依附于每个函数身上。使用点语法,可以通过function.prototype访问和操作原型对象。

    【示例】为函数P定义原型:

        function P(x){                     //构造函数
            this.x = x;                    //声明私有属性,并初始化为参数x
        }
        P.prototype.x = 1                  //添加原型属性x,赋值为1
        var p1 = new P(10);                //实例化对象,并设置参数为10
        P.prototype.x = p1.x               //设置原型属性值为私有属性值
        console.log(P.prototype.x);        //返回10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.2、访问原型

    访问原型对象有3种方法,简单说明如下:

    • obj.proto
    • obj.constructor.prototype。
    • Object.getPrototypeOf(obj)。

    其中,obj表示一个实例对象,constructor表示构造函数。proto(前后各一条下画线)是一个私有属性,可读可写,与prototype属性相同,都可以访问原型对象。Object.getPrototypeOf(obj)是一个静态函数,参数为实例对象,返回值是参数对象的原型对象。

    注意:__proto__属性是一个私有属性,存在浏览器兼容性问题,以及缺乏非浏览器环境的支持。使用obj.constructor.prototype也存在一定风险,如果obj对象的constructor属性值被覆盖,则obj.constructor.prototype将会失效。因此,比较安全的用法是使用Object.getPrototypeOf(obj)。

    【示例】创建一个空的构造函数,然后实例化,分别使用上述3种方法访问实例对象的原型:

        var F = function(){};                       //构造函数
        var obj = new F();                          //实例化
        var proto1 = Object.getPrototypeOf( obj );  //引用原型
        var proto2 =  obj.__proto__;                //引用原型,注意,IE暂不支持
        var proto3 = obj.constructor.prototype;     //引用原型
        var proto4 = F.prototype;                   //引用原型
        console.log( proto1 === proto2 );           //true
        console.log( proto1 === proto3 );           //true
        console.log( proto1 === proto4 );           //true
        console.log( proto2 === proto3 );           //true
        console.log( proto2 === proto4 );           //true
        console.log( proto3 === proto4 );           //true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.3、设置原型

    设置原型对象有3种方法,简单说明如下:

    • obj.proto = prototypeObj。
    • Object.setPrototypeOf(obj,prototypeObj)。
    • Object.create(prototypeObj)。

    其中,obj表示一个实例对象,prototypeObj表示原型对象。注意,IE不支持前面两种方法。

    【示例】简单演示原型对象的3种方法,为对象直接量设置原型:

        var proto = { name:"prototype"};        //原型对象
        var obj1 = {};                          //普通对象直接量
        obj1.__proto__ = proto;                 //设置原型
        console.log( obj1.name);
        var obj2 = {};                          //普通对象直接量
        Object.setPrototypeOf(obj2, proto);     //设置原型
        console.log( obj2.name);
        var obj3 = Object.create(proto);        //创建对象,并设置原型
        console.log( obj3.name);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.4、检测原型

    使用isPrototypeOf方法可以判断该对象是否为参数对象的原型。isPrototypeOf是一个原型方法,可以在每个实例对象上调用。

    【示例】简单演示检测原型对象:

        var F = function(){};                           //构造函数
        var obj = new F();                              //实例化
        var proto1 = Object.getPrototypeOf( obj );      //引用原型
        console.log( proto1.isPrototypeOf(obj) );       //true
    
    • 1
    • 2
    • 3
    • 4

    提示:可以使用下面代码检测不同类型的实例:

        var proto = Object.prototype;
        console.log( proto.isPrototypeOf({}) );             //true
        console.log( proto.isPrototypeOf([]) );             //true
        console.log( proto.isPrototypeOf(/ /) );            //true
        console.log( proto.isPrototypeOf(function(){}) );   //true
        console.log( proto.isPrototypeOf(null) );           //false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.5、原型属性

    原型属性可以被所有实例访问,而私有属性只能被当前实例访问。

    【示例】定义一个构造函数,并为实例对象定义私有属性:

        function f(){                //声明一个构造类型
            this.a = 1;              //为构造类型声明一个私有属性
            this.b = function(){     //为构造类型声明一个私有方法
                return this.a;
            };
        }
        var e =new f();              //实例化构造类型
        console.log(e.a);            //调用实例对象的属性a,返回1
        console.log(e.b());          //调用实例对象的方法b,提示1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    构造函数f中定义了两个私有属性,分别是属性a和方法b()。当构造函数实例化后,实例对象继承了构造函数的私有属性,此时可以在本地修改实例对象的属性a和方法b():

        e.a = 2;
        console.log(e.a);
        console.log(e.b());
    
    • 1
    • 2
    • 3

    如果给构造函数定义了与原型属性同名的私有属性,则私有属性会覆盖原型属性值。

    如果使用delete运算符删除私有属性,则原型属性会被访问。在上面示例基础上删除私有属性,则会发现可以访问原型属性。

    2.6、原型链

    在JavaScript中,实例对象在读取属性时,总是先检查私有属性,如果存在,则返回私有属性值,否则就会检索prototype原型。如果找到同名属性,则返回protoype原型的属性值。

    protoype原型允许引用其他对象。如果在protoype原型中没有找到指定的属性,则JavaScript将会根据引用关系,继续检索protoype原型对象的protoype原型,以此类推。

    【示例】演示对象属性查找原型的基本方法和规律:

        function a(x){              //构造函数a
            this.x = x;
        }
        a.prototype.x = 0;          //原型属性x的值为0
        function b(x){              //构造函数b
            this.x = x;
        }
        b.prototype = new a(1);     //原型对象为构造函数a的实例
        function c(x){              //构造函数c
            this.x = x;
        }
        c.prototype = new b(2);     //原型对象为构造函数b的实例
        var d = new c(3);           //实例化构造函数c
        console.log(d.x);           //调用实例对象d的属性x,返回值为3
        delete d.x;                 //删除实例对象的私有属性x
        console.log(d.x);           //调用实例对象d的属性x,返回值为2
        delete c.prototype.x;       //删除c类的原型属性x
        console.log(d.x);           //调用实例对象d的属性x,返回值为1
        delete b.prototype.x;       //删除b类的原型属性x
        console.log(d.x);           //调用实例对象d的属性x,返回值为0
        delete a.prototype.x;       //删除a类的原型属性x
        console.log(d.x);           //调用实例对象d的属性x,返回值为undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    原型链能够帮助用户更清楚地认识JavaScript面向对象的继承关系,如下图所示:
    在这里插入图片描述

    3、类

    3.1、定义类

    在ECMAScript 5版本中,JavaScript是没有类这个概念的,其行为最接近的是创建一个构造函数,并在构造函数的原型上添加方法,这种方法也被称为自定义类型。

    ECMAScript 6引入了Class(类)的概念,作为对象的模板,通过class关键字,可以定义类。class关键字必须小写,后面跟类名

    【示例】演示定义Person类,并为其添加一个属性和一个方法:

        class Person{                    //等效于Person()构造函数
            constructor(name) {          //类的构造函数,constuctor关键字小写
                this.name = name;        //设置属性
            }
            sayName() {                  //设置方法,等效于 Person.prototype.sayName
                console.log(this.name);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    constructor()是类的默认方法,通过new命令生成实例时,会自动调用该方法。一个类必须有constructor()方法,如果没有显式定义,默认会添加一个空的constructor()方法。constructor()方法默认会返回实例对象(this),也可以返回另一个指定对象。

    ECMAScript 6为new命令引入了一个new.target属性,返回new命令作用于构造函数。如果构造函数不是通过new命令调用的,new.target将返回undefined。因此,在Class内部可以通过调用new.target访问当前Class。当子类继承父类时,new.target会返回子类。

    提示:ECMAScript 6中的class类只是对ECMAScript 5中的构造函数做了一次封装,其语法格式与Java编程语言的类相似。

    实例化类的方法与构造函数的实例化方法相同,都是使用new关键字。例如:

        let person = new Person("小张");
        person.sayName();                //输出“小张”
    
    • 1
    • 2

    注意:关于类的定义和实例化,需要注意以下几点:

    • 类声明和函数定义不同,类的声明是不会被提升的。类声明的行为与let比较相似。
    • 类的所有方法都是不可枚举的,这与自定义类型相比是不同的,因为后者需要使用Object.defineProperty()才能定义不可枚举的方法。
    • 所有方法都不能使用new调用。
    • 不能使用new调用类的构造函数,必须使用类的实例化方式调用构造函数。
    • 不允许在类的方法内部重写类名,因为在类的内部,类名是作为一个常量存在的。

    3.2、继承

    在Class中通过extends关键字可以实现继承,子类会继承父类的属性和方法。

    【示例】定义两个类Father、Son,通过extends关键字让Son继承Father:

        class Father{                          //父类
            constructor(name){                 //构造函数
                this.name = name;
            }
            sayName(){                         //本地方法
                console.log(this.name);
            }
        }
        class Son extends Father{              //子类,extents后面指定要继承的类型
            constructor(name, age){            //构造函数
                super(name);                   //相当于以前的Father.call(this, name);
                this.age = age;
            }
            sayAge(){                          //子类的私有方法
                console.log(this.age);
            }
        }
        var son1 = new Son("李四", 30);        //实例化
        son1.sayAge();                         //调用本地方法
        son1.sayName();                        //调用继承的方法
        console.log(son1 instanceof Son);      //返回true
        console.log(son1 instanceof Father);   //返回true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    super()作为函数时,指向父类的构造函数。super作为对象时,指向父类的原型对象。

    super代表父类的构造函数,但是返回的是子类的实例,即super内部的this指的是子类,因此super()相当于A.prototype.constructor.call(this)。

    使用super()时要注意以下几点:

    • 只能在子类的构造函数中使用super(),否则将抛出异常。
    • 必须在构造函数的起始位置调用super(),因为它会初始化this,任何在super()之前访问this的行为都会造成错误,即super()必须放在构造函数的首行。因为子类实例的构建,是基于对父类实例的加工,只有super()方法才能返回父类实例。
    • 在类的构造函数中,唯一能避免调用super()的办法是返回一个对象。

    提示:ECMAScript 5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ECMAScript 6的继承机制完全不同,实质是先创造父类的实例对象this,所以必须先调用super()方法,然后再用子类的构造函数修改this。如果子类没有定义constructor()方法,这个方法会被默认添加。

    3.3、静态方法

    类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类调用,也被称为静态方法。父类的静态方法,可以被子类继承。

    【示例】为类定义静态方法:

        class Father{                         //父类
          static foo(){                       //定义静态方法
              console.log("我是父类的静态方法");
          }
        }
        class Son extends Father{             //子类,继承自Father
        }
                                              //子类继承了父类的静态方法
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在子类身上调用父类的静态方法与直接通过父类名调用是一样的。

    4、模块

    JavaScript一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。当开发大型的、复杂的项目时,会非常麻烦。

    在ECMAScript 6之前,主要使用CommonJS和AMD模块加载方案,前者用于服务器,后者用于浏览器。ECMAScript 6实现了模块功能,主要由两个命令构成,简单说明如下:

    • export命令:显式指定输出的代码,用于用户自定义模块,规定对外接口。
    • import命令:用于输入其他模块提供的功能,同时创造命名空间(namespace),防止函数名冲突。

    【示例】演示如何使用JavaScript模块功能:

    1.foo.js

        export let counter = 3;              //指定输出变量
        export function inc(){               //指定输出函数
            counter++;
        }
    
    • 1
    • 2
    • 3
    • 4

    2.main.js

        import {counter, inc} from 'foo';        //从foo.js文件中导入变量counter和函数inc
        console.log(counter);                    //输出3
        inc();                                   //调用外部函数
        console.log(counter);                    //输出4
    
    • 1
    • 2
    • 3
    • 4
    • 使用export命令导出对象时,这个关键字可以无限次使用。
    • 使用import命令将其他模块导入指定模块时,可用来导入任意数量的模块。
    • 支持模块的异步加载,同时为加载模块提供编程支持。
  • 相关阅读:
    【Hello Linux】多路转接之 epoll
    maven创建父子依赖项目—pom
    ZZNUOJ_用Java编写程序实现1585:super prime(附源码)
    推理框架概览
    Python飞机大战项目终篇(一步一步实现---最全笔记)
    Android 12(S) 图像显示系统 - 简述Allocator/Mapper HAL服务的获取过程(十五)
    华为OD机试 - BOSS的收入 - 回溯(Java 2023 B卷 100分)
    做了这些年开发,今天第一次梳理了这三种常用的变量
    小程序 打开方式 页面效果 表单页面 点击跳到详情页 图标 获取后台数据 进行页面渲染
    帆软报表-SQL片段报错处理
  • 原文地址:https://blog.csdn.net/YYBDESHIJIE/article/details/134278286