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

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面向对象的精妙设计。

类继承和原型继承的区别

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

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

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

类继承和原型继承的区别

原文地址:https://www.cnblogs.com/fmyao/p/12813408.html