Giter VIP home page Giter VIP logo

underscore-analysis's Introduction

underscore 源码阅读

希望通过阅读 underscore 这一工具库的源码,巩固自己的 javascript 功力,如果觉得我写得好, 欢迎给我一个 star。你的关注是我持续写作的最大动力。

Source Code

添加注释的 underscore 源码 -- 1.8.3

Article List

root 对象初始化

underscore 中的变量缓存与继承

硬绑定代码性能优化

解读 cb 函数

模拟 ES6 rest 运算符

Javascript 中的数据类型判断

Licence

MIT License

Copyright (c) 2017 Shawn Cheung

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Concat

Mail [[email protected]](mailto [email protected])

Blog zhangxiang958.github.io

underscore-analysis's People

Contributors

zhangxiang958 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

underscore-analysis's Issues

root 对象初始化

从头开始阅读源码:

使用 IIFE

underscore 将所有函数封装在一个 IIFE 里面, 避免污染全局对象:

(function(){
    
}());

第一行代码:

var root = typeof self == 'object' && self.self === self && self || 
          typeof global == 'object' && global.global === global && global ||
          this ||
          {};

self

第一行代码就有所收获, self 是 window.self, self 属性是对窗口的自身的只读引用, 经常会使用 self 来代替 window 对象. 在防 HTTP 劫持的时候就会使用 window.top 是不是等于 window.self 来判断是不是被劫持了. 第一行的意思就是判断代码是不是在客户端下运行也就是 window 对象里面, 如果 self 是一个对象而且 self.self === self 的话那么就说明是在浏览器端, 就将 self 值传给 root, 相当于下面:

var root = typeof self == 'object' && self.self === self && self;

// var root = true && true && self;  
// ==> 
// var root = self;

global

global 是 node 环境下的全局对象的引用, 类似于 self, global.global === global 的话就说明是 node 环境下, 那么就是:

var root = typeof global == 'object' && global.global && global;

// var root = true && true && global;
// ==>
// var root = global;

this

如果都不是那么就是在其他的一些 js 环境里面比如 web worker, 你可以在浏览器里面试试, 打印出来的 this 是 worker 对象:

new Worker('console.log(this);');  //  Worker { onmessage: null, onerror: null }

所以 root 用这个 this 对象.如果最后连 this 对象都不存在, 那么就让 root 值为一个对象.

for...in 循环的兼容性问题

本系列通过阅读 underscore 源码与实战进而体验函数式编程的**, 而非通过冗长的文字教程, 细读精度
约 1500 行的 underscore 有利于写出耦合度低, 符合函数式编程**的代码, 并且可以学到 call 与 apply 执行效率的不同进而进行代码性能优化的技巧等.

欢迎大家 star 或者 watch 本系列, 您的关注是作者的最大动力, 让我们一起持续进步.
本系列仓库: https://github.com/zhangxiang958/underscore-analysis

兼容 for...in 循环

我们在遍历对象的时候往往会使用 for...in 循环得到 object 的键值对,但是其实 for...in 是有兼容性问题的。
for in 循环是有 bug 的, 不过 bug 的触发条件是有限制的.
条件有两个:

  1. 需要在 IE9 以下的浏览器
  2. 被枚举的对象被重写了一些不可枚举属性.
    下面这段代码是用来兼容 for in 中的 bug 的。在 underscore 里面很少会使用 for..in 来遍历对象, 作者通过 _.keys 方法加上 for 循环代替了 for...in, 但是还是无法避免地在 _.keys 中使用了 for...in 循环:
 if (hasEnumBug) collectNonEnumProps(obj, keys);

上面这段代码就是在 _.keys 函数内部中的 for...in 循环中的一句代码, 判断是否有兼容问题, 若有则做兼容处理。
下面是兼容的详细代码:

var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
  var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
                      'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];

  var collectNonEnumProps = function(obj, keys) {
    var nonEnumIdx = nonEnumerableProps.length;
    var constructor = obj.constructor;
    var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;

    // Constructor is a special case.
    var prop = 'constructor';
    if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

    while (nonEnumIdx--) {
      prop = nonEnumerableProps[nonEnumIdx];
      if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
        keys.push(prop);
      }
    }
  };

这段代码就是用来兼容 bug 的.

var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');

第一: hasEnumBug 是用来判断有没有 for in bug 的, 因为重写了Object 原型中的 toString 属性,对于有兼容问题的浏览器而言,是打印不出对象中 toString 属性的值的, 因为 toString 属性在那些有问题的浏览器中属于不可重写的不可枚举属性,所以根据这个来判断是否有兼容问题。
然后. 核心在于在 collectNonEnumProps 函数, 第一先确定哪些属性名在低版本浏览器是不可枚举的, 分别是 valueOf, isPrototypeOf, toString, propertyIsEnumberable, hasOwnProperty toLocalString 这几个方法, 当然还有 constructor, 那为什么 constructor 属性需要独立抽取出来做特殊判断呢?

var constructor = obj.constructor;
    var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;

    // Constructor is a special case.
    var prop = 'constructor';
    if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

因为 constructor 属性有其特殊性,因为对于 toString 那些属性而言, 判断是否需要加入到 keys 数组中是根据 obj[prop0] !== proto[prop] 来判断的, 不一样说明重写了,说明这个属性是用户新定义的属性,需要作为一个数据属性名放到 keys 数组中去,但是 constructor 不能做这个判断,不能单纯使用 === 来判断是否被重写,因为对象在继承的时候 constructor 函数通常都不会等于 Object, 所以 constructor 需要被提出来单独判断。
在函数最后,得到不可枚举的属性名列表接下来就好办了, 就是判断对象里面的和原型链里面的同属姓名的值相不相同, 不相同就是重写了, 将这个属性名加到 keys 数组里面, 如果相同就不加. 这就是为什么一进入函数就需要存储 obj.prototype.
之前有人说说循环里面 prop in obj 多余, 这个并不是, 可以试试: 下面是我在 chrome 浏览器做的测试代码

var obj = Object.create(null);
'toString' in obj;  // false

这样是 false 的, underscore 为了避免这样的情况发生, 添加了判断.
最后说一下感受, for in 循环到底有没有必要进行兼容, 不兼容的危害大不大? 我认为并不大, 但是作为一个类库, 代码的严谨性是需要的, 但是我们在平时使用 for in 循环的时候有没有必要担惊受怕呢?
其实没有必要的, 我们如果不是特别需要, IE9 以下的兼容情况会越来越少, 而且一般来说我们也不会在对象里面去覆盖像 toString 这样的原型属性.

如果觉得有收获, 请到 github 给作者一个 star 表示支持吧, 谢谢大家.

解读 cb 函数

继续阅读 underscore 源码:

  var builtinIteratee;

  // An internal function to generate callbacks that can be applied to each
  // element in a collection, returning the desired result — either `identity`,
  // an arbitrary callback, a property matcher, or a property accessor.
  var cb = function(value, context, argCount) {
    if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
    if (value == null) return _.identity;
    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
    if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
    return _.property(value);
  };

  // External wrapper for our callback generator. Users may customize
  // `_.iteratee` if they want additional predicate/iteratee shorthand styles.
  // This abstraction hides the internal-only argCount argument.
  _.iteratee = builtinIteratee = function(value, context) {
    return cb(value, context, Infinity);
  };

cb 函数到底是做什么的? cb 函数是一个内部方法, 一下子可能看不出来它的功能, 但是我们可以借助 .iteratee 函数来看, 根据文档 ".iteratee 函数是用来生成可以应用到集合中每个元素的回调的, 返回想要的结果", 而 _.iteratee 函数主要是用在了 _.each, _.map, _.find, _.filter 等等的这些需要回调的函数的.

if(_.iteratee !== builtIteratee) return _.iteratee(value, context);

上面这句是判断 _.iteratee 函数是否有被重写, 如果已经被重写, 也就是 _.iteratee 与 builtinIteratee 变量缓存的函数不同时, 则使用用户自定义的那个函数并返回.

if(value == null) return _.identify;

这句是判断 value, 也就是第一个函数是不是为空, 因为像 _.each, _.map 这些函数是需要回调函数来构造出需要的数据(对象, 数组等)的, 但是用户有可能会省缺这个函数, 作为一个优秀的工具库, 当然
需要考虑这种情况, 所以当 value 为空的时候, 自动使用一个匿名函数, 然后这个匿名函数并没有做什么特别的操作, 只是将传入的参数直接返回而已, 为什么呢? 既然省缺了回调当然就不知道需要做什么操作,所以直接返回就可以了:

_.identify = function(value){
  return value;
}

接下来:

if (_.isFunction(value)) return optimizeCb(value, context, argCount);

这里会判断 value 是不是一个函数, 如果是函数, 说明用户传入回调, 那么就是用 optimizeCb 将传入的回调函数进行 this 硬绑定.

if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);

如果传入的 value 是一个非数组对象, 那么就返回一个匿名函数, 判断传入的 obj 是否包含了 value 对象: 如果包含则返回 true, 不包含则返回 false

_.matcher = _.matches = function(attrs) {
    attrs = _.extendOwn({}, attrs);
    return function(obj) {
      return _.isMatch(obj, attrs);
    };
  };

然后:

return _.property(value);

到了最后这一步, 那就说明传入的既不是函数, 也不是 key:value 形式的对象, 而是集合(数组)或者是字符串之类的值. _.property 函数返回一个匿名函数, 这个匿名函数会返回传入的对象的对应的键值,
而对应的键名也就是这里的 value 了.

Javascript 中的数据类型判断

本系列通过阅读 underscore 源码与实战进而体验函数式编程的**, 而非通过冗长的文字教程, 细读精度
约 1500 行的 underscore 有利于写出耦合度低, 符合函数式编程**的代码, 并且可以学到 call 与 apply 执行效率的不同进而进行代码性能优化的技巧等.

欢迎大家 star 或者 watch 本系列, 您的关注是作者的最大动力, 让我们一起持续进步.
本系列仓库: https://github.com/zhangxiang958/underscore-analysis

Typeof

我们都使用 typeof 是用来判断数据类型的命令, 在常规的场景中足以应付数据类型判断的需求:

var obj = {
   name: 'zhangxiang'
};

function foo() {
    console.log('this is a function');
}

var arr = [1,2,3];

console.log(typeof 1);  // number
console.log(typeof '1');  //string
console.log(typeof true);  //boolean
console.log(typeof null); //object
console.log(typeof undefined); //undefined
console.log(typeof obj); //object
console.log(typeof foo);  //function
console.log(typeof arr);   //object

可以看到, typeof 命令可以判断所有 javascript 中的基本数据类型(Null, Undefined, Boolean, String, Number), 虽然 null 使用 typeof 返回的是 object 字符串, 但是无碍
它的基本使用, 但是在一些复杂的场景比如 object 与 null, array 与 object, function 与 object 等等的类型区分, typeof 就会显得心有余力不足了.
所以一般来说, typeof 会使用在比较简单的场景, 比如你几乎可以确定数据是哪一类数据然后稍微加以区分的时候.举个简单的例子来说明情况:

function unique(array){
  var hash = {};
  var result = [], key;
  array.forEach(function(item, index){
    key = item;
    if(typeof item === 'string') {
      key = '_' + item;
    }
    if(!hash[key]) {
      result.push(item);
    } else {
      hash[key] = true;
    }
  });
  return result;
}

instanceof

instanceof 其实适合用于判断自定义的类实例对象, 而不是用来判断原生的数据类型, 举个例子:

// a.html
<script>
  var a = [1,2,3];
</script>
//main.html
<iframe src="a.html"></iframe>

<script>
  var frame = window.frame[0];
  var a = frame.a;
  console.log(a instanceof Array);  // false
  console.log(a.contructor === Array);  //false
  console.log(a instanceof frame.Array); // true
</script>

是什么原因导致上面的结果呢? 其实 iframe 之间不会共享原型链, 因为他们有独立的执行环境, 所以 frame a 中的数组 a 不会是本执行环境的实例对象. 通过特性嗅探同样不靠谱, 像通过 contructor
sort, slice 等等的特有的数组(或者其他数据类型)方法或属性, 万一对象中也有 sort, slice 属性, 就会发生误判. 所以最靠谱的方法是使用 Object.prototype.toString 方法.

Object.prototype.toString

使用 Object.prototype.toString 方法, 可以获取到变量的准确的类型.

function foo(){};

Object.prototype.toString.call(1);  '[object Number]'
Object.prototype.toString.call('1'); '[object String]'
Object.prototype.toString.call(NaN); '[object Number]'
Object.prototype.toString.call(foo);  '[object Function]'
Object.prototype.toString.call([1,2,3]); '[object Array]'
Object.prototype.toString.call(undefined); '[object Undefined]'
Object.prototype.toString.call(null); '[object Null]'
Object.prototype.toString.call(true); '[object Boolean]'
....

Object.prototype.toString 的原理是当调用的时候, 就取值内部的 [[Class]] 属性值, 然后拼接成 '[object ' + [[Class]] + ']' 这样的字符串并返回. 然后我们使用 call 方法来获取任何值的数据类型.

有用的数据类型判断函数

isArray polyfill

isArray 是数组类型内置的数据类型判断函数, 但是会有兼容性问题, 所以模拟 underscore 中的写法如下:

isArray = Array.isArray || function(array){
  return Object.prototype.toString.call(array) === '[object Array]';
}

isNaN polyfill

判断一个数是不是 NaN 不能单纯地使用 === 这样来判断, 因为 NaN 不与任何数相等, 包括自身, 所以:

isNaN: function(value){
  return isNumber(value) && isNaN(value);
}

这里的 isNumber 就是用上面所说的 Object.prototype.toString 进行判断的, 然后使用 isNaN 来判断值, 至于为什么需要在判断 isNaN 之前需要判断是不是 Number 类型, 这是因为 NaN 本身
也是数字类型(Object.prototype.toString 可知), 在 ES6 的 isNaN 中只有值为数字类型使用 NaN 才会返回 true, 这是为了模拟 ES6 的 isNaN.

判断是否是 DOM 元素

在实际项目里面, 有时或许我们需要判断是否是 DOM 元素对象, 那么在判断的时候利用的是 DOM 对象特有的 nodeType 属性:

isElement: function(obj){
  return !!(obj && obj.nodeType === 1);
}

判断是否是对象

isObject: function(obj){
  var type = typeof obj;
  return type === 'function' || typeof === 'object' && obj !== null;
}

这里的对象是狭义的, 是通常所指的 key-value 型的集合, 或者是 function 函数并且不为 null.

判断是否是 arguments 对象 polyfill

判断一个对象是不是 arguments 对象可以通过 Object.prototype.toString 来判断, 但是低版本的浏览器不支持, 他们返回的是 [object Object], 所以需要兼容:

isArguments: function(obj){
  return Object.prototype.toString.call(obj) === '[object Arguments]' || (obj != null && Object.hasOwnProperty.call(obj, 'callee'));
}

兼容做法原理是通过对象的 hasOwnProperty 方法来判断对象是否拥有 callee 属性从而判断是不是 arguments 对象.

如果觉得有收获, 请到 github 给作者一个 star 表示支持吧, 谢谢大家.

硬绑定代码性能优化

underscore 源码中有下面这一段代码

var optimizeCb = function(func, context, argCount) {
    if (context === void 0) return func;
    switch (argCount) {
      case 1: return function(value) {
        return func.call(context, value);
      };
      // The 2-parameter case has been omitted only because no current consumers
      // made use of it.
      case null:
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function() {
      return func.apply(context, arguments);
    };
  };

这个函数是什么意思?从整体上看并没有特别的意思,我们知道 this 的指向是动态的,所以在实际开发中肯定免不了对函数的 this 值进行硬绑定的做法,但是 bind 函数会有兼容性问题,
所以会倾向于使用 call 方法和 apply 方法,这两个原生函数在使用的时候区别就在于 call 后面是可以跟随 n 的参数,而 apply 后面是跟随数组形式的参数的,
那为什么 underscore 源码需要将这两种方法区分呢?可以看到 optimizeCb 函数会将传递参数少于或等于 4 个的采用 call 方法来绑定 this,
而对于多于 4 个参数的方法则会采用 apply 方法来进行绑定,其实这是代码性能优化的手段,apply 方法在执行的时候其实是比 call 方法要慢得多, apply 方法在执行的时候需要对传进来的参数数组
进行深拷贝:apply 内部执行伪代码

argList = [];

len = argArray.length;

index = 0;

while(index < len){
  indexName = index.toString();
  argList.push(argArray[indexName]);
  index = index + 1;
}

return func(argList);

此外,apply 内部还需要对传进来的参数数组进行判断到底是不是一个数组对象,是否是 null 或者 undefined,而对于 call 则简单许多:

argList = [];

if(argCount > 1) {
  while(arg) {
    argList.push(arg);
  }
}

return func(argList);

总的来说就是 apply 不管在什么时候,对参数的循环遍历都是必定执行的,而对于 call 来说则是当参数数目大于 1 的时候才会执行循环遍历,而且 apply 比 call 多了一些对参数的检查,
所以 call 比 apply 快,基于这一点,underscore 使用 optimize 函数优化需要硬绑定的代码性能。

模拟 ES6 rest 运算符

underscore 源码里面会常常需要用到对于 this 值的硬绑定, 其中让我们头疼的,其实是不定传参,虽然说我们可以通过对 argument 对象使用 slice 方法转为数组,但是在函数内部对 arguments 进行分割无疑代码耦合了, 因为 rest 数组的生成其实与具体函数的逻辑并无关系,所以 underscore 就采用一个通用的 restArgs 方法来进行对函数包装, 使之支持 rest 参数,个人认为这也是 underscore 函数式编程**的体现之一。
源码中有下面这段代码:

// Similar to ES6's rest param (http://ariya.ofilabs.com/2013/03/es6-and-rest-parameter.html)
  // This accumulates the arguments passed into an array, after a given index.
  var restArgs = function(func, startIndex) {
    startIndex = startIndex == null ? func.length - 1 : +startIndex;
    return function() {
      var length = Math.max(arguments.length - startIndex, 0),
          rest = Array(length),
          index = 0;
      for (; index < length; index++) {
        rest[index] = arguments[index + startIndex];
      }
      switch (startIndex) {
        case 0: return func.call(this, rest);
        case 1: return func.call(this, arguments[0], rest);
        case 2: return func.call(this, arguments[0], arguments[1], rest);
      }
      var args = Array(startIndex + 1);
      for (index = 0; index < startIndex; index++) {
        args[index] = arguments[index];
      }
      args[startIndex] = rest;
      return func.apply(this, args);
    };
  };

从原作者的注释可看出,这个函数是用来模仿 ES6 语法中的拓展运算符的. 那究竟 ES6 的函数 rest 运算符是怎么样的? 举个简单的例子就懂了:

function push(array, ...items){
  items.forEach(function(item){
    array.push(item);
    console.log(item);
  });
}

var a = [];
push(a, 1, 2, 3);

这里大家应该就懂了, items 代表的就是除了第一个定义的形参 array 之外, 剩余的其他参数都变成了 items 的一部分, 而 items 会将剩余的所有参数集中起来, 放入一个数组里面, 所以 items 本身 就是一个数组, 里面按顺序存放了除 array 之外的传进来的参数. 那么我们在没有 ES6 语法的情况下, 就需要使用 arguments 对象, 将传进来的没有对应形参名的参数放入到一个数组里面, 所以我们当然需要知道函数本来已经定义了多少个已经命名了的形参的数量, 假如原来函数已经定义了 2 个参数, 那么我们就从 arguments 的第三个参数也就是 arguments 转化后得到的数组的下标为 2 的元素开始放入到 rest 数组中. 这也就是 startIndex 的意义. 然后通过一个闭包, 缓存起 startIndex 的值, 然后将模拟 rest 数组的生成:

return function() {
  var length = Math.max(arguments.length - startIndex, 0),
      rest = Array(length),
      index = 0;
  for (; index < length; index++) {
    rest[index] = arguments[index + startIndex];
  }
};

初始化一个 rest 数组, 长度为除去命名参数剩下的参数数量, 利用 for 循环将 arguments 除去命名了的参数外得到的数组, 将里面的值赋给 rest 数组.

switch (startIndex) {
  case 0: return func.call(this, rest);
  case 1: return func.call(this, arguments[0], rest);
  case 2: return func.call(this, arguments[0], arguments[1], rest);
}
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
  args[index] = arguments[index];
}
args[startIndex] = rest;
return func.apply(this, args);

这里如果看过之前的 optimizeCb 函数这里就不难理解了, 也是对于 call 和 apply 函数的代码性能优化.如果命名形参少于 3 个就使用 call 来进行硬绑定, 多于 3 个则使用 apply, 最后一个参数就是 rest 数组.

tips:

startIndex 是用来定位下标的, 这里学到一个小技巧就是 function.length 代表函数定义形参的数量, 对于将字符串转化为数字可以使用 +"1" 这样就可以转化为数字 1.

underscore 中的变量缓存与继承

underscore 源码中会将一些常用的方法缓存起来:

var nativeIsArray = Array.isArray,
    nativeKeys = Object.keys,
    nativeCreate = Object.create;

将一些常用的原生方法缓存起来, 可以方便压缩和提高读取速度. Array.isArray 方法其实就是我们常用判断是不是数组的方法, 不过我自己常用的是 Object.prototype.toString.call() 这个方法 判断值是不是 [object Array], 如果是就是数组对象, 实际上 Array.isArray 的 polyfill 也是这么做的. Object.keys 是返回对象的所有键名数组, 这样就可以直接时候迭代器, 而不需要 for in 循环了. Object.create 方法是常用于原型继承的, 它返回一个新的对象, 这个对象和输入的对象已经进行了 原型链中的原型指针的连接(proto).

var Ctor = function(){};

先说第一个 var Ctor = function(){}, 为什么要声明这个函数呢? 看命名 Ctor 知道这是一个构造器, 一开始并不知道这个有什么用, 其实往下看就明白了: 下面这个函数是 underscore 134 行的函数, 其中用到了 Ctor

var baseCreate = function(prototype){
  if(!_.isObject(prototype)) return {};
  if(nativeCreate) return nativeCreate(prototype);
  Ctor.prototype = prototype;
  var result = new Ctor;
  Ctor.prototype = null;
  return result;
}

上面这个函数就是最最常用的组合继承的优化方式, 也就是优化组合继承 Sub.prorotype = new Super() 中子类原型对象有多一份父类的属性这个缺点的. 我们可以看到这个 baseCreate 函数本质上 就是为了连接对象的原型指针 proto 的, 所以 baseCreate 函数会优先使用 Object.create 方法, 如果不兼容则使用

Ctor.prototype = prototype;
var result = new Ctor;
Ctor.prototype = null;
return result;

这个方法在高程里面就有, 这里不细讲了, 这里的 Ctor 函数需要是一个"干净"的函数, 只是为了链接两个对象的原型链用, 使用完就会将 Ctor.prototype = null 还原.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.