Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript深入之new的模拟实现 #13

Open
mqyqingfeng opened this issue May 4, 2017 · 120 comments
Open

JavaScript深入之new的模拟实现 #13

mqyqingfeng opened this issue May 4, 2017 · 120 comments
Labels

Comments

@mqyqingfeng
Copy link
Owner

@mqyqingfeng mqyqingfeng commented May 4, 2017

new

一句话介绍 new:

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一

也许有点难懂,我们在模拟 new 之前,先看看 new 实现了哪些功能。

举个例子:

// Otaku 御宅族,简称宅
function Otaku (name, age) {
    this.name = name;
    this.age = age;

    this.habit = 'Games';
}

// 因为缺乏锻炼的缘故,身体强度让人担忧
Otaku.prototype.strength = 60;

Otaku.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
}

var person = new Otaku('Kevin', '18');

console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60

person.sayYourName(); // I am Kevin

从这个例子中,我们可以看到,实例 person 可以:

  1. 访问到 Otaku 构造函数里的属性
  2. 访问到 Otaku.prototype 中的属性

接下来,我们可以尝试着模拟一下了。

因为 new 是关键字,所以无法像 bind 函数一样直接覆盖,所以我们写一个函数,命名为 objectFactory,来模拟 new 的效果。用的时候是这样的:

function Otaku () {
    ……
}

// 使用 new
var person = new Otaku(……);
// 使用 objectFactory
var person = objectFactory(Otaku, ……)

初步实现

分析:

因为 new 的结果是一个新对象,所以在模拟实现的时候,我们也要建立一个新对象,假设这个对象叫 obj,因为 obj 会具有 Otaku 构造函数里的属性,想想经典继承的例子,我们可以使用 Otaku.apply(obj, arguments)来给 obj 添加新的属性。

在 JavaScript 深入系列第一篇中,我们便讲了原型与原型链,我们知道实例的 __proto__ 属性会指向构造函数的 prototype,也正是因为建立起这样的关系,实例可以访问原型上的属性。

现在,我们可以尝试着写第一版了:

// 第一版代码
function objectFactory() {

    var obj = new Object(),

    Constructor = [].shift.call(arguments);

    obj.__proto__ = Constructor.prototype;

    Constructor.apply(obj, arguments);

    return obj;

};

在这一版中,我们:

  1. 用new Object() 的方式新建了一个对象 obj
  2. 取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 arguments 会被去除第一个参数
  3. 将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性
  4. 使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性
  5. 返回 obj

更多关于:

原型与原型链,可以看《JavaScript深入之从原型到原型链》

apply,可以看《JavaScript深入之call和apply的模拟实现》

经典继承,可以看《JavaScript深入之继承》

复制以下的代码,到浏览器中,我们可以做一下测试:

function Otaku (name, age) {
    this.name = name;
    this.age = age;

    this.habit = 'Games';
}

Otaku.prototype.strength = 60;

Otaku.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
}

function objectFactory() {
    var obj = new Object(),
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    Constructor.apply(obj, arguments);
    return obj;
};

var person = objectFactory(Otaku, 'Kevin', '18')

console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60

person.sayYourName(); // I am Kevin

[]~( ̄▽ ̄)~**

返回值效果实现

接下来我们再来看一种情况,假如构造函数有返回值,举个例子:

function Otaku (name, age) {
    this.strength = 60;
    this.age = age;

    return {
        name: name,
        habit: 'Games'
    }
}

var person = new Otaku('Kevin', '18');

console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // undefined
console.log(person.age) // undefined

在这个例子中,构造函数返回了一个对象,在实例 person 中只能访问返回的对象中的属性。

而且还要注意一点,在这里我们是返回了一个对象,假如我们只是返回一个基本类型的值呢?

再举个例子:

function Otaku (name, age) {
    this.strength = 60;
    this.age = age;

    return 'handsome boy';
}

var person = new Otaku('Kevin', '18');

console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18

结果完全颠倒过来,这次尽管有返回值,但是相当于没有返回值进行处理。

所以我们还需要判断返回的值是不是一个对象,如果是一个对象,我们就返回这个对象,如果没有,我们该返回什么就返回什么。

再来看第二版的代码,也是最后一版的代码:

// 第二版的代码
function objectFactory() {

    var obj = new Object(),

    Constructor = [].shift.call(arguments);

    obj.__proto__ = Constructor.prototype;

    var ret = Constructor.apply(obj, arguments);

    return typeof ret === 'object' ? ret : obj;

};

下一篇文章

JavaScript深入之类数组对象与arguments

相关链接

《JavaScript深入之从原型到原型链》

《JavaScript深入之call和apply的模拟实现》

《JavaScript深入之继承》

深入系列

JavaScript深入系列目录地址:https://github.com/mqyqingfeng/Blog

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

@strongcode9527
Copy link

@strongcode9527 strongcode9527 commented May 5, 2017

var obj = Object.create(null)
这样创建会比较好

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 5, 2017

并没有什么区别吧,毕竟最后都是要更改原型的


以上是最初的看法,两者有很大的不同,欢迎下拉看两者之间的区别。

@jawil
Copy link

@jawil jawil commented May 9, 2017

function objectFactory() {

    var obj = new Object(),//从Object.prototype上克隆一个对象

    Constructor = [].shift.call(arguments);//取得外部传入的构造器

    var F=function(){};
    F.prototype= Constructor.prototype;
    obj=new F();//指向正确的原型

    var ret = Constructor.apply(obj, arguments);//借用外部传入的构造器给obj设置属性

    return typeof ret === 'object' ? ret : obj;//确保构造器总是返回一个对象

};

学习学习,之前一直是理论了解new,也来实践模拟一把😄

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 9, 2017

哈哈,添加的注释很赞 o( ̄▽ ̄)d

@jawil
Copy link

@jawil jawil commented May 9, 2017

想看看博主写的react系列,准备学习react了,最近react和vue一直在撕逼😄

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 9, 2017

我也好想写React呐,不过React估计是第三个或者第四个系列,现在第一个系列还没有发布完呢,第二个系列估计要写20篇左右,写到React,估计都下下个月了……看来是赶不上这波撕逼的浪潮了~😂😂😂


实际上,过了一年都没有开始写 React 系列 T^T

@wcflmy
Copy link

@wcflmy wcflmy commented May 27, 2017

objectFactory函数里最后一行建议改成“return typeof ret === 'object' ? ret||obj : obj;”,否则如果在Otaku函数里面return null,会有问题的。

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented May 27, 2017

@wcflmy 哈哈,被你发现了,我在模拟的时候,也发现这一点了,后来觉得反正主要目的是为了让大家了解 new 的原理,没有必要再写一句专门判断 null ,就没有写,不过使用你这种写法就不用多写那一句了,真的很赞!给你 32 个赞,哈哈~~~ o( ̄▽ ̄)d

@Allen3039
Copy link

@Allen3039 Allen3039 commented Jun 1, 2017

return Object.prototype.toString.call(ret).match(/^\[object (\w+)\]$/)[1]==='Object' ? ret : obj; 也可以吧

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jun 2, 2017

@Allen3039 哈哈,类型判断加正则,大家这是各显神通呐~ o( ̄▽ ̄)d

@xdwxls
Copy link

@xdwxls xdwxls commented Jun 3, 2017

@jawil var F=function(){};
F.prototype= Constructor.prototype;
obj=new F();//指向正确的原型 这写法是什么意思 没太看明白

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jun 4, 2017

@xdwxls 其实就是将 obj 的原型指向 Constructor.prototype,只不过@jawil 的写法中在 new 的模拟实现中又用到了 new 😂

@a1029563229
Copy link

@a1029563229 a1029563229 commented Jun 7, 2017

@strongcode9527 试了你这种写法 后面直接用prototype添加的方法,无法被继承....

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jun 8, 2017

@a1029563229 能提供下这段代码吗?

@Izayoih
Copy link

@Izayoih Izayoih commented Jul 15, 2017

@mqyqingfeng @a1029563229 我也发现了这个问题 试了一下改成Object.create(Object.prototype)结果可以了...不是很明白什么道理

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Jul 19, 2017

@strongcode9527 @a1029563229 @lzayoih 我也发现了这个问题,测试 demo 为:

function Otaku (age) {}

Otaku.prototype.sayHello = function() {
	console.log('hello')
}

var person = objectFactory(Otaku, 'Kevin', '18');
console.log(person)
person.sayHello() //???

如果使用 Object.create(null),person.sayHello 就会报错,使用 new Object(),会正常打印 hello。

查看由两种方式生成的 person 对象,第一个是由 Object.create 生成的,第二个是 new Object 生成的

default

两者的区别就在于 __proto__ 一个是实的,一个是虚的,由此我们可以猜测第一种方式是把 __proto__ 当成了一个属性值,而非去修改了原型!

原因其实在 《JavaScript深入之从原型到原型链》中有提过:

__proto__ ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.__proto__ 时,可以理解成返回了 Object.getPrototypeOf(obj)。

default

@yh284914425
Copy link

@yh284914425 yh284914425 commented Aug 3, 2017

__proto__在ie里面不是没用么,代码在ie里面应该实现不了吧

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Aug 4, 2017

@yh284914425 是的,本篇是希望通过模拟 new 的实现,让大家了解 new 的原理~

@Williris
Copy link

@Williris Williris commented Aug 6, 2017

返回值应该判断三种类型: Function,Object 和Array

@qianL93
Copy link

@qianL93 qianL93 commented Aug 31, 2017

这里构造函数返回null的时候,new返回的是obj,不是ret

@mqyqingfeng
Copy link
Owner Author

@mqyqingfeng mqyqingfeng commented Sep 1, 2017

@qianL93 确实如此哈,@wcflmy@Allen3039 已经在这个 issue 下提出了解决方案~

@huangmxsysu
Copy link

@huangmxsysu huangmxsysu commented Sep 4, 2017

话说__proto__一个实一个虚的问题不是因为Object.create(null)是原型链顶端导致var obj = Object.create(null)之后 obj根本访问不到__proto__这个原型属性导致后面的obj.proto = Constructor.prototype使它的__proto__是实的,成为一个属性,从而没有修改原型。
我觉得是不是可以不需要

var obj = new Object()
obj.__proto__ = Constructor.prototype

直接用

var obj = Object.create(Constructor.prototype)

是一样也可以的吧?

@Vstar18
Copy link

@Vstar18 Vstar18 commented Jun 5, 2020

@mqyqingfeng
关于Object.create(null)的猜想我同意,使用Object.keys验证一下。person1是Object.create({})生成的,person2是Object.create(null)生成的。结果如下:
image

猜想:
Object.create(null)是创建一个原型链指向null的对象,当原型链为null时,不可再次更改原型链。因此再次obj.proto 赋值时,实际是添加了一个属性,而不是链接原型了。

原因可能是原型链的顶端就是null(Object.prototype.proto === null//true),所以原型链是null的不可更改


@strongcode9527 @a1029563229 @lzayoih 我也发现了这个问题,测试 demo 为:

function Otaku (age) {}

Otaku.prototype.sayHello = function() {
	console.log('hello')
}

var person = objectFactory(Otaku, 'Kevin', '18');
console.log(person)
person.sayHello() //???

如果使用 Object.create(null),person.sayHello 就会报错,使用 new Object(),会正常打印 hello。

查看由两种方式生成的 person 对象,第一个是由 Object.create 生成的,第二个是 new Object 生成的

default

两者的区别就在于 proto 一个是实的,一个是虚的,由此我们可以猜测第一种方式是把 proto 当成了一个属性值,而非去修改了原型!

原因其实在 《JavaScript深入之从原型到原型链》中有提过:

proto ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.proto 时,可以理解成返回了 Object.getPrototypeOf(obj)。

default
@bosens-China
Copy link

@bosens-China bosens-China commented Jun 10, 2020

    return typeof ret === 'object' ? ret : obj;

这个判断有问题,记得判断null的情况

    return typeof ret === 'object' && ret ? ret : obj;
@xieyezi
Copy link

@xieyezi xieyezi commented Jun 29, 2020

我也来试着写一下:

function myNew(Cons, ...args) {
  let obj = {};
  obj.__proto__ = Cons.prototype;
  let res = Cons.call(obj, args);
  return typeof res === "object" ? res  obj;
}
@uioz
Copy link

@uioz uioz commented Jul 19, 2020

肥宅有什么错???!!!

function objectFactory(constructor) {

	var obj = Object.create(constructor.prototype);

	var instance = constructor.apply(obj,Array.prototype.slice.call(arguments,1))

	return typeof instance === 'object' ? instance || obj : obj;
}

function test(name,age){
	this.name = name;
	this.age = age;
}

console.log(objectFactory(test,'hello world',23))
@zhixinpeng
Copy link

@zhixinpeng zhixinpeng commented Jul 21, 2020

我也来试着写一下:

function myNew(Cons, ...args) {
  let obj = {};
  obj.__proto__ = Cons.prototype;
  let res = Cons.call(obj, args);
  return typeof res === "object" ? res  obj;
}

call 的参数怎么成args了

@javaSwing
Copy link

@javaSwing javaSwing commented Aug 18, 2020

我也来写一个

function objectFactory (FromFn, ...args) {
    // 绑定原型
    var obj = Object.create(FromFn.prototype)
    // 把传入的参数绑定到对象上
    var result = FromFn.apply(obj, args)
    // 如果返回的为null,则返回原对象
    return typeof result === 'object' ? result || obj : obj
}
@puck1006
Copy link

@puck1006 puck1006 commented Sep 26, 2020

我也好想写React呐,不过React估计是第三个或者第四个系列,现在第一个系列还没有发布完呢,第二个系列估计要写20篇左右,写到React,估计都下下个月了……看来是赶不上这波撕逼的浪潮了~😂😂😂

实际上,过了一年都没有开始写 React 系列 T^T

三年了

@javaSwing
Copy link

@javaSwing javaSwing commented Sep 27, 2020

我也好想写React呐,不过React估计是第三个或者第四个系列,现在第一个系列还没有发布完呢,第二个系列估计要写20篇左右,写到React,估计都下下个月了……看来是赶不上这波撕逼的浪潮了~😂😂😂
实际上,过了一年都没有开始写 React 系列 T^T

三年了

三年,你知道我这三年怎么过的吗? react我吃定了,耶稣都留不住它

@FullOfVigour
Copy link

@FullOfVigour FullOfVigour commented Sep 28, 2020

console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18

image
大神想问一下为什么返回值为null的时候会报错?而普通new的时候对返回null的处理是同返回普通值一样处理的

@bigbigDreamer
Copy link

@bigbigDreamer bigbigDreamer commented Oct 8, 2020

console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18

image
大神想问一下为什么返回值为null的时候会报错?而普通new的时候对返回null的处理是同返回普通值一样处理的

因为自己写的new函数并没有做处理,大佬说了,仅仅是为了带大家体验一下new是怎么实现的,并不会去过多考虑过多的细节。如果你非要消除这个错误。

function myNew() {
    const Constructor = [].shift.call(arguments);
    const obj = Object.create(Constructor.prototype);
    const ret = Constructor.apply(obj, arguments);
     
    return (typeof ret === 'object' && ret) ? ret : obj;
}

最后返回值判断只是为了迎合js中的设计,如果构造函数返回的是一个对象,我们需要返回的是构造函数中返回的对象,如果返回值是null 、string、undefined、number、boolean、symbol、bigInt,那么我们忽略返回值,继续返回这个构造函数的主体,也即实例化对象

@bigbigDreamer
Copy link

@bigbigDreamer bigbigDreamer commented Oct 8, 2020

console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18

image
大神想问一下为什么返回值为null的时候会报错?而普通new的时候对返回null的处理是同返回普通值一样处理的

因为自己写的new函数并没有做处理,大佬说了,仅仅是为了带大家体验一下new是怎么实现的,并不会去过多考虑过多的细节。如果你非要消除这个错误。

function myNew() {
    const Constructor = [].shift.call(arguments);
    const obj = Object.create(Constructor.prototype);
    const ret = Constructor.apply(obj, arguments);
     
    return (typeof ret === 'object' && ret) ? ret : obj;
}

最后返回值判断只是为了迎合js中的设计,如果构造函数返回的是一个对象,我们需要返回的是构造函数中返回的对象,如果返回值是null 、string、undefined、number、boolean、symbol、bigInt,那么我们忽略返回值,继续返回这个构造函数的主体,也即实例化对象

其他传统语言的设计中,作为区分构造函数与普通函数的标准就是:构造函数是不应该有返回值的。

@tb1565647408
Copy link

@tb1565647408 tb1565647408 commented Oct 26, 2020

var obj = Object.create(null)
这样创建会比较好

感觉这样好一点

var obj = Object.create(Constructor.prototype)

感觉这样比较好
var obj = Object.create({},Constructor.prototype)

@qwer1024
Copy link

@qwer1024 qwer1024 commented Nov 11, 2020

Constructor = [].shift.call(arguments); 这里的 Constructor 不会创建一个全局变量造成污染么

@Jackywa124
Copy link

@Jackywa124 Jackywa124 commented Nov 13, 2020

三年之后再三年,会不断的有人来这里学习

@anjina
Copy link

@anjina anjina commented Nov 16, 2020

根据mdn new 运算符定义

/**
 * 创建一个空的简单JavaScript对象(即{});
 * 链接该对象(即设置该对象的构造函数)到另一个对象 ;
 * 将步骤1新创建的对象作为this的上下文 ;
 * 如果该函数没有返回对象,则返回this。
 */

const _new = (_Contructor, ...args) => {
  // 1.创建一个空的简单JavaScript对象(即{});
  const obj = {}
  // 2.链接该对象(即设置该对象的构造函数)到另一个对象 ;
  Object.setPrototypeOf(obj, _Contructor.prototype)
  // 3.将步骤1新创建的对象作为this的上下文 ;
  const ret = _Contructor.apply(obj, args)
  //4.如果该函数没有返回对象,则返回this。
  return ret instanceof Object ? ret : this
}

这里的返回this 应该是指构造函数里的this, 此时构造函数里的this指向obj ,所以应该返回obj

@anjina
Copy link

@anjina anjina commented Nov 16, 2020

根据mdn new 运算符定义

/**
 * 创建一个空的简单JavaScript对象(即{});
 * 链接该对象(即设置该对象的构造函数)到另一个对象 ;
 * 将步骤1新创建的对象作为this的上下文 ;
 * 如果该函数没有返回对象,则返回this。
 */

const _new = (_Contructor, ...args) => {
  // 1.创建一个空的简单JavaScript对象(即{});
  const obj = {}
  // 2.链接该对象(即设置该对象的构造函数)到另一个对象 ;
  Object.setPrototypeOf(obj, _Contructor.prototype)
  // 3.将步骤1新创建的对象作为this的上下文 ;
  const ret = _Contructor.apply(obj, args)
  //4.如果该函数没有返回对象,则返回this。
  return ret instanceof Object ? ret : this
}

ret instanceof Object 这里不行,如果

const _new = (_Contructor, ...args) => {
  // 1.创建一个空的简单JavaScript对象(即{});
  const obj = {}
  // 2.链接该对象(即设置该对象的构造函数)到另一个对象 ;
  Object.setPrototypeOf(obj, _Contructor.prototype)
  // 3.将步骤1新创建的对象作为this的上下文 ;
  const ret = _Contructor.apply(obj, args)
  //4.如果该函数没有返回对象,则返回this。
  return ret instanceof Object ? ret : this
}

代码对于 __proto__的设置是不正确的,你用下面的测试代码试下就知道了
// 下面是测试代码
function Person (name, age) {
this.name = name;
this.age = age;

return {
    name: name,
    habit: 'read book'
}

}

Person.prototype.strength = 60;

Person.prototype.sayName = function () {
console.log('I am ' + this.name);
}

var person = _new(Person, 'angela', '20')

console.log(person.name)
console.log(person.habit)
console.log(person.strength)

person.sayName();

这是因为你的构造函数本身就返回了一个对象,自然原型链没有继承

@anjina
Copy link

@anjina anjina commented Nov 16, 2020

console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18

image
大神想问一下为什么返回值为null的时候会报错?而普通new的时候对返回null的处理是同返回普通值一样处理的

你这个打印注释是在误解。。 拟写的工厂函数 当返回null 的时候也是对象也会当成new 的结果返回。null.a null.age 都会报错

@anjina
Copy link

@anjina anjina commented Nov 16, 2020

function objectFactory() {
  var _Constructor = Array.prototype.shift.call(arguments);

  if (typeof _Constructor !== "function") {
    throw new Error("constructor should be a function");
  }

  var obj = {};
  /**
   * es5中
   * var obj = Object.create(_Constructor.prototype);
   * Object.create实现中其实也借助了new
   * Object.create(p);
   * var func = function() {}
   * func.prototype = p;
   * return new func();
   */
  obj.__proto__ = _Constructor.prototype;

  var res = _Constructor.apply(obj, arguments);

  /**
   * 使用instanceof判断
   * 当返回值为函数和对象 都是直接返回,  返回值为null时仍然返回上下文
   */
  return res instanceof Object ? res : obj;
}
@liuestc
Copy link

@liuestc liuestc commented Dec 3, 2020

function newFactory(){

const agrs = [...arguments]
const fn = agrs.shift()
const prototypeObj = Object.create(fn.prototype)
const result = fn.apply(prototypeObj,agrs)

return typeof result ==='object'?result:prototypeObj;

}

@afishhhhh
Copy link

@afishhhhh afishhhhh commented Dec 8, 2020

@mqyqingfeng
关于Object.create(null)的猜想我同意,使用Object.keys验证一下。person1是Object.create({})生成的,person2是Object.create(null)生成的。结果如下:
image

猜想:
Object.create(null)是创建一个原型链指向null的对象,当原型链为null时,不可再次更改原型链。因此再次obj.proto 赋值时,实际是添加了一个属性,而不是链接原型了。

原因可能是原型链的顶端就是null(Object.prototype.proto === null//true),所以原型链是null的不可更改

@strongcode9527 @a1029563229 @lzayoih 我也发现了这个问题,测试 demo 为:

function Otaku (age) {}

Otaku.prototype.sayHello = function() {
	console.log('hello')
}

var person = objectFactory(Otaku, 'Kevin', '18');
console.log(person)
person.sayHello() //???

如果使用 Object.create(null),person.sayHello 就会报错,使用 new Object(),会正常打印 hello。
查看由两种方式生成的 person 对象,第一个是由 Object.create 生成的,第二个是 new Object 生成的
default
两者的区别就在于 proto 一个是实的,一个是虚的,由此我们可以猜测第一种方式是把 proto 当成了一个属性值,而非去修改了原型!
原因其实在 《JavaScript深入之从原型到原型链》中有提过:

proto ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.proto 时,可以理解成返回了 Object.getPrototypeOf(obj)。

default

原型链为 null 的时候是可以修改原型链的,Object.setPrototypeOf(obj) 就行,但是直接赋值 __proto__ 是不行的,__proto__ 本质上是一个 getter/setter,所以当它存在于原型链上的时候,访问 __proto__ 必然会调用 getter,赋值必然会调用 setter。如果原型链是 null,那以上就不成立了,getter 和 setter 都调用不到,obj.__proto__ = xxx 相当于在 obj 上添加了一个普通属性。

@yangHui233
Copy link

@yangHui233 yangHui233 commented Dec 17, 2020

return typeof ret === 'object' ? ret : obj;
这里只能判断返回值为对象,当返回值为函数的时候,也是需要返回ret的,还是我理解有误。。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet