美文网首页
53.4-JS新旧类构造、静态方法和this的问题

53.4-JS新旧类构造、静态方法和this的问题

作者: BeautifulSoulpy | 来源:发表于2020-05-27 15:00 被阅读0次
    如今这个年代,需用实力说话。不靠人情关系,就靠本事竞争。虽然这样比较辛苦,但于外能赢得别人尊重,于内能得到心里安稳,多好!你要知道,一个人如果不想过低三下四的生活,就必须有能让自己抬头挺胸的资本。所以,要不断提高自己,直面风雨人生!

    关键字:难点章节
    总结:

    1. 学完Python 学JAVA也很简单,主要头疼的问题是:一些框架、类该怎么用;
    2. 静态的概念和Python的静态不同,相当于Python中的类变量;
    3. 父类、子类使用同一种方式类定义属性或方法,子类覆盖父类;
    4. 普通函数调用,this 为全局对象;

    1. JS对象模型

    JavaScript 是一种基于原型(Prototype)的面向对象语言,而不是基于类的面向对象语言。

    C++、Java有类Class和实例Instance的概念,类是一类事物的抽象,而实例则是类的实体。

    JS是基于原型的语言,它只有原型对象的概念。原型对象就是一个模板,新的对象从这个模板构建从而获取最初的属性。任何对象在运行时可以动态的增加属性。而且,任何一个对象都可以作为另一个对象的原型,这样后者就可以共享前者的属性。

    2. 定义类

    字面式声明方式

    var obj = {
        property_1: value_1, // property_# may be an identifier...
        property_2: value_2, // or a number...
        ...,
        "property n": value_n  // or a string 
    };
    
    
    var obj = {
        a: 1,
        1: 'abc',
        'y': '123'
    }
    for (let x in obj)
        console.log(x, typeof (x));
    #----------------------------------------------------
    1 string
    a string
    y string
    

    这种方法也称作字面值创建对象。
    Js 1.2开始支持;

    3. JS定义类的两套标准(新老语法——ES6之前之后)

    ES6之前——构造器

    1、定义一个函数(构造器)对象,函数名首字母大写
    2、使用this定义属性
    3、使用new和构造器创建一个新对象

    new 构建一个新的通用对象,new操作符会将新对象的this值传递给Point3D构造器函数,函数为这个对象创建z属性。
    从上句话知道,new后得到一个对象,使用这个对象的this来调用构造器,那么如何执行“基类”的构造器方法呢?
    使用Point3D对象的this来执行Point的构造器,所以使用call方法,传入子类的this。
    最终,构造完成后,将对象赋给p2。

    
    var obj = {
        a: 1,
        1: 'abc',
        'y': '123'
    }
    for (let x in obj)
        console.log(x, typeof (x));
    console.log(typeof obj)
    console.log('----------------------------------')
    
    // ES6之前的类定义方式
    // 定义类,构造器 (首字母大写)
    function Point(x, y) {
        this.x = x;     // 普通函数调用,this 为全局对象;
        this.y = y;
        this.show = () => { console.log(this, this.x, this.y) };  // 对象方法;
        console.log('Point~~~~~~~~');
    }
    console.log(Point);
    
    p1 = new Point(4, 5);  // 所有的构造实例都要 用new;
    console.log(2,p1);
    console.log('----------------------------');
    p1.show()
    console.log('----------------------------');
    
    // 继承
    function Point3D(x, y, z) {
        Point.call(this, x, y); // "继承"  
        this.z = z;
        console.log('Point3D~~~~~~~~');
    }
    console.log(Point3D);
    p2 = new Point3D(14, 15, 16);
    console.log(3,p2);
    p2.show();
    #-----------------------------------------------------------
    Info: Start process (上午8:57:50)
    1 string
    a string
    y string
    object
    ----------------------------------
    [Function: Point]
    Point~~~~~~~~
    2 Point { x: 4, y: 5, show: [Function] }
    ----------------------------
    Point { x: 4, y: 5, show: [Function] } 4 5
    ----------------------------
    [Function: Point3D]
    Point~~~~~~~~
    Point3D~~~~~~~~
    3 Point3D { x: 14, y: 15, show: [Function], z: 16 }
    Point3D { x: 14, y: 15, show: [Function], z: 16 } 14 15
    Info: End process (上午8:57:51)
    

    能不能改成新的方式呢?

    ES6中的class

    从ES6开始,新提供了class关键字,使得创建对象更加简单、清晰(换汤不换药);

    1、类定义使用class关键字。创建的本质上还是函数,是一个特殊的函数;
    2、一个类只能拥有一个名为constructor的函数构造器方法;如果没有显示的定义一个构造方法,则会添加一个默认的constructor方法;
    3、继承使用extends关键字;
    4、一个构造器可以使用super关键字来调用一个父类的构造函数;
    5、类没有私有属性

    // 基类定义
    class Point {
        constructor(x, y) /*构造器*/ {
            this.x = x;
            this.y = y;
        }
        show() /*方法*/ {
            console.log(this, this.x, this.y);
        }
    }
    let p1 = new Point(10, 11)
    p1.show()
    
    // 继承
    class Point3D extends Point {
        constructor(x, y, z) {
            super(x, y); // 调用父类;
            this.z = z;
        }
    }
    let p2 = new Point3D(20, 21, 22);
    p2.show()
    #--------------------------------------------------------------------------
    Point { x: 10, y: 11 } 10 11
    Point3D { x: 20, y: 21, z: 22 } 20 21
    
    
    重写方法

    子类Point3D的show方法,需要重写

    // 基类定义
    class Point {
        constructor(x, y) /*构造器*/ {
            this.x = x;
            this.y = y;
        }
        show() /*方法*/ {
            console.log(this, this.x, this.y);
        }
    }
    let p1 = new Point(10, 11)
    p1.show()
    
    // 继承
    class Point3D extends Point {
        constructor(x, y, z) {
            super(x, y); // 调用父类;
            this.z = z;
        }
        show(){//重写
            console.log(this,this.x,this.y,this.z);
    
        }
    }
    let p2 = new Point3D(20, 21, 22);
    p2.show()
    #-----------------------------------------------------------------------------
    Point { x: 10, y: 11 } 10 11
    Point3D { x: 20, y: 21, z: 22 } 20 21
    

    子类中直接重写父类的方法即可。如果需要使用父类的方法,使用super.method()的 方式调用
    使用箭头函数重写上面的方法

    // 基类定义
    // 基类定义
    class Point {
        constructor(x, y) /*构造器*/ {
            this.x = x;
            this.y = y;
            //this.show = function () {console.log(this,this.x,this.y)};
            this.show = () => console.log('Point');
        }
    }
    // 继承
    class Point3D extends Point {
        constructor(x, y, z) {
            super(x, y);
            this.z = z;
            this.show = () => console.log('Point3D'); // 子类方法覆盖父类的方法;
        }
    }
    let p2 = new Point3D(20, 21, 22);
    p2.show(); // Point3D
    
    

    从运行结果来看,箭头函数也支持子类的覆盖

    // 基类定义
    class Point {
        constructor(x, y) /*构造器*/ {
            this.x = x;
            this.y = y;
            //this.show = function () {console.log(this,this.x,this.y)};
            this.show = () => console.log('Point');
        }
    }
    // 继承
    class Point3D extends Point {
        constructor(x, y, z) {
            super(x, y);
            this.z = z;
            //this.show = () => console.log('Point3D'); // 子类方法覆盖父类的方法;
        }
        show(){// 重写
            console.log('Point3D');
        }
    }
    let p2 = new Point3D(20, 21, 22);
    p2.show(); // Point
    

    上例优先使用了父类的属性show

    // 基类定义
    class Point {
        constructor(x, y) /*构造器*/ {
            this.x = x;
            this.y = y;
            //this.show = () => console.log('Point');
        }
        show() /*方法*/ {
            console.log(this, this.x, this.y);
        }
    }
    
    // 继承
    class Point3D extends Point {
        constructor(x, y, z) {
            super(x, y);
            this.z = z;
            this.show = () => console.log('Point3D');
        }
    }
    
    let p2 = new Point3D(20, 21, 22);
    p2.show(); // Point3D
    

    优先使用了子类的属性

    总结

    1. 父类、子类使用同一种方式类定义属性或方法,子类覆盖父类;
    2. 访问同名属性或方法时,优先使用属性;
    静态属性

    静态属性目前还没有得到很好的支持;

    静态方法

    在方法名前加上static,就是静态方法了;

    class Add {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        static print() {
            console.log(this.x); // ? this是什么
        }
    }
    add = new Add(40, 50);
    console.log(Add);
    Add.print();
    //add.print(); // 实例不能访问直接访问静态方法,和C++、Java一致
    add.constructor.print(); // 实例可以通过constructor访问静态方法
    

    静态方法中的this是Add类,而不是Add的实例
    注意:静态的概念和Python的静态不同,相当于Python中的类变量。

    4. this的坑

    虽然Js和 C++ 、Java一样有this,但是Js的表现是不同的。
    定义:javascript 中 this 总是指向一个对象,指向哪个对象,则根据函数执行环境动态绑定,不是函数声明。

    var school = {
        name: 'magedu',
        getNameFunc: function (){
            console.log(this.name);
            console.log(this);
            return function () {
                console.log(this === global); // this是否是global对象
                return this.name;    // 普通函数调用,this 为全局对象;
            };
        }
    };
    
    console.log(school.getNameFunc()());
    #运行结果------------------------------------------------------------
    magedu
    { name: 'magedu', getNameFunc: [Function: getNameFunc] }
    true
    undefined
    

    将 myObj 对象的 getName 方法赋值 getName 变量,这时这个方法被间接挂在了 window 上,所以 this 就指向了 window,在严格模式下也是无法找对 this 的宿主对象,也是 undefined。

    this的指向

    除去js中不常用的 with和 eval 的情况,this的指向大致分为以下四类:

    • 作为对象的方法调用
    • 作为普通函数调用
    • 构造器调用
    • Function.prototype.call 或 Function.prototype.apply 调用

    为了分析上面的程序,先学习一些知识:
    函数执行时,会开启新的执行上下文环境ExecutionContext。
    创建this属性,但是this是什么就要看函数是怎么调用的了。

    1、myFunction(1,2,3),普通函数调用方式,this指向全局对象。全局对象是nodejs的global或者浏览器中的window。
    2、myObject.myFunction(1,2,3),对象方法的调用方式,this指向包含该方法的对象。
    3、call和apply方法调用,要看第一个参数是谁。

    分析上例

    1. magedu 和 { name: 'magedu', getNameFunc: [Function: getNameFunc] } 很好理解。
    2. 第三行打印的true,是 console.log(this == global) 执行的结果,说明当前是global,因为调用这个返回的函数是直接调用的,这就是个普通函数调用,所以this是全局对象;
    3. 第四行undefined,就是因为this是global,没有name属性。
    4. 这就是函数调用的时候,调用方式不同,this对应的对象不同,它已经不是C++、Java的指向实例本身了。this的问题,这是历史遗留问题,新版只能保留且兼容了。

    而我们在使用时,有时候需要明确的让this必须是我们期望的对象,如何解决这个问题呢?

    1 显式传入
    var school = {
        name: 'magedu',
        getNameFunc: function (){
            console.log(this.name);
            console.log(this);
            return function (that) {
                console.log(this === global); // this是否是global对象
                return that.name;
            };
        }
    };
    
    console.log(school.getNameFunc()(school)); // ()1 方法调用,()2 函数调用;
    #---------------------------------------------------------------------------------------------------------
    magedu
    { name: 'magedu', getNameFunc: [Function: getNameFunc] }
    true
    magedu
    

    通过主动传入对象,这样就避开了this 的问题;

    2 ES3(ES-262第三版)引入了apply、call方法

    apply、call方法 都是函数对象的方法,第一参数都是传入对象引入的;
    apply传其他参数 需要使用数组;
    call传其他参数 需要使用可变参数收集;

    call 和 apply
    区别:作用一致,参数形式不同;

    var func = function( a, b, c ){
        console.log ([a, b, c]); // 输出 [ 1, 2, 3 ]
    };
    // func.apply( null, [ 1, 2, 3 ] ); // apply 需要一个数组;
    func.call(null, 1,2,3)  // call 方法数 往后列就行
    

    分析: 上面的代码只是用于传参,用于 this 指向的改变,可以参考 this 指向的第四点,此地,不再赘述

    var school = {
        name: 'magedu',
        getNameFunc: function (){
            console.log(this.name);
            console.log(this);
            return function () {
                console.log(this === global); // this是否是global对象
                return this.name;
            };
        }
    };
    
    console.log(school.getNameFunc().call(school)); // 
    #-----------------------------------------------------------------------------
    magedu
    { name: 'magedu', getNameFunc: [Function: getNameFunc] }
    false
    magedu
    
    3 ES5 引入了bind方法

    bind方法来设置函数的this值

    var school = {
        name: 'magedu',
        getNameFunc: function () {
            console.log(this.name);
            console.log(this);
            return function () {
                console.log(this === global); // this是否是global对象
                return this.name;
            };
        }
    };
    
    console.log(school.getNameFunc().bind(school)); 
    #-----------------------------------------------------------------
    magedu
    { name: 'magedu', getNameFunc: [Function: getNameFunc] }
    [Function: bound ]
    

    只打印了三行,说明哪里有问题,问题出在bind方法用错了。

    apply、call方法,参数不同,调用时传入this。
    bind方法是为函数先绑定this,调用时直接用。

    var school = {
        name: 'magedu',
        getNameFunc: function () {
            console.log(this.name);
            console.log(this);
            return function () {
                console.log(this == global); // this是否是global对象
                return this.name;
            };
        }
    }
    
    var func = school.getNameFunc();
    console.log(func);
    console.log('-------------------------------')
    var boundfunc = func.bind(school); // bind绑定后返回新的函数
    console.log(boundfunc);
    console.log(boundfunc());
    #-------------------------------------------------------
    magedu
    { name: 'magedu', getNameFunc: [Function: getNameFunc] }
    [Function]
    -------------------------------
    [Function: bound ]
    false
    magedu
    
    4 ES6引入支持this的箭头函数

    ES6 新技术,就不需要兼容this问题;

    var school = {
        name: 'magedu',
        getNameFunc: function () {
            console.log(this.name);
            console.log(this);
            return () => {
                console.log(this == global); // this是否是global对象
                return this.name;
            };
        }
    }
    
    console.log(school.getNameFunc()());
    #---------------------------------------------------------
    magedu
    { name: 'magedu', getNameFunc: [Function: getNameFunc] }
    false
    magedu
    

    ES6 新的定义方式如下

    class school {
        constructor() {
            this.name = 'magedu';
        }
        getNameFunc() {
            console.log(this.name);
            console.log(this, typeof (this));
            return () => {
                console.log(this == global); // this是否是global对象
                return this.name;
            };
        }
    }
    console.log(new school().getNameFunc()());
    /* 运行结果
    magedu
    school { name: 'magedu' } 'object'
    false
    magedu
    */
    

    5. 高阶对象、高阶类、或称Mixin模式

    相关文章

      网友评论

          本文标题:53.4-JS新旧类构造、静态方法和this的问题

          本文链接:https://www.haomeiwen.com/subject/dzyiahtx.html