Skip to content

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

@mqyqingfeng

Description

@mqyqingfeng
Owner

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,对作者也是一种鼓励。

Activity

strongcode9527

strongcode9527 commented on May 5, 2017

@strongcode9527

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

mqyqingfeng

mqyqingfeng commented on May 5, 2017

@mqyqingfeng
OwnerAuthor

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


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

jawil

jawil commented on May 9, 2017

@jawil
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

mqyqingfeng commented on May 9, 2017

@mqyqingfeng
OwnerAuthor

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

jawil

jawil commented on May 9, 2017

@jawil

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

mqyqingfeng

mqyqingfeng commented on May 9, 2017

@mqyqingfeng
OwnerAuthor

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


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

wcflmy

wcflmy commented on May 27, 2017

@wcflmy

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

182 remaining items

jiahui336

jiahui336 commented on Jun 24, 2022

@jiahui336

真不错

cliYao

cliYao commented on Jun 24, 2022

@cliYao
wangqianqian0228

wangqianqian0228 commented on Aug 4, 2022

@wangqianqian0228

const obj = Object.create(Constructor.prototype);
创建一个以Constructor.prototype为原型的对象应该也可以吧~

lwpersonal

lwpersonal commented on Jun 21, 2023

@lwpersonal

箭头函数的情况没有处理

cliYao

cliYao commented on Jun 21, 2023

@cliYao
Chenmin926

Chenmin926 commented on Jul 15, 2023

@Chenmin926

function myNew(fn, ...args) {
let obj = Object.create(fn.prototype);
let res = fn.apply(obj, args);
return res instanceof Object ? res : obj
}

ZhongJingBo

ZhongJingBo commented on Jul 24, 2023

@ZhongJingBo

对于 这段代码,有人知道为什么无法new class呢?
class Age {
name='123'
}
function myNew(context) {
const obj = new Object();
constructor = [].shift.call(arguments);
obj.porto = context.prototype;
const ret = constructor.apply(obj, arguments);
return typeof ret === "object" ? ret : obj;
}
const myFun = myNew(Age);
会有如下报错
Uploading image.png…

cliYao

cliYao commented on Sep 18, 2023

@cliYao
bosens-China

bosens-China commented on Oct 6, 2023

@bosens-China
function _new (fn, ...args) {
  const obj = Object.create(fn.prototype);
  const result = fn.apply(obj, args);

  return (result && typeof result === 'object') ? result: obj;
}
cliYao

cliYao commented on Oct 6, 2023

@cliYao
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @enggirl@usherwong@roninliu@xuchaobei@luckymore

        Issue actions

          JavaScript深入之new的模拟实现 · Issue #13 · mqyqingfeng/Blog