「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」。
前言
这篇文章主要来解析牛客笔试题部分的原型与原型链题目。首先我们先从宏观上了解一下 JavaScript 中的原型与原型链。
prototype
每个函数都有一个属性 prototype ,它就是原型,默认情况下它是一个普通 Object 对象,这个对象是调用该构造函数所创建的实例的原型。
constructor
JavaScript 同样存在由原型指向构造函数的属性:constructor,即 Func.prototype.constructor --> Func
__proto__
JavaScript 中所有对象(除了 null )都具有一个 __proto__ 属性,该属性指向该对象的原型。
原型与原型链总结图

详解
题目一: 原型链基础题
function Fn1(name) {
if(name){
this.name = name;
}
}
Fn1.prototype.name="jack"
let a = new Fn1();
console.log('a:', a.name);
function Fn2(name) {
this.name = name;
}
Fn2.prototype.name="jack"
let b = new Fn2();
console.log('b:', b.name);
解析
- 定义实例
a时,未传入name,if (name) false,无法添加this.name属性,当访问name属性时,沿原型链查找,打印Fn1.prototype.name - 定义实例
b时,未传入name,this.name赋值为undefined。b实例上有name属性,打印b.name
答案
a: jack
b: undefined
题目二: 闭包配合原型链
var Foo = (function() {
var x = 0;
function Foo() {}
Foo.prototype.increment = function() {
++x;
console.log(x);
};
return Foo;
})();
var a = new Foo();
a.increment();
a.increment();
var b = new Foo();
b.increment();
解析
题目中共出现三个函数: 外层立即执行函数、Foo 函数、 increment 函数。返回值为 Foo 函数。
increment 函数与 Foo 函数的上层作用域都是立即执行函数,即这两个函数都可以访问到立即执行函数中的 x 变量
a = new Foo(): 创建函数Foo的实例aa.increment(): 实例a上没有该函数,沿作用域链查找到Foo.prototype.increment函数;increment函数内部没有变量x,获取立即执行函数中x,++x,打印1(立即执行函数作用域中的x修改为1)a.increment(): 与上一步相同,修改立即执行函数中的x为2,打印2b = new Foo(): 创建函数Foo的实例bb.increment(): 获取立即执行函数中的x,修改为3,打印3
答案
1
2
3
题目三: new执行与普通函数执行
var name = 'Jay'
function Person(name){
this.name = name;
console.log(this.name)
}
var a = Person('Tom')
console.log(name)
console.log(a)
var b = new Person('Michael')
console.log(b)
这应该算是
this方向的题目,整理时没分好类
解析
Person('Tom'): 默认绑定,非严格模式this -> window,修改全局name = 'Tom',打印Tomconsole.log(name): 打印Tomconsole.log(a):Person()执行无返回值,打印undefinednew Person('Michael'): 定义实例b,打印Michaelconsole.log(b): 打印实例b
答案
Tom
Tom
undefined
Michael
Person {name: "Michael"}
题目四: 表述以下代码的执行结果和原因(推荐看)
var tmp = {};
var A = function() {};
A.prototype = tmp;
var a = new A();
A.prototype = {};
var b = Object.create(tmp);
b.constructor = A.constructor;
console.log(a instanceof A);
console.log(b instanceof A);
解析
我先给大家举个例子,咱们就能很轻易地理解该题:
var obj = {x: 1};
var a = obj;
obj = {x: 2};
console.log(a.x);
打印结果是 1,为什么?
第二行代码中已经把 a 存放的地址指向最初 obj 指向地址,也就是 {x:1} 的存放地址;之后将 obj 指向新的地址 {x:2}, a 地址指向没有收到任何影响,依旧指向 {x:1} 的存放地址,所以打印结果是 1。
上面题目与案例是类似的,我们来剖析一下:
// 将A的原型指向 tmp 对象地址
A.prototype = tmp;
// a.__proto__ 指向 A.prototype指向地址,即 tmp 地址
var a = new A();
// 修改A.prototype 指向为空对象 {}
A.prototype = {};
// a.__proto__ 仍然指向 tmp 地址
instanceof 判断对象的原型链上是否存在构造函数的原型。只能判断引用类型。
A.prototype 已经修改为 {} ,a,b 的原型链的原型链为: a.__proto__ -> tmp -> tmp.__proto__ -> Object.prototype -> null
答案
false
false
题目五: delete
const Book = {
price: 32
}
const book = Object.create(Book);
book.type = 'Math';
delete book.price;
delete book.type;
console.log(book.price);
console.log(book.type);
解析
delete 只能删除自身属性,不能删除继承来的属性。
- delete book.price: Book 的属性,只能访问,不能删除
- delete book.type: 自身属性,可以删除
答案
32
undefined