牛客最新笔试题解析(二)原型与原型链题目

1,877 阅读3分钟

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」。

前言

这篇文章主要来解析牛客笔试题部分的原型与原型链题目。首先我们先从宏观上了解一下 JavaScript 中的原型与原型链。

prototype

每个函数都有一个属性 prototype ,它就是原型,默认情况下它是一个普通 Object 对象,这个对象是调用该构造函数所创建的实例的原型。

constructor

JavaScript 同样存在由原型指向构造函数的属性:constructor,即 Func.prototype.constructor --> Func

__proto__

JavaScript 中所有对象(除了 null )都具有一个 __proto__ 属性,该属性指向该对象的原型。

原型与原型链总结图

原型链.png

详解

传送门: JavaScript之彻底理解原型与原型链

题目一: 原型链基础题

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 时,未传入 nameif (name) false ,无法添加 this.name 属性,当访问 name 属性时,沿原型链查找,打印 Fn1.prototype.name
  • 定义实例 b 时,未传入 namethis.name 赋值为 undefinedb 实例上有 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(): 与上一步相同,修改立即执行函数中的 x2 ,打印 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