lodash源码分析(create.js)

本系列使用 lodash 4.17.4

前言

没有引用任何文件

正文

/**
 * Creates an object that inherits from the `prototype` object. If a
 * `properties` object is given, its own enumerable string keyed properties
 * are assigned to the created object.
 *
 * @since 2.3.0
 * @category Object
 * @param {Object} prototype The object to inherit from.
 * @param {Object} [properties] The properties to assign to the object.
 * @returns {Object} Returns the new object.
 * @example
 *
 * function Shape() {
 *   this.x = 0
 *   this.y = 0
 * }
 *
 * function Circle() {
 *   Shape.call(this)
 * }
 *
 * Circle.prototype = create(Shape.prototype, {
 *   'constructor': Circle
 * })
 *
 * const circle = new Circle
 * circle instanceof Circle
 * // => true
 *
 * circle instanceof Shape
 * // => true
 */
function create(prototype, properties) {
  prototype = prototype === null ? null : Object(prototype)
  const result = Object.create(prototype)
  return properties == null ? result : Object.assign(result, properties)
}

export default create
复制代码

这个函数的做用是建立某个原型对象的新对象,能够传入可枚举的配置属性对新生成的对象进行属性的配置修改,主要使用到的仍是Object.create()函数。虽然这个函数代码简短,可是设计到原型、及其基于原型的继承和对象属性拷贝,涉及的知识点仍是比较多的。下面我就简单的分析一下包含的知识点而后再回来看这段代码及其实例吧。

1.原型


咱们都知道javascript是基于原型的一门语言。原型是javascript中比较重要也是比较难的一点。初学的时候很容易被卷入鸡生蛋,蛋生鸡的死胡同里。不信,咱们来试试几个题:

Function.prototype === Function.__proto__   //true 
Function.prototype === Object.__proto__     //true
Function.prototype.__proto__ === Object.prototype //true
Object.prototype === Object.__proto__       //false
Function instanceof Function                //true
Object instanceof Object                    //true
Object instanceof Function                  //true
Function instanceof Object                  //true
Object.__proto__ instanceof Object          //true
Object.prototype instanceof Object          //false
Function.prototype instanceof Object        //true
Function.__proto__ instanceof Object        //true
先有Function仍是先有Object构造函数?          //没有前后顺序,若是有那也是做者编写代码时的前后

/*全部对象由构造函数生成,而构造函数也是对象,那这个构造函数对象是怎么生成的呢?若是把对象比做鸡,构造函数比做蛋,那就是鸡生蛋蛋生鸡的悖论*/
/*归根到底就是
Function构造函数自己也算是Function类型的实例吗?
Function构造函数的prototype属性和__proto__属性都指向同一个原型,是否能够说Function对象是由Function构造函数建立的一个实例?*/
/*这一悖论的根源来自Object和Function互为对方的实例*/
复制代码

若是对上述问题你都知道,而且对其有较深的理解,那么你可能已经理解了什么是原型。固然若是你对这几道题还有疑惑,那么你就须要更加深刻的理解这一知识点。

好了,话说回来,我来简单的复习一下原型的知识点和应当注意的地方,而且解决掉鸡生蛋蛋生鸡的问题。

根据js红宝书讲的,每个构造函数A建立的时候都会建立另一个对象,这个对象为A.prototype,(咱们知道函数也是一个对象,因此之后就叫函数为函数对象,还要注意不是全部函数都有prototype属性,好比 Object.prototype.toString.bind(Array)),此时该函数对象存在一个属性叫prototype,其指向为这个自动建立的对象(原型对象)。而且使用该构造函数建立实例时该实例a会存在 __proto__属性(实际上是 [[Prototype]]属性,只不过Firefox和Chrome提供了"__proto__"这个非标准的访问器)来指向该原型对象A.prototype。因此咱们能够知道当建立一个构造函数和实例化其构造函数生成的实例以及自动建立的对象(原型对象)关系以下:


这里咱们能够提出几个疑问了?javascript

  1. 原型对象是怎么建立的或者说是经过什么方式建立的,new 仍是 对象字面量?有没有构造函数?若是有,构造函数是什么?
  2. 构造函数对象又是怎么建立的?有没有构造函数?若是有,构造函数是什么?
  3. 全部对象都是构造函数的实例吗?或者全部对象都是Object的实例吗?
  4. 鸡生蛋蛋生鸡问题

要解决这几个问题咱们须要了解几个重要概念(哲学三问~~~~)
html

1.什么是对象,什么是实例对象java

An object is a collection of properties and has a single prototype object. The prototype may be the null value.

全部的具备属性的数据集合就是对象。而实例对象是由构造函数建立的一个具象化物品,全部的实例对象都是对象,而不是全部对象都是实例对象。好比最多见的Object.prototype就是内置对象,并非任何构造函数的实例。固然咱们遇到的大多数都是实例对象。c++

2.什么是函数bash

ECMAScript规范定义的函数:函数

对象类型的成员,标准内置构造器 Function的一个实例,而且可作为子程序被调用。
注: 函数除了拥有命名的属性,还包含可执行代码、状态,用来肯定被调用时的行为。函数的代码不限于 ECMAScript。

函数就是构造函数Function的实例。那什么是构造函数呢?ECMAScript规范如此定义:源码分析

建立和初始化对象的函数对象
注:构造器的“prototype”属性值是一个原型对象,它用来实现继承和共享属性。

构造函数对象做为一个函数对象均实例化自构造函数Function(构造函数对象是实例化对象的一部分),固然构造函数Function也是函数对象,也实例化自Function(这里你须要明白实例化是将原型对象的属性拷贝到实例化对象中,而Function的原型Function.prototype是个内置对象)。因此若是不考虑继承的话,构造函数对象都是基于Function.prototype这个内置对象的。ui

注:对于Function是不是Function构造函数实例存在必定的争议

3.什么是原型this

ECMAScript标准以下:
spa

为其余对象提供共享属性的对象。
当构造器建立一个对象,为了解决对象的属性引用,该对象会隐式引用构造器的“prototype”属性。经过程序表达式 constructor.prototype 能够引用到构造器的“prototype”属性,而且添加到对象原型里的属性,会经过继承与全部共享此原型的对象共享。另外,可以使用 Object.create 内置函数,经过明确指定原型来建立一个新对象。

原型就是为对象提供共享属性的对象,否则每一个对象的属性都是私有的,致使空间浪费之类的问题。其建立是在建立构造函数的时候隐式建立的。其属性constructor指向构造函数,而构造函数的prototype属性指向原型对象。

4.咱们说一个对象是某个构造函数实例的根据是啥

咱们通常经过instanceof 判断对象是不是构造函数的实例,而instanceof 实际是调用hasInstance,而hasInstance的循环调用prototype直到最顶上那个对象,若是这个与第二个参数的prototype一致,那就是真,不然假。也就是遍历实例对象的原型链并判断构造函数的原型是否在该原型链上。是Object.getPrototypeOf(Function) === Function.prototype 或者简单来讲Object.__proto__ === Function.prototype的结果,只是一种运算关系,知足这种关系就断定是该构造函数的实例。

好了回到正题,咱们一一解决上述问题:

1.原型对象经过调用Object.create()来建立,是Object的实例。能够经过A.prototype.__proto__  === Object.prototype来讲明

2.构造函数对象经过构造函数的构造函数Function来建立,构造函数对象是Function的实例。因为构造函数Function也是对象,因此Function既是鸡也是蛋。具体的请看4.

3.什么是对象那里已经回答了,不是全部对象都是Object的实例

4.要解决鸡生蛋蛋生鸡问题,不能从javascript的角度上思考,必须跳出来从编译角度上看这一问题。javascript的实现不是javascript,而是更为底层的语言,好比c,c++之类的。咱们能够很轻易的知道Function这个函数对象是内置对象(不晓得是经过啥语言建立了这个对象),只不过其属性prototype和__proto__都指向另一个内置对象Function.prototype,因此从咱们javascript语法的角度就会获得悖论,但其实是该对象先存在,只不过表现为这种关系罢了。(感受只要说不过去扯上内置对象均可以说过去啊23333)

扯不下去了。下面贴一个对原型解释很好的图:

给个别人的解释:

JavaScript引擎是个工厂。
最初,工厂作了一个最原始的产品原型。
这个原型叫Object.prototype,本质上就是一组无序key-value存储({})
以后,工厂在Object.prototype的基础上,研发出了能够保存一段“指令”并“生产产品”的原型产品,叫函数。
起名为Function.prototype,本质上就是[Function: Empty](空函数)
为了规模化生产,工厂在函数的基础上,生产出了两个构造器:
生产函数的构造器叫Function,生产kv存储的构造器叫Object。
你在工厂定制了一个产品,工厂根据Object.prototype给你作了一个Foo.prototype。
而后工厂发现你定制的产品很不错。就在Function.prototype的基础上作了一个Foo的构造器,叫Foo。
工厂在每一个产品上打了个标签__proto__,以标明这个产品是从哪一个原型生产的。
为原型打了个标签constructor,标明哪一个构造器能够依照这个原型生产产品。
为构造器打了标签prototype,标明这个构造器能够从哪一个原型生产产品。
因此,我以为先有Function仍是Object,就看工厂先造谁了。其实先作哪一个都无所谓。由于在你定制以前,他们都作好了。

再给个比较好的连接:

javascript 世界万物诞生记

2.原型链和继承

原型链就是若是一个实例对象a的原型对象A.prototype为另一个实例对象b的话,a就能够共享a.__proto__,a.__proto__.__proto__(b.__proto__)上的属性,这个原型之间链状的东西咱们就叫他原型链。其主要做用是用于继承。

继承是为了解决new的实例对象不能共享属性和方法的缺点。不说啥了,上六种继承方式:

只给连接,不想写了。。。。。。有空的时候再修改和放图吧。。。

继承的六种方式

源码分析

1.Object()方法描述:

  • obj if obj is an object
  • {} if obj is undefined or null

这玩意儿和这个语句差很少:obj = obj || {},可是区别在于若是传入的obj是原始值,好比4,这个方法会返回包装过的Number对象而不是4这个原始值。

2.Object.create(prop):根据传入的对象建立一个将该对象做为原型的对象,执行空的构造函数。

Object.create =  function (o) {
    var F = function () {};
    F.prototype = o;
    return new F();
};
复制代码

Object.create是内部定义一个对象,而且让F.prototype对象 赋值为引进的对象/函数 o,并return出一个新的对象。

new作法是新建一个实例对象o,而且让o的__proto__指向了原型对象。而且使用call 进行强转做用环境。从而实现了实例的建立。

偷偷截张图:


3.Object.assign():主要用于对象属性合并和添加。没什么好说的。注意的是其方式是浅拷贝,而不是深拷贝。也就是说,若是源对象某个属性的值是对象,那么目标对象拷贝获得的是这个对象的引用。若是要实现深拷贝须要递归的进行属性拷贝。并且这个方法能够被...代替使用


使用方式

function Shape() {
    this.x = 0
    this.y = 0
  }
 
  function Circle() {
    Shape.call(this)
  }
 
  Circle.prototype = create(Shape.prototype, {
    'constructor': Circle
  })
 
  const circle = new Circle
  circle instanceof Circle
  // => true

  circle instanceof Shape
  // => true复制代码

Object.create(proto[, propertiesObject])没啥区别。用于实现类式继承

使用场景

用于寄生组合式继承避免屡次调用构造函数。其余的。。。。没时间了,,

结语

两天赶完了,因为时间跨度缘由,估计有些地方牛头不对马嘴。mayiga。。。

相关文章
相关标签/搜索