• 侯策《前端开发核心知识进阶》读书笔记——面向对象和原型


    new关键字做了什么

    • step1:首先创建一个空对象,这个对象将会作为执行 new 构造函数() 之后,返回的对象实例
    • step2:将上面创建的空对象的原型(__proto__),指向构造函数的 prototype 属性
    • step3:将这个空对象赋值给构造函数内部的 this,并执行构造函数逻辑
    • step4:根据构造函数执行逻辑,返回第一步创建的对象或者构造函数的显式返回值

    newFunc的模拟训练

    function Person(name) {
      this.name = name
    }
    
    const person = new newFunc(Person, 'lucas')
    
    console.log(person)
    
    // {name: "lucas"}

    对newFunc的实现:

    function newFunc(...args) {
      // 取出 args 数组第一个参数,即目标构造函数
      const constructor = args.shift()
    
      // 创建一个空对象,且这个空对象继承构造函数的 prototype 属性
      // 即实现 obj.__proto__ === constructor.prototype
      const obj = Object.create(constructor.prototype)
    
      // 执行构造函数,得到构造函数返回结果
      // 注意这里我们使用 apply,将构造函数内的 this 指向为 obj,注意:如果构造函数有返回值,则result为返回值,否则为undefined
      const result = constructor.apply(obj, args)
    
      // 如果造函数执行后,返回结果是对象类型,就直接返回,否则返回 obj 对象
      return (typeof result === 'object' && result != null) ? result : obj
    }

    上述三目运算的处理,其实和构造函数的显式返回有关:

    function Person(name) {
      this.name = name
      return {1: 1}
    }
    
    const person = new Person(Person, 'lucas')
    
    console.log(person)
    
    // {1: 1}

    如何优雅地实现继承

    JS创建对象的几种方式  

    JS原型继承与原型链(一)

    JS原型继承与原型链(二) 

    JS原型继承与原型链(三) 

    有关继承的知识点在之前的几篇博客中都讲得很清晰了,下面就是对知识的进一步梳理总结:

    其中原型链实现继承最关键的要点是:

    Child.prototype = new Parent()

    构造函数实现继承的要点是:

    function Child (args) {
        // ...
        Parent.call(this, args)
    }

    这样的实现其实只是实现了实例属性继承,Parent 原型的方法在 Child 实例中并不可用。

    组合继承的实现要点是:

    function Child (args1, args2) {
        // ...
        this.args2 = args2
        Parent.call(this, args1)
    }
    Child.prototype = new Parent()
    Child.prototype.constrcutor = Child

     Child 实例会存在 Parent 的实例属性。因为我们在 Child 构造函数中执行了 Parent 构造函数。同时,Child.__proto__ 也会存在同样的 Parent 的实例属性,且所有 Child 实例的 __proto__ 指向同一内存地址。同时上述实现也都没有对静态属性的继承。

     一个比较完整的实现:

    function inherit(Child, Parent) {
         // 继承原型上的属性 
        Child.prototype = Object.create(Parent.prototype)
    
         // 修复 constructor
        Child.prototype.constructor = Child
    
        // 存储超类
        Child.super = Parent
    
        // 静态属性继承
        if (Object.setPrototypeOf) {
            // setPrototypeOf es6
            Object.setPrototypeOf(Child, Parent)
        } else if (Child.__proto__) {
            // __proto__ es6 引入,但是部分浏览器早已支持
            Child.__proto__ = Parent
        } else {
            // 兼容 IE10 等陈旧浏览器
            // 将 Parent 上的静态属性和方法拷贝一份到 Child 上,不会覆盖 Child 上的方法
            for (var k in Parent) {
                if (Parent.hasOwnProperty(k) && !(k in Child)) {
                    Child[k] = Parent[k]
                }
            }
        }
    
    }

    静态属性继承存在一个问题:在陈旧浏览器中,属性和方法的继承我们是静态拷贝的,继承完后续父类的改动不会自动同步到子类。这是不同于正常面向对象思想的。但是这种组合式继承,已经相对完美、优雅。

    小细节:这种继承方式无法实现对 Date 对象的继承,因为: JavaScript 的日期对象只能通过 JavaScript Date 作为构造函数来实例化得到。

    如何实现对 Date 的继承呢?

    function DateConstructor() {
        var dateObj = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))()
      //实现DateConstructor.prototype.__proto__ === Date.prototype
        Object.setPrototypeOf(dateObj, DateConstructor.prototype)
    
        dateObj.foo = 'bar'
    
        return dateObj
    }
    
    Object.setPrototypeOf(DateConstructor.prototype, Date.prototype)
    
    DateConstructor.prototype.getMyTime = function getTime() {
        return this.getTime()
    }
    
    let date = new DateConstructor()
    
    console.log(date.getMyTime())

    ES6实现对Date的继承:

    class DateConstructor extends Date {
        constructor() {
            super()
            this.foo ='bar'
        }
        getMyTime() {
            return this.getTime()
        }
    }
    
    let date = new DateConstructor()
    
    date.getMyTime() // 1558921640586

    缺点:Babel 并没有对继承 Date 进行特殊处理,无法做到兼容。

    Babel 编译结果研究

    class Person {
        constructor(){
            this.type = 'person'
        }
    }
    
    class Student extends Person {
        constructor(){
            super()
        }
    }
    
    var student1 = new Student() student1.type // "person"
    
    student1 instanceof Student // true student1 instanceof Person // true student1.hasOwnProperty('type') // true

    Babel 编译后的代码

    var Person = function Person() {
        _classCallCheck(this, Person);
        this.type = 'person';
    };
    
    // 实现定义 Student 构造函数,它是一个自执行函数,接受父类构造函数为参数
    var Student = (function(_Person) {
        // 实现对父类原型链属性的继承
        _inherits(Student, _Person);
    
        // 将会返回这个函数作为完整的 Student 构造函数
        function Student() {
            // 使用检测
            _classCallCheck(this, Student);  
            // _get 的返回值可以先理解为父类构造函数       
            _get(Object.getPrototypeOf(Student.prototype), 'constructor', this).call(this);
        }
    
        return Student;
    })(Person);
    
    // _x为Student.prototype.__proto__
    // _x2为'constructor'
    // _x3为this
    var _get = function get(_x, _x2, _x3) {
        var _again = true;
        _function: while (_again) {
            var object = _x,
                property = _x2,
                receiver = _x3;
            _again = false;
            // Student.prototype.__proto__为null的处理
            if (object === null) object = Function.prototype;
            // 以下是为了完整复制父类原型链上的属性,包括属性特性的描述符
            var desc = Object.getOwnPropertyDescriptor(object, property);
            if (desc === undefined) {
                var parent = Object.getPrototypeOf(object);
                if (parent === null) {
                    return undefined;
                } else {
                    _x = parent;
                    _x2 = property;
                    _x3 = receiver;
                    _again = true;
                    desc = parent = undefined;
                    continue _function;
                }
            } else if ('value' in desc) {
                return desc.value;
            } else {
                var getter = desc.get;
                if (getter === undefined) {
                    return undefined;
                }
                return getter.call(receiver);
            }
        }
    };
    
    //让 Student 子类继承 Person 父类原型链上的方法
    function _inherits(subClass, superClass) {
        // superClass 需要为函数类型,否则会报错
        if (typeof superClass !== 'function' && superClass !== null) {
            throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
        }
        // Object.create 第二个参数是为了修复子类的 constructor
        subClass.prototype = Object.create(superClass && superClass.prototype, {
            constructor: {
                value: subClass,
                enumerable: false,
                writable: true,
                configurable: true
            }
        });
        // Object.setPrototypeOf 是否存在做了一个判断,否则使用 __proto__
        if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
    }

    jQuery 中的对象思想

    const pNodes = $('p')
    // 我们得到一个数组
    const divNodes= $('div')
    // 我们得到一个数组
    const pNodes = $('p')
    pNodes.addClass('className')
    //数组上可是没有 addClass 方法
    $('p')
    //$是一个函数
    $.ajax()
    //$是一个对象

    查看$的源码:

    var jQuery = (function(){
        var $
    
        // ...
    
        $ = function(selector, context) {
            return function (selector, context) {
                var dom = []
                dom.__proto__ = $.fn
    
                // ...
    
                return dom
            }
        }
    
        $.fn = {
            addClass: function() {
                // ...
            },
            // ...
        }
    
        $.ajax = function() {
            // ...
        }
    
        return $
    })()
    
    window.jQuery = jQuery
    window.$ === undefined && (window.$ = jQuery)

    当调用 $('p') 时,最终返回的是 dom,而 dom.__proto__ 指向了 $.fn$.fn 是包含了多种方法的对象集合。因此返回的结果(dom)可以在其原型链上找到 addClass 这样的方法。

    同时 ajax 方法直接挂载在构造函数 $ 上,它是一个静态属性方法。

    这个很微妙地表现出来了jQuery面向对象的精妙设计。

    类继承和原型继承的区别

    传统的面向对象语言的类继承,引发的一些问题:

    • 紧耦合问题
    • 脆弱基类问题
    • 层级僵化问题
    • 必然重复性问题
    • 大猩猩—香蕉问题

    对于类继承和原型继承的区别,我们可以参看这篇文章:

    类继承和原型继承的区别

  • 相关阅读:
    vb代码控制 Excel锁定单元格
    SendMessage
    vb 中Treeview控件的一些问题!
    NGWS runtime C# 开始学习 第一天 (2006.6.7)
    DTS Transform Data Task的使用
    GetTickCount
    ASP.NET 2.0 中Login控件的使用
    core dump解析(2)
    tcp滑动窗口机制
    linux 查看文件夹大小 du命令
  • 原文地址:https://www.cnblogs.com/fmyao/p/12813408.html
Copyright © 2020-2023  润新知