了解new底层机制,手撕new

108 阅读3分钟

前言

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
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); // 张三 调用属性

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

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

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

手撕 new

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

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

function objectFactory(Constructor, ...args) {
    const obj = Object.create(Constructor.prototype); // 使用 Object.create 设置 __proto__
    Constructor.apply(obj, args); // 调用构造函数并传递参数
    return obj;
}

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

  • 手写new关键字(高配版,objectFactory函数),附带注释
function objectFactory() {

    const obj = new Object(); // 空对象
    // [].shift 数组调用shift方法,调用.call 方法改变this指向,指向类数组arguments
    const Constructor = [].shift.call(arguments); 
    // apply 参数一个传入this指向,一个是数组
    Constructor.apply(obj,arguments);
    // 手动实现__proto__ 指向Person.prototype,让实例化对象可以访问构造函数里面的方法
    obj.__proto__ = Constructor.prototype;
    return obj;
}

提问:

  • 1.为什么高配版更值得面试官考察?
  • 2.为什么使用shift(这个函数每次将数组第一个元素删除)?
  • 3.一样实现this指向变化,使用call(arguments)为啥不使用bind或是apply

(欢迎评论区留下你的答案)

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

总结

成长之路还是要persistently work ,赶快去试着自己手写一下吧~~

欢迎评论区留言讨论