你不知道的原型链

140 阅读7分钟

原型链是什么

顾名思义,肯定是一条链,既然每个对象都有一个_proto_属性指向原型对象,那么原型对象也有_proto_指向原型对象的原型对象,直到指向上图中的null,这才到达原型链的顶端。

实现 new

function newFunc(...args) {
 // 取出 args 数组第一个参数,即目标构造函数
 const constructor = args.shift()

 // 创建一个空对象,且这个空对象继承构造函数的 prototype 属性
 // 即实现 obj.__proto__ === constructor.prototype
 const obj = Object.create(constructor.prototype)

 // 执行构造函数,得到构造函数返回结果
 // 注意这里我们使用 apply,将构造函数内的 this 指向为 obj
 const result = constructor.apply(obj, args)

 // 如果造函数执行后,返回结果是对象类型,就直接返回,否则返回 obj 对象
 return (typeof result === 'object' && result != null) ? result : obj
}

上述代码并不复杂,几个关键点:

  • 使用 Object.create 将 obj 的 __proto__ 指向为构造函数的原型
  • 使用 apply 方法,将构造函数内的 this 指向为 obj
  • 在 newFunc 返回时,使用三目运算符决定返回结果

JavaScript 中实现继承

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

Child.prototype = new Parent()

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

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

  • 组合继承的实现才基本可用,其要点是:
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__ 指向同一内存地址。

Object.appendChain 附加原型链

通过  Object.getPrototypeOf() 和 Object.prototype.__proto__ 的组合允许将一个原型链完整的附加到一个新的原型对象上

Object.appendChain = function(oChain, oProto) {
  if (arguments.length < 2) {
    throw new TypeError('Object.appendChain - Not enough arguments');
  }
  if (typeof oProto === 'number' || typeof oProto === 'boolean') {
    throw new TypeError('second argument to Object.appendChain must be an object or a string');
  }

  var oNewProto = oProto,
      oReturn,
      o2nd,
      oLast;

  oReturn = o2nd = oLast = oChain instanceof this ? oChain : new oChain.constructor(oChain);

  for (var o1st = this.getPrototypeOf(o2nd);
    o1st !== Object.prototype && o1st !== Function.prototype;
    o1st = this.getPrototypeOf(o2nd)
  ) {
    o2nd = o1st;
  }

  if (oProto.constructor === String) {
    oNewProto = Function.prototype;
    oReturn = Function.apply(null, Array.prototype.slice.call(arguments, 1));
    this.setPrototypeOf(oReturn, oLast);
  }

  this.setPrototypeOf(o2nd, oNewProto);
  return oReturn;
}

使用

例子一:向一个原型附加一个链

function Mammal() {
  this.isMammal = 'yes';
}

function MammalSpecies(sMammalSpecies) {
  this.species = sMammalSpecies;
}

MammalSpecies.prototype = new Mammal();
MammalSpecies.prototype.constructor = MammalSpecies;

var oCat = new MammalSpecies('Felis');

console.log(oCat.isMammal);
// 'yes'

function Animal() {
  this.breathing = 'yes';
}

Object.appendChain(oCat, new Animal());

console.log(oCat.breathing);
// 'yes'
复制代码

例子二:将一个基本类型转化为对应的对象类型并添加到原型链上

function Symbol() {
  this.isSymbol = 'yes';
}

var nPrime = 17;

console.log(typeof nPrime); // 'number'

var oPrime = Object.appendChain(nPrime, new Symbol());

console.log(oPrime); // '17'
console.log(oPrime.isSymbol); // 'yes'
console.log(typeof oPrime); // 'object'
复制代码

例子三:给函数类型的对象添加一个链,并添加一个新的方法到那个链上

function Person(sName) {
  this.identity = sName;
}

var george = Object.appendChain(new Person('George'), 'console.log("Hello guys!!");');

console.log(george.identity); // 'George'
george(); // 'Hello guys!!'

Object.assign()

Object.assign()  方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。

if (typeof Object.assign !== 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, "assign", {
    value: function assign(target, varArgs) { // .length of function is 2
      'use strict';
      if (target === null || target === undefined) {
        throw new TypeError('Cannot convert undefined or null to object');
      }

      var to = Object(target);

      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource !== null && nextSource !== undefined) {
          for (var nextKey in nextSource) {
            // Avoid bugs when hasOwnProperty is shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}

应用 合并具有相同属性的对象

const o1 = { a: 1, b: 1, c: 1 };
const o2 = { b: 2, c: 2 };
const o3 = { c: 3 };

const obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }

Object.setPrototypeOf()

Object.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或  null

var dict = Object.setPrototypeOf({}, null);
if (!Object.setPrototypeOf) {
    // 仅适用于Chrome和FireFox,在IE中不工作:
     Object.prototype.setPrototypeOf = function(obj, proto) {
         if(obj.__proto__) {
             obj.__proto__ = proto;
             return obj;
         } else {
             // 如果你想返回 prototype of Object.create(null):
             var Fn = function() {
                 for (var key in obj) {
                     Object.defineProperty(this, key, {
                         value: obj[key],
                     });
                 }
             };
             Fn.prototype = proto;
             return new Fn();
         }
     }
}

源码解析

模块化设计

image.png

开发时的流程

首次绘制无论绘制多少个图形,最终都是通过Stage类的add方法进行绘制,而后续的更新绘制则是通过,Layer类的draw方法进行绘制,接下来分别看下Stage::add和Layer::draw方法:

小结一下上面源码,以绑定position为例,调用Factory.addGetterSetter后做了三件事

  1. 添加setPosition方法,若Node类有定义setPosition,则直接执行,没有定义则调用_setAttr方法,更新node.attrs.position的值,触发positionChange事件,有的地方会像这样监听on('positionChange', xxx),最后调用到Shape::drawScene重绘
  2. 添加getPosition方法,同上,若Node类有定义getPosition,则直接执行,没有则绑定function
  3. 模拟getter和setter方法
var GET = 'get',
  SET = 'set';

export const Factory = {
  /**
   * addGetterSetter 模拟es6的getter和setter
   * @param constructor Node类
   * @param attr 'width'
   * @param def '0'
   * @param validator def参数的数据类型检查器
   * @param after 设置属性之后的回调事件
   */
  addGetterSetter(constructor, attr, def?, validator?, after?) {
    // 添加getXXX方法
    Factory.addGetter(constructor, attr, def);
    // 添加setXXX方法
    Factory.addSetter(constructor, attr, validator, after);
    // 添加getter和setter方法
    Factory.addOverloadedGetterSetter(constructor, attr);
  },
  // 设置getter
  addGetter(constructor, attr, def?) {
    // method = getWidth
    var method = GET + Util._capitalize(attr);

    // 为Node类的原型对象添加getWidth方法,这里有个很关键的判断,如果Node类上定义了getWidth方法直接使用,没定义则使用下面的函数
    constructor.prototype[method] =
      constructor.prototype[method] ||
      function () {
        var val = this.attrs[attr];
      	// 初始化时,获取def,例:addGetterSetter(Node, 'width', 0, getNumberValidator())
      	// 更新时,获取val,例:node.width(10) 
        return val === undefined ? def : val;  
      };
  },
  // 设置setter
  addSetter(constructor, attr, validator?, after?) {
    // method = setWidth
    var method = SET + Util._capitalize(attr);

    // 如果Node类中没有定义setWidth,则调用overWriteSetter重写setWidth
    if (!constructor.prototype[method]) {
      Factory.overWriteSetter(constructor, attr, validator, after);
    }
  },
  // 重写setter,绑定到原型对象上
  overWriteSetter(constructor, attr, validator?, after?) {
    // method = setWidth
    var method = SET + Util._capitalize(attr);
    constructor.prototype[method] = function (val) {
      // 检查更新时传入值得数据类型,不符合则抛错
      if (validator && val !== undefined && val !== null) {
        val = validator.call(this, val, attr);
      }

      // 更新node.attrs上属性,触发on('widthChange', xxx)事件,最终调用到Shape::drawScene重绘
      this._setAttr(attr, val);

      // 事后的回调,上面绑定filters时有用到
      if (after) {
        after.call(this);
      }

      return this;
    };
  },
  
  // ...
  
  // 设置getter和setter方法
  addOverloadedGetterSetter(constructor, attr) {
    var capitalizedAttr = Util._capitalize(attr),
      setter = SET + capitalizedAttr,  // setWidth
      getter = GET + capitalizedAttr;  // getWidth

    // 为Node类的原型对象设置width方法,有传参则为setter,未传参则为getter
    constructor.prototype[attr] = function () {
      // node.width(10)
      if (arguments.length) {
        this[setter](arguments[0]);
        return this;
      }
      // node.width()
      return this[getter]();
    };
  },
  
  // ...
};

模块化设计

image.png

开发时的流程

首次绘制无论绘制多少个图形,最终都是通过Stage类的add方法进行绘制,而后续的更新绘制则是通过,Layer类的draw方法进行绘制,接下来分别看下Stage::add和Layer::draw方法:

小结一下上面源码,以绑定position为例,调用Factory.addGetterSetter后做了三件事

  1. 添加setPosition方法,若Node类有定义setPosition,则直接执行,没有定义则调用_setAttr方法,更新node.attrs.position的值,触发positionChange事件,有的地方会像这样监听on('positionChange', xxx),最后调用到Shape::drawScene重绘
  2. 添加getPosition方法,同上,若Node类有定义getPosition,则直接执行,没有则绑定function
  3. 模拟getter和setter方法
var GET = 'get',
  SET = 'set';

export const Factory = {
  /**
   * addGetterSetter 模拟es6的getter和setter
   * @param constructor Node类
   * @param attr 'width'
   * @param def '0'
   * @param validator def参数的数据类型检查器
   * @param after 设置属性之后的回调事件
   */
  addGetterSetter(constructor, attr, def?, validator?, after?) {
    // 添加getXXX方法
    Factory.addGetter(constructor, attr, def);
    // 添加setXXX方法
    Factory.addSetter(constructor, attr, validator, after);
    // 添加getter和setter方法
    Factory.addOverloadedGetterSetter(constructor, attr);
  },
  // 设置getter
  addGetter(constructor, attr, def?) {
    // method = getWidth
    var method = GET + Util._capitalize(attr);

    // 为Node类的原型对象添加getWidth方法,这里有个很关键的判断,如果Node类上定义了getWidth方法直接使用,没定义则使用下面的函数
    constructor.prototype[method] =
      constructor.prototype[method] ||
      function () {
        var val = this.attrs[attr];
      	// 初始化时,获取def,例:addGetterSetter(Node, 'width', 0, getNumberValidator())
      	// 更新时,获取val,例:node.width(10) 
        return val === undefined ? def : val;  
      };
  },
  // 设置setter
  addSetter(constructor, attr, validator?, after?) {
    // method = setWidth
    var method = SET + Util._capitalize(attr);

    // 如果Node类中没有定义setWidth,则调用overWriteSetter重写setWidth
    if (!constructor.prototype[method]) {
      Factory.overWriteSetter(constructor, attr, validator, after);
    }
  },
  // 重写setter,绑定到原型对象上
  overWriteSetter(constructor, attr, validator?, after?) {
    // method = setWidth
    var method = SET + Util._capitalize(attr);
    constructor.prototype[method] = function (val) {
      // 检查更新时传入值得数据类型,不符合则抛错
      if (validator && val !== undefined && val !== null) {
        val = validator.call(this, val, attr);
      }

      // 更新node.attrs上属性,触发on('widthChange', xxx)事件,最终调用到Shape::drawScene重绘
      this._setAttr(attr, val);

      // 事后的回调,上面绑定filters时有用到
      if (after) {
        after.call(this);
      }

      return this;
    };
  },
  
  // ...
  
  // 设置getter和setter方法
  addOverloadedGetterSetter(constructor, attr) {
    var capitalizedAttr = Util._capitalize(attr),
      setter = SET + capitalizedAttr,  // setWidth
      getter = GET + capitalizedAttr;  // getWidth

    // 为Node类的原型对象设置width方法,有传参则为setter,未传参则为getter
    constructor.prototype[attr] = function () {
      // node.width(10)
      if (arguments.length) {
        this[setter](arguments[0]);
        return this;
      }
      // node.width()
      return this[getter]();
    };
  },
  
  // ...
};