前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JS 原生方法原理探究(二):如何实现 Object.create?

JS 原生方法原理探究(二):如何实现 Object.create?

作者头像
Chor
发布2021-06-08 21:26:45
1.9K0
发布2021-06-08 21:26:45
举报
文章被收录于专栏:前端之旅

这是JS 原生方法原理探究系列的第二篇文章。本文会介绍如何实现 Object.create() 方法。关于这个方法的具体用法,MDN 已经描述得很清楚了,这里我们只做简单的介绍,具体的重点在于如何模拟实现。

语法简介

调用:Object.create ( proto , propertiesObject ) 返回: 一个新的实例对象

调用这个方法的时候接受两个参数,第一个参数作为返回对象的 __proto__,这个参数只能是 null 或者对象(而且不能是基本类型的包装对象)。

第二个参数作为返回对象的属性描述,它和 Object.defineProperties() 的第二个参数形式是一样的:

代码语言:javascript
复制
{
    propertyA: {
        value: xxx,
        configurable: xxx,
        enumerable: xxx,
        writable: xxx    
    },
    propertyB: {...},
    propertyC: {...}    
}

这个参数的每一个属性都会作为返回对象的属性,而属性值则是相应属性的特性描述(该属性的属性值、是否可读、是否可枚举、是否可配置)。第二个参数只能是对象或者 undefined(表示没有传第二个参数),不能是 null。

ES 规范

对于 Object.create() 的具体实现,规范中其实已经描述得很清楚,可以进入http://es5.github.io/#x15.2.3.5查看:

我简单翻译一下这段话:

create() 方法会创建一个具有指定原型的新对象,当调用该方法的时候,会有如下步骤:

  1. 如果传入的参数 O 不是对象也不是 null,抛出 TypeError 错误
  2. obj 作为调用 new Object() 方法所创建的新对象
  3. obj 的内部属性 [[prototype]] 设置为 O
  4. 如果提供了第二个参数 Properties,且不是 undefined,则调用 Object.defineProperties 方法并传入 objProperties 作为参数,从而为 obj 添加它自己的属性
  5. 返回 obj

可以说,整个过程是一目了然的,我们实现的时候也只需要按照上述步骤实现即可。

代码实现

我们先看第一种实现:

代码语言:javascript
复制
Object.create = function(proto,propertiesObject){
    if(typeof proto != 'object' && proto !== null){
        throw new Error('the first param must be an object or null')
    }
    if(typeof propertiesObject === null){
        throw 'TypeError'
    }
    let obj = {}
    obj.__proto__ = proto
    if(propertiesObject){
        Object.defineProperties(obj,propertiesObject)
    }
    return obj
}

基本上没有什么大问题。不过,我们要留意两个地方:

  • 在这个实现中,没有检测第一个参数是不是基本类型的包装对象,只要传进来的参数是对象,我们就认为是合法的
  • 当传入 null 也即 Object.create(null) 的时候,我们实际上创建了一个很纯粹的空对象,这个对象的原型直接就是 null,Object.prototype 甚至没有出现在该对象的原型链中,这意味这个对象不会继承 Object 的任何方法。

此外,你还可能在其他地方看到类似下面这样的实现:

具体实现如下:

代码语言:javascript
复制
Object.create = function(proto,propertiesObject){
    if(typeof proto != 'object' && proto !== null){
        throw new Error('the first param must be an object or null')
    }
    if(propertiesObject === null){
        throw 'TypeError'
    }
    function F(){}
    F.prototype = proto
    const obj = new F()
    // 处理传参 null 的情况
    if(proto === null){
        obj.__proto__ = proto
    }
    if(propertiesObject){
        Object.defineProperties(obj,propertiesObject)
    }
    return obj
}

这个实现和前面的实现有一个很关键的区别:代码中单独处理了传参 protonull 的情况。可能你会觉得很奇怪:当 protonull 的时候,F.prototype = proto 的效果和 obj.__proto__ = proto 应该是一样的,为什么还要在这种情况下执行一遍 obj.__proto__ = proto 呢?这似乎说明,用 null 重写 F 的原型后,新创建的实例的 __proto__ 并不是 null —— 事实上确实不是。

关于调用构造函数时会执行的操作,规范明确提到了这一点:

If Type(proto) is not Object, set the [[Prototype]] internal property of obj to the standard built-in Object prototype object as described in 15.2.4.

由于我们这里是通过 new 构造函数的方式创建新对象(而不是像之前那样通过对象字面量的形式),所以在 new F 的时候,内部会检测 F 的原型是不是对象,如果不是对象,那么会把实例的 __proto__ 链接到内建的 Object.prototype。因此,这里新创建的实例的 __proto__ 还真不是 null。

但根据 Object.create 的实现规范,这里必须让实例的 __proto__ 指向 null,所以才需要执行 obj.__proto__ = proto 去手动设置对象原型。

当然,如果我们像第一个实现那样,直接去设置对象的 __proto__,而不是采用构造函数的方式,就不存在这个问题了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-05-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 语法简介
  • ES 规范
  • 代码实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档