了解new底层机制,快看手撕new

431 阅读5分钟

推荐好文:# 你还不知道 常用 ES6 语法糖?!

# 剖析JS底层数据类型和堆栈内存

前言

JavaScirpt中new关键字,是JavaScript的通配符,作用于构造函数,用于创建一个实例化对象。在面向对象编程,继承机制等都能找到new 关键字的身影。下文,对new 底层实现机制进行剖析,再手写一个new功能相同的函数,加深对new 的理解。

明白new 底层

1. 创建一个新的对象

function Person(name) {
    this.name = name;
}

const awei = new Person('awei辣');
const bwei = new Person('awei辣');
console.log(awei);
console.log(bwei == awei);

image.png

  • 当我们使用 new 关键字调用 Person 构造函数时,JavaScript 创建了一个新的对象。
  • 提问:明明两个传入相同属性值的wei对象,bwei == awei 为什么输出false?
    • 因为==比较对象地址,因此,我们了解到bewi对象并不是调用awei在栈内存里面的引用,而是在堆内存创建拥有name='awei辣'属性的新对象。

2.绑定this

new关键字不仅创建新对象,还将该对象的内部 [[Prototype]] 链接到了 Person.prototype。这使得新对象能够继承来自 Person.prototype 的所有属性和方法。

function Person(name) {
    this.name = name;
}
Person.prototype.sayName = function () {
    console.log(this.name);
}
const awei = new Person('张三');
const bwei = new Person('李四');

console.log(awei.name); // 张三 调用属性
bwei.sayName()  // 李四 调用新增方法
``
构造函数this绑定到新创建的对象,并且可以在构造函数内部对新对象进行属性和方法添加,例如`Person.prototype.sayName `

- 3.设置原型链
```js
function Person(name) {
    this.name = name;
}
const awei = new Person('张三');
console.log(awei.name); // 张三 调用属性

3.返回处理值

前三项完成后,new将创建的对象返回wei,使得指向该栈内存的新对象引用。 image.png

了解完new 底层进行的四项操作后,下面写个函数实现一样的操作。

多种方式——手撕 new关键字

先总结使用 new 操作符 底层的机制 :

  1. 创建新对象:JavaScript 引擎首先创建了一个全新的空对象 {}
  2. 设置原型链:接着,该新对象的内部属性 [[Prototype]](可通过 __proto__ 访问)被设置为 Person.prototype,建立了新对象与构造函数之间的原型链连接。
  3. 绑定 this:然后,构造函数内的 this 被绑定到了新创建的对象上,使得我们可以在构造函数内部对新对象进行属性和方法的添加。
  4. 返回新对象:最后,如果构造函数没有显式返回另一个对象,则默认返回新创建的对象。

1,字面量与 Object.create 实现

const personProto = { greet() { return "hello"; } };

const person = Object.create(personProto);
person.name = "Jane";

console.log(person, person.__proto__);

在这里,Object.create 通过原型 personProto 创建了一个新对象,并保持原型链的继承关系。

2,实现源码如下(简单版objectFactory函数)

function Person(name,age) {
    this.name = name;
    this.age = age;
}
Person.prototype.sayName = function () {
    console.log(this.name);
}
// 实现一个objectFactory 函数,创建一个对象,注意:不能使用new 关键字
function objectFactory(Fun,...args) {
    let obj = {};
    obj.__proto__ = Fun.prototype;
    Fun.apply(obj,args);
    return obj;
}
let awei = objectFactory(Person,'张三',18);
console.log(awei.name);
awei.sayName();

3,深入 es6实现(推荐)

function myNewES6(Constructor, ...args) {
    const obj = Object.create(Constructor.prototype); // 创建对象,并设置原型
    const result = Constructor.apply(obj, args); // 执行构造函数
    return result instanceof Object ? result : obj; // 返回构造结果
}

这里使用ECMAScript里面Object.create 来设置__proto__,实现比较好的兼容和性能。同时,也使用剩余参数语法(...args)传入数组属性作为参数。

4,(es5实现)原生方式

面试时候,只要让面试官知道会写es5 就行,首要推荐写es6 形式,当然还有下面Object.assign 实现方式。

function myNew() {
    var obj = {};  // 创建一个空对象
    var Constructor = [].shift.call(arguments); // 取出构造函数
    obj.__proto__ = Constructor.prototype; // 继承原型链

    // 执行构造函数,将 `obj` 作为 `this`
    var result = Constructor.apply(obj, arguments);
    return typeof result === "object" && result !== null ? result : obj;
}
  • new 关键字会创建一个新对象,并自动将 __proto__ 指向构造函数的 prototype
  • 通过 apply 绑定 this,让构造函数执行并返回新对象。
  • 如果构造函数返回的是对象类型(非 null),那么返回该对象,否则返回 obj 本身。

5,Object.assign 合并对象,实现new

const target = { a: 1, b: 2 };
const source = { b: 4, c: { d: 5 } };
const res = Object.assign({}, target, source); // 避免修改 target

console.log(res === target); // false,res 是新对象
console.log(res, res.__proto__); // { a: 1, b: 4, c: { d: 5 } },覆盖 `b`
console.log(target);  // { a: 1, b: 2 }
console.log(source);  // { b: 4, c: { d: 5 } }
  • Object.assign 进行对象合并,避免直接修改 target
  • 浅拷贝,嵌套对象 c 仍然是引用。

看下面图片,说明我们的手写new关键字成了。sayName 方法都可以调用。 image.png

复现 Object.create

  1. 创建一个新对象,并设置其 __proto__proto
  2. 支持属性定义,可使用 Object.defineProperties 添加属性。
  3. 处理特殊情况
    • proto 不是对象或 null 时抛出错误。
    • protonull 时,确保 __proto__ 为空。
Object.create = function (proto, propertyObject = undefined) {
    if (typeof proto !== "object" && typeof proto !== "function") {
        throw new TypeError("Object prototype may only be an Object or null: " + proto);
    }
    if (propertyObject === null) {
        throw new TypeError("Cannot convert undefined or null to object");
    }

    function F() {}
    F.prototype = proto;
    const obj = new F();

    if (propertyObject !== undefined) {
        Object.defineProperties(obj, propertyObject);
    }
    if (proto === null) {
        obj.__proto__ = null;
    }
    return obj;
};

关键点:

  • 构造函数 F 继承 proto,避免直接操作 __proto__
  • 动态添加属性,用 Object.defineProperties 控制属性特性(可写、可枚举等)。
  • 兼容 proto === null 的情况

补充: Object.defineProperties 设置属性特性

let obj = {};
Object.defineProperties(obj, {
    a: {
        value: 1,
        writable: true,
        enumerable: true,
        configurable: true
    },
    b: {
        value: 2,
        writable: true,
        enumerable: true,
        configurable: true
    }
});
console.log(obj); // { a: 1, b: 2 }
  • writable:是否可修改。
  • enumerable:是否可遍历。
  • configurable:是否可删除或重新配置。
2.png

总结:

  1. new 的核心是创建对象、设置原型、执行构造函数并返回。
  2. Object.create 可精确控制原型继承,并支持 null 原型对象。
  3. Object.assign 进行浅拷贝,注意引用类型问题。