面试高频手写new

57 阅读5分钟

先来测测你对原型的理解

Object1 = {
    name:'deng',
    age:18
}
Object2 ={
    name:'ru',
    age:18
}

const Person = function(){

}
Person.prototype = Object1;
const p1 = new Person();
console.log(p1); 

console.log(Person.prototype); 

Person.prototype = Object2;
console.log(Person.prototype);
console.log(p1.name); 
console.log(p1.__proto__);

const p=new Person();
console.log(p.name);

正确答案如下

image.png 作为面试高频之一今天我们来好好看看new这个关键字到底new了个啥

function Person(name,age){
    this.name=name;
    this.age=age;
}
Person.prototype.sayName=function(){
    console.log(this.name);
}

首先我们写一个构造函数给这个构造函数的原型上加一个方法

我们先来想想,一个实例化对象要如何new出来,其实这个问题非常值得思考。也就是说,new发生了什么

function objectFaction() {
  const obj = new Object(); // 创建一个空的对象实例
  const Constructor = [].shift.call(arguments); // 获取传递给 function 的第一个参数作为构造函数
  obj.__proto__ = Constructor.prototype; // 设置新对象的原型为构造函数的 prototype
  Constructor.apply(obj, arguments); // 使用 apply 方法调用构造函数,使 this 指向新创建的对象,并传递剩余的参数
  return obj; // 返回新创建并初始化的对象
}

详细解释

  1. 创建一个新的空对象实例:

    const obj = new Object();
    

    这里我们使用了 new Object() 来创建一个新的空对象实例。这一步类似于 new 操作符的第一步,即创建一个新的、空的、继承自 Constructor.prototype 的对象 。

  2. 获取构造函数:

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

    在这里,代码从 arguments 对象中移除了第一个元素,并将其赋值给变量 Constructorarguments 是一个类数组对象,包含了传递给函数的所有参数。通过 [].shift.call(arguments),我们可以安全地处理 arguments,即使它不是一个真正的数组。这样做是为了获取传递给 objectFaction 的第一个参数,也就是预期的构造函数 。

  3. 设置新对象的原型:

    obj.__proto__ = Constructor.prototype;
    

    接下来,这段代码设置了新对象 obj 的内部属性 [[Prototype]](可通过 __proto__ 属性访问),使其指向构造函数的原型 (Constructor.prototype)。这一步确保了新对象能够访问到构造函数原型上的所有属性和方法,从而实现了继承机制 。

  4. 应用构造函数并传递参数:

    Constructor.apply(obj, arguments);
    

    然后,代码使用 apply 方法来调用构造函数,同时将 this 绑定到新创建的对象 obj 上,并传递剩余的参数。这意味着构造函数中的 this 将引用新创建的对象,并且构造函数可以接收并处理传递给 objectFaction 的其他参数。这里需要注意的是,由于我们在第一步已经从 arguments 中移除了第一个参数(构造函数本身),所以在调用 apply 时,传递给构造函数的实际参数是原始参数列表减去第一个元素后的结果 。

  5. 返回新创建的对象:

    return obj;
    

    最后,函数返回了新创建并初始化的对象 obj。这与 new 操作符的最后一部分行为相匹配:如果没有显式返回一个对象,则默认返回新创建的对象 。

ok其实const Constructor = [].shift.call(arguments);这一步完全是为了炫技,完全可以写成Constructor=arguments[0]

我第一次看见这行也是一脸懵逼,我们来细细解读 const Constructor = [].shift.call(arguments); 这一行代码的作用是从 arguments 对象中提取出第一个参数,并将其赋值给常量 Constructor。为了更深入地理解这段代码的工作原理,我们需要分解它并探讨每个部分的功能。

arguments 对象

在 JavaScript 中,arguments 是一个类数组对象,它包含了传递给函数的所有参数。尽管它的行为类似于数组,但它并不是真正的 Array 实例,因此不能直接使用数组的方法,如 pop(), push(), 或者 slice()

[].shift()

这里的 [] 实际上是创建了一个空数组的简写形式,即 Array.prototype 的快捷方式。.shift() 是数组的一个方法,用于移除数组的第一个元素,并返回该元素。当应用于真正的数组时,它会改变原数组的内容,使其长度减一,并返回被移除的那个元素 。

然而,在这个例子中,我们并没有直接在一个真正的数组上调用 .shift(),而是通过 call 方法将 .shift() 应用于 arguments 对象。这是因为 arguments 不是真正的数组,所以不能直接调用数组的方法。但是,由于 .shift() 方法内部依赖于 this 来指代调用它的对象,我们可以利用 Function.prototype.call() 来改变 this 的绑定,从而让 .shift() 作用于 arguments

call 方法

callFunction 对象的一个方法,它可以用来调用一个函数,并指定该函数执行时的 this 值。此外,call 还允许你以逗号分隔的形式传递额外的参数给目标函数。在这个例子中,[].shift.call(arguments) 的意思是:调用数组原型上的 .shift() 方法,并将 this 绑定到 arguments 对象上,这样就可以像操作数组一样从 arguments 中移除并返回第一个元素了 。

合起来看

将上述几点结合起来,const Constructor = [].shift.call(arguments); 的含义就变得清晰了:

  • 它首先获取了传递给当前函数的第一个参数(假设这是构造函数)。
  • 然后,它把这个参数赋值给了一个名为 Constructor 的常量。
  • 同时,这也意味着 arguments 对象中的第一个参数已经被移除了,剩下的参数将是构造函数之外的其他参数,这些参数将在后续步骤中传递给构造函数作为其自身的参数 。

注意事项

需要注意的是,这种方法适用于非严格模式下的函数。在严格模式下,arguments 和显式命名的参数之间的关系更加严格,且 arguments.calleearguments.caller 被禁用。因此,在严格模式下,最好避免依赖 arguments 的这种行为,转而使用 rest 参数语法(如 ...args)来处理不定数量的参数 。 。