前言
这篇文章主要来解析牛客笔试题部分的原型与原型链题目。首先我们先从宏观上了解一下 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
的实例a
a.increment()
: 实例a
上没有该函数,沿作用域链查找到Foo.prototype.increment
函数;increment
函数内部没有变量x
,获取立即执行函数中x
,++x
,打印1
(立即执行函数作用域中的x
修改为1
)a.increment()
: 与上一步相同,修改立即执行函数中的x
为2
,打印2
b = new Foo()
: 创建函数Foo
的实例b
b.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'
,打印Tom
console.log(name)
: 打印Tom
console.log(a)
:Person()
执行无返回值,打印undefined
new Person('Michael')
: 定义实例b
,打印Michael
console.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