起因
当我输入了大量来自优质个人博客文章,经典书籍,名牌讲师的 JavaScript 系统知识之后,我反而变得有些困惑。我明明可以流利的书写八大继承,流利的书写 Underscore 库中的防抖节流,深拷贝……,我也知晓什么是闭包,什么是迭代器,生成器……。但是问题来了,当我试图将一个 JavaScript 核心概念,比如闭包……,介绍给同学,讲解给自己,对话面试官的时候。原本自认为可以脱口而出的语言,到嘴边却显得吞吞吐吐。只能用只言片语,或者东拼西凑的知识点来表达给对方。这无疑让我有了一种,虽然花了大量时间但是却从未拥有过 JavaScript 的感觉。
目的
所以这个系列要尝试解决的问题就是当别人询问或者考察我JavaScript 核心概念的时候,我可以尽可能流畅的,清晰的表达给他人。
期望
希望掘金优秀的前端技术人员和前辈们可以在百忙之中多多补充这篇文章,多多审查这篇文章,多多提问我。我期望可以通过这个系列来解决我当前的问题。
你说一下你对原型是怎么样理解的
回答你对原型的理解
首先想和面试官聊一聊我对原型的理解,JavaScript 是基于原型的语言,原型类似于经典面向对象语言当中的类,我们可以利用 JavaScript 原型让我们使用一种类风格的面向对象和继承技术进行编码。但是和传统面向对象语言不同,JavaScript 通过原型来继承和实例化的时候,并不会像传统面向对象语言那样将类的行为复制到实例或者子类当中去,而是将多个对象进行关联来实现。
回答原型在 JavaScript 当中的实现
其次想和面试官聊一聊原型在JavaScript当中的实现,JavaScript 当中的所有函数在初始化的时候都有一个 prototype 属性,该属性指向的就是通过调用构造函数创建的实例对象的原型,使用原型对象的好处是我们可以预定义属性,方法。这些属性和方法会被实例对象所共享。
回答 JavaScript 当中 Object Layout(原型设计)
最后想和面试官聊一聊 gitHub 上的一张关于 Object Layout 的神图。这张图,清晰的画出了 JavaScript 语言的原型设计。
这张图,我们可以按照显式原型和隐式原型两条线来进行全面梳理。
显式原型路线是:无论是基本类型构造函数(Number, String, BigInt, Boolean, Symbol)还是引用类型构造函数(Array, Date, Function, RegExp,除了 Object),还是其他自定义构造函数,都遵守:
构造函数.prototype.__proto__
等于Object.prototype
Object.prototype.__proto__
等于顶层原型null
。
// 基本类型 构造函数
console.log(Number.prototype.__proto__ == Object.prototype);
console.log(String.prototype.__proto__ == Object.prototype);
console.log(BigInt.prototype.__proto__ == Object.prototype);
console.log(Boolean.prototype.__proto__ == Object.prototype);
console.log(Symbol.prototype.__proto__ == Object.prototype);
// 引用类型 构造函数
console.log(Function.prototype.__proto__ == Object.prototype);
console.log(Array.prototype.__proto__ == Object.prototype);
console.log(Date.prototype.__proto__ == Object.prototype);
console.log(RegExp.prototype.__proto__ == Object.prototype);
// 自定义构造函数
console.log(Person.prototype.__proto__ == Object.prototype, "自定义");
//全部为true
// 顶层原型 null
console.log(Object.prototype.__proto__);
// null
复制代码
隐式原型路线是:无论是基本类型构造函数(Number, String, BigInt, Boolean, Symbol),还是引用类型构造函数(Array, Date, Function, RegExp,包括 Object),还是其他自定义构造函数,都遵守:
构造函数.__proto__ 等于 Function.prototype
构造函数.__proto__.__proto__ 等于 Object.prototype
// 基本类型 构造函数
console.dir(Number.__proto__.__proto__ === Object.prototype);
console.dir(String.__proto__.__proto__ === Object.prototype);
console.dir(BigInt.__proto__.__proto__ === Object.prototype);
console.dir(Boolean.__proto__.__proto__ === Object.prototype);
console.dir(Symbol.__proto__.__proto__ === Object.prototype);
// 引用类型 构造函数
console.dir(Array.__proto__.__proto__ === Object.prototype);
console.dir(Date.__proto__.__proto__ === Object.prototype);
console.dir(RegExp.__proto__.__proto__ === Object.prototype);
console.dir(Function.__proto__.__proto__ === Object.prototype);
// 自定义构造函数
console.dir(Function.__proto__.__proto__ === Object.prototype,"自定义");
// 基本类型 构造函数
console.dir(Number.__proto__ === Function.prototype);
console.dir(String.__proto__ === Function.prototype);
console.dir(BigInt.__proto__ === Function.prototype);
console.dir(Boolean.__proto__ === Function.prototype);
console.dir(Symbol.__proto__ === Function.prototype);
// 引用类型 构造函数
console.dir(Array.__proto__ === Function.prototype);
console.dir(Date.__proto__ === Function.prototype);
console.dir(RegExp.__proto__ === Function.prototype);
console.dir(Function.__proto__ === Function.prototype);
console.log(Object.__proto__.__proto__ === Object.prototype);
// 自定义构造函数
console.log(Person.__proto__ == Function.prototype, "自定义");
// 全部都是true
复制代码
练手滴滴面试题
有了上面的基础,做下面的题如同切菜一般简单了吧!
Object.prototype.name = "object";
Function.prototype.name = "function";
function Person() {}
const p = new Person();
console.log(Person.name);
console.log(p.name);
console.log(p.__proto__.__proto__.constructor.constructor.constructor.constructor.constructor);
// function
// object
// Function构造函数
复制代码
你说一下你是怎么样理解原型链的
相互关联的原型对象的链接被称为原型链, 当我们读取实例属性或者方法的时候, 会通过
[[prototype]]
查找即沿着原型链进行委托查找,一直找到最顶层为止。
其他原型知识 - 重写原型对象
- 如果我们需要在原型对象上定义很多属性和方法的时候,我们可以重写原型对象。
- 重写时需要注意,原型对象的 constructor已经丢失,我们需要重新定义具有不可遍历特征的constructor属性。
const foo.prototype = {
name: 'CIc',
age: 18,
eating() {},
drinking() {}
}
Object.defineProperty(foo.prototype, 'constructor', {
enumerable: false,
value: foo,
writable: true,
configurable: true;
})
复制代码
其他原型知识 - 检测原型的方法
in
判断属性是否在某个对象或者某个对象的原型上。
var obj = {
name: "why",
age: 18,
};
var info = Object.create(obj, {
address: {
value: "北京市",
enumerable: true,
},
});
// in 操作符: 不管在当前对象还是原型中返回的都是true
console.log("address" in info);
console.log("name" in info);
复制代码
hasOwnProperty
判断对象是否有属于自己的属性(不在原型链上找)。
var obj = {
name: "why",
age: 18,
};
var info = Object.create(obj, {
address: {
value: "北京市",
enumerable: true,
},
});
// hasOwnProperty方法判断
console.log(info.hasOwnProperty("address")); // true
console.log(info.hasOwnProperty("name")); // false
复制代码
instanceof
用于检测构造函数的原型对象是否出现在实例对象的原型链上。
function Person() {}
function Student() {}
var stu = new Student();
inheritPrototype(Student, Person);
console.log(stu instanceof Student); // true
console.log(stu instanceof Person); // true
console.log(stu instanceof Object); // true
复制代码
constructor
用于检测实例对象的是否属于某个构造函数。
function Person() {}
const p1 = new Person();
console.log(p1.constructor === Person); // true
复制代码
参考
- GitHub神图 - Hursh Jain/mollypages.org
- 《你不知道的JavaScript》
- 《JavaScript忍者秘籍》