尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

new 高频面试题 + 满分答案

new 高频面试题 + 满分答案 一、new到底做了什么核心思路一句话new的本质是调用一个构造函数创建对象创建新对象、建立原型关系、以新对象作为this执行构造函数最后根据构造函数返回值决定最终结果。流程图new Person(张三) │ ▼ 创建一个新对象 obj │ ▼ obj.[[Prototype]] Person.prototype │ ▼ 以 obj 作为 this 执行 Person(张三) │ ▼ 检查构造函数返回值 │ ├── 返回对象/函数 ──→ 返回该对象 │ └── 返回其他值 ────→ 返回 obj满分答案new可以理解成一次完整的“构造调用”。第一创建一个新的普通对象第二把这个对象的[[Prototype]]设置为构造函数的prototype第三以这个新对象作为this执行构造函数并传入参数第四根据构造函数的返回值决定最终结果如果构造函数显式返回的是对象或函数就返回它否则返回刚创建的对象。所以new并不只是“创建对象”它同时完成了实例创建、原型关联、构造函数初始化和返回值处理。二、为什么new创建出来的对象可以访问构造函数prototype上的方法核心思路一句话因为new创建实例时会把实例的[[Prototype]]设置成构造函数的prototype。functionPerson(name){this.namename;}Person.prototype.sayHellofunction(){console.log(this.name);};constpnewPerson(张三);p.sayHello();实际上p │ │ [[Prototype]] ▼ Person.prototype │ ├── constructor └── sayHello所以p.sayHello();查找过程是p 自身 ↓ 没找到 sayHello ↓ p.[[Prototype]] ↓ Person.prototype ↓ 找到 sayHello满分答案new创建实例时会建立实例对象 → 构造函数.prototype的原型关系。因此实例自身没有某个属性时会沿着原型链向上查找最终能够访问Person.prototype上的方法。这也是 JavaScript 构造函数实现实例共享原型方法的基础。三、new Person()和Object.create(Person.prototype)有什么区别这是非常典型的高级追问。核心思路一句话Object.create()只负责创建对象并设置原型不会执行构造函数new除此之外还会执行构造函数完成实例初始化。functionPerson(name){this.namename;}constanewPerson(张三);constbObject.create(Person.prototype);此时console.log(a.name);// 张三console.log(b.name);// undefined因为new Person() │ ├── 创建对象 ├── 设置原型 └── 执行 Person() Object.create() │ ├── 创建对象 └── 设置原型满分答案两者最大的区别是是否执行构造函数。Object.create(Person.prototype)只是创建一个对象并把它的原型设置为Person.prototype不会执行Person。而new Person()除了完成原型关联还会调用Person利用它初始化实例。所以Object.create()更偏向于“按指定原型创建对象”而new是完整的“构造调用”。四、构造函数返回基本类型为什么会被忽略functionPerson(){this.name张三;return123;}constpnewPerson();console.log(p.name);// 张三核心思路构造调用中只有对象或函数类型的显式返回值才能替代new创建的实例。可以理解成constresultPerson.call(obj);if(result!null(typeofresultobject||typeofresultfunction)){returnresult;}returnobj;注意returnnull;也不会替换newPerson()因为typeofnullobject但规范对构造返回值的判断并不是简单的typeof object。满分答案构造函数返回值存在特殊规则如果显式返回的是对象或函数构造调用会返回这个值如果返回的是undefined、数字、字符串、布尔值、null等非对象值则忽略这个返回值最终仍然返回new创建的实例。这里不能简单写成typeof result object因为null虽然typeof null是object但它并不能替代构造出来的实例。五、构造函数返回一个对象会发生什么functionPerson(){this.name张三;return{name:李四};}constpnewPerson();console.log(p.name);// 李四流程创建 obj ↓ obj.[[Prototype]] Person.prototype ↓ Person.call(obj) ↓ 返回 { name: 李四 } ↓ 返回值是对象 ↓ 最终 p 指向这个对象所以console.log(Object.getPrototypeOf(p)Person.prototype);结果false满分答案如果构造函数显式返回一个对象或函数那么这个返回值会覆盖new创建的实例成为最终结果。一个容易忽略的点是原来创建的那个对象已经建立了原型关系并执行过构造函数但最终没有被返回。六、手写一个new这是new最经典的编程题。基础实现functionmyNew(Constructor,...args){// 1. 创建对象并建立原型关系constobjObject.create(Constructor.prototype);// 2. 执行构造函数让 this 指向新对象constresultConstructor.apply(obj,args);// 3. 如果构造函数返回对象或函数// 则使用显式返回值if(result!null(typeofresultobject||typeofresultfunction)){returnresult;}// 4. 否则返回新创建的对象returnobj;}使用functionPerson(name,age){this.namename;this.ageage;}Person.prototype.sayHellofunction(){console.log(Hello${this.name});};constpersonmyNew(Person,张三,20);console.log(person.name);// 张三console.log(person.age);// 20person.sayHello();要主动指出的问题上面的实现用于理解new没问题但不是规范级完整模拟。例如classPerson{constructor(name){this.namename;}}myNew(Person,张三);直接使用Constructor.apply(obj,args);会失败。因为class 构造函数 ↓ 不能像普通函数一样直接调用因此如果面试官继续追问“能不能用更接近规范的方式实现”就应该谈Reflect.construct()七、Reflect.construct()和new有什么关系这是高级开发非常值得掌握的追问。核心思路一句话Reflect.construct()提供了函数的“构造调用”能力本质上对应规范中的构造过程比Function.prototype.apply()更接近new。例如functionPerson(name){this.namename;}constpReflect.construct(Person,[张三]);效果类似constpnewPerson(张三);但它提供了new语法无法直接表达的第三个参数Reflect.construct(Target,args,NewTarget);为什么重要functionParent(){}functionChild(){}constobjReflect.construct(Parent,[],Child);这里实际执行的构造函数Parent 决定实例原型的构造目标Child所以Object.getPrototypeOf(obj)Child.prototype满分答案new是语法形式而Reflect.construct()提供的是更底层、更灵活的构造调用能力。两者都可以触发构造过程但Reflect.construct()允许显式指定newTarget因此可以把“执行哪个构造函数”和“最终实例原型参考哪个构造函数”分开。这也是理解 JavaScript 类继承、super()、内建对象继承以及部分元编程行为的重要基础。八、为什么手写new不应该简单写成Constructor.apply(obj, args)错误实现functionmyNew(Constructor,...args){constobj{};Constructor.apply(obj,args);returnobj;}问题至少有两个。问题 1原型不对constobj{};得到obj ↓ Object.prototype而真正的newPerson()应该是obj ↓ Person.prototype ↓ Object.prototype所以至少应该constobjObject.create(Constructor.prototype);问题 2构造函数返回值没有处理functionPerson(){return{name:张三};}真正newPerson()返回这个对象。而Constructor.apply(obj,args);returnobj;直接把它丢掉了。满分答案apply()只能解决“让构造函数以某个this执行”的问题不能完整模拟new。完整模拟至少还需要处理两个核心问题新对象的原型必须指向构造函数的prototype构造函数显式返回对象或函数时最终结果必须使用这个返回值。另外如果要求支持class、代理对象以及更接近规范的构造行为应该进一步使用Reflect.construct()。九、new为什么能让构造函数里的this指向新对象functionPerson(name){this.namename;}constpnewPerson(张三);关键不是“构造函数天然绑定了this。”而是构造调用会把正在构造的实例作为构造函数执行时的this。可以粗略理解Person.call(obj,张三);所以this.namename;实际上相当于obj.name张三;最终pobj十、new调用普通函数和直接调用有什么区别functionPerson(name){this.namename;}Person(张三);和newPerson(张三);完全不是同一种调用。直接调用Person() ↓ 普通函数调用 ↓ this 根据调用模式决定newnew Person() ↓ 构造调用 ↓ 创建新对象 ↓ this 新对象 ↓ 执行构造函数 ↓ 返回实例满分答案new的关键作用之一就是把普通的函数调用变成构造调用。两者最大的区别不是“有没有参数”而是调用语义不同普通调用不会自动创建实例构造调用会创建实例并将这个实例作为构造函数执行时的this同时建立原型关系和处理构造返回值。十一、箭头函数为什么不能被newconstPerson(name){this.namename;};newPerson(张三);会报错TypeError: Person is not a constructor为什么关键不是“箭头函数没有prototype。”虽然通常可以观察到Person.prototype// undefined但更本质的是箭头函数没有[[Construct]]内部方法因此不能进行构造调用。普通函数通常具有[[Call]] [[Construct]]所以既能Person()又能newPerson()箭头函数主要只有[[Call]]因此Person()可以newPerson()不可以。满分答案箭头函数不能被new根本原因不是简单的“没有prototype”而是它没有[[Construct]]内部方法因此不是构造器。prototype缺失是这种设计的外在表现之一而不是最底层原因。十二、为什么class必须用newclassPerson{constructor(name){this.namename;}}Person(张三);会报错。而newPerson(张三);正常。核心class构造器只能进行构造调用class Person ↓ [[Construct]] ↓ new Person()不能进行普通函数调用Person() ↓ TypeError满分答案class构造器被设计成只能通过构造调用执行不能像普通函数一样直接调用。这是为了让类的使用方式更加明确class用于定义可构造的实例类型而不是普通函数。这也是为什么Person()会报错而newPerson()可以正常执行。十三、new.target是干什么的这是高级面试比较喜欢问的一个点。functionPerson(){console.log(new.target);}newPerson();输出Person如果Person();则undefined核心思路new.target用来判断当前函数是否通过构造调用进入以及实际的构造目标是谁。例如functionPerson(){if(!new.target){thrownewError(必须使用 new);}}这样Person();会报错。而newPerson();正常。继承时更加重要classParent{constructor(){console.log(new.target);}}classChildextendsParent{}newChild();这里输出的是Child而不是Parent这和前面的Reflect.construct(..., newTarget)是一条知识链。十四、new、Object.create()、Reflect.construct()到底怎么区分能力newObject.create()Reflect.construct()创建对象✅✅✅设置原型✅✅✅执行构造函数✅❌✅处理构造函数返回值✅❌✅支持newTarget❌❌✅适合模拟构造调用✅❌✅一句话记忆Object.create() 创建对象 指定原型 new 创建对象 指定原型 执行构造函数 处理返回值 Reflect.construct() 更底层、更灵活的构造调用十五、new和原型链到底是什么关系这是面试官很喜欢拿来判断候选人是否真正理解new的问题。核心思路new本身并不是“继承机制”它只是构造实例时建立了实例到构造函数prototype的原型关系真正的属性查找依赖原型链。例如functionPerson(){}Person.prototype.sayfunction(){};constpnewPerson();关系p │ │ [[Prototype]] ▼ Person.prototype │ │ [[Prototype]] ▼ Object.prototype │ ▼ null因此p.say();实际上发生的是p ↓ Person.prototype ↓ 找到 say这个区别很重要不要说new实现了继承。更准确new建立了实例与构造函数原型之间的关系原型链机制负责属性和方法查找。十六、new创建出来的对象一定是普通对象吗不一定。高级场景需要区分newObject();newArray();newDate();newMap();newSet();这些都属于构造调用。而newArray(10)还会触发Array自己特殊的构造行为。所以不能简单认为new永远就是{}prototypecall()。这只是理解普通构造函数时的简化模型。高级答法对普通 JavaScript 构造函数可以用“创建对象、设置原型、执行构造函数、处理返回值”理解new。但从规范角度看不能把new简化成Object.create()加call()。不同构造器可能有自己的构造行为内建对象尤其如此。因此手写new的实现通常只是行为近似而不是规范的完整复制。十七、new和instanceof有什么关系functionPerson(){}constpnewPerson();pinstanceofPerson;// true很多人会误以为new给对象打了一个“Person 类型”的标记。这是错的。实际上new Person() ↓ p.[[Prototype]] Person.prototype ↓ instanceof Person ↓ 沿原型链查找 Person.prototype所以pinstanceofPerson本质上和Person.prototype是否出现在p的原型链上有关。满分答案new和instanceof没有直接的“类型标记”关系。new Person()创建实例时建立了p.[[Prototype]] Person.prototype后续instanceof Person会检查Person.prototype是否存在于p的原型链中。所以两者是通过原型关系联系起来的。十八、如果修改Person.prototype已经创建的实例会怎样functionPerson(){}constpnewPerson();Person.prototype.sayfunction(){console.log(hello);};p.say();可以正常调用。因为p ↓ Person.prototype ↓ sayp保存的是对Person.prototype对象的原型引用。但是如果Person.prototype{say(){console.log(new);}};再p.say();就不一样了。因为旧实例仍然指向旧的 prototype 对象旧 p │ ▼ 旧 Person.prototype Person.prototype │ ▼ 新的 prototype这是很好的高级追问修改原prototype对象的属性会影响已有实例直接替换prototype对象不会改变已有实例的原型引用。十九、new为什么是 JavaScript 面向对象的重要基础核心思路构造函数 │ ├── prototype ───────┐ │ │ ▼ ▼ 构造逻辑 原型方法 │ │ └───────┬────────────┘ ▼ new Person() │ ▼ 实例对象new把构造函数 prototype 实例 原型链连接起来。所以面试中真正应该理解的是new ↓ 构造调用 ↓ 实例创建 ↓ [[Prototype]] ↓ prototype ↓ 原型链 ↓ instanceof这条链打通以后new相关的大部分题都能解释。二十、最容易被追问的完整追问链建议你把这一组直接作为一个专题背下来① new 做了什么 ↓ ② 为什么实例能访问 prototype ↓ ③ new 和 Object.create() 区别 ↓ ④ 构造函数返回对象怎么办 ↓ ⑤ 为什么返回基本类型会被忽略 ↓ ⑥ 手写 new 怎么实现 ↓ ⑦ 为什么不能只用 apply ↓ ⑧ Reflect.construct() 是什么 ↓ ⑨ Reflect.construct 的 newTarget 有什么用 ↓ ⑩ new.target 是什么 ↓ ⑪ 箭头函数为什么不能 new ↓ ⑫ class 为什么必须 new ↓ ⑬ new 和 instanceof 什么关系 ↓ ⑭ new 和原型链什么关系 ↓ ⑮ 修改/替换 prototype 对已有实例有什么影响 ↓ ⑯ 手写 new 为什么不能完全等价于原生 new最后真正应该背的「满分答案」如果面试官只问“你说一下new做了什么”不要一上来就背四步。建议直接这样回答new本质上是一次构造调用创建实例、建立原型关系、执行构造函数、确定最终返回值。比如constpersonnewPerson(张三);可以从四步理解new Person(张三) │ ▼ ① 创建一个新对象 obj │ ▼ ② obj.[[Prototype]] Person.prototype │ ▼ ③ 以 obj 作为 this 执行 Person(张三) │ ▼ ④ 根据构造函数返回值决定最终结果 ├─ 返回对象/函数 → 返回这个值 └─ 其他值 → 返回 obj第二步非常关键它建立了person │ ▼ Person.prototype │ ▼ Object.prototype所以实例可以通过原型链访问Person.prototype上的方法。例如functionPerson(name){this.namename;}Person.prototype.sayHellofunction(){console.log(this.name);};constpersonnewPerson(张三);person.sayHello();这里person自己没有sayHello而是沿着原型链找到Person.prototype.sayHello。另外构造函数的返回值有特殊规则functionPerson(){this.name张三;return{name:李四};}constpersonnewPerson();console.log(person.name);// 李四因为构造函数显式返回了一个对象所以这个对象替代了new创建的实例。如果返回的是基本类型则会被忽略。需要注意的是面试中常说的“new四步”是理解模型并不是 JavaScript 引擎内部源码。对于普通构造函数可以近似理解成constobjObject.create(Constructor.prototype);constresultConstructor.apply(obj,args);if(result!null(typeofresultobject||typeofresultfunction)){returnresult;}returnobj;但这并不能完整等价于原生new例如class构造器、内建构造器以及更复杂的构造语义都不能简单用Object.create() apply()模拟。如果继续往底层看Reflect.construct()提供了更接近规范层面的构造调用能力而且可以额外指定newTarget这也是理解class继承、super()和new.target的重要基础。所以我认为理解new最重要的不是死记“四步”而是把这条链打通new ↓ 构造调用 ↓ 创建实例 ↓ [[Prototype]] → Constructor.prototype ↓ 原型链 ↓ instanceof ↓ new.target / Reflect.construct
返回列表