这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
原型和原型链
什么是原型和原型链
在 JavaScript 中,每个对象都有一个原型对象,原型对象也是一个普通的对象,它有一个 constructor 属性指向创建它的构造函数,它也有一个原型对象,这样就构成了一个原型链。在 JS 中,面向对象的实现是基于原型的,而不是基于类的。就算 ES6 中引入了 class 关键字,但是它只是语法糖,本质上还是基于原型的。
class 和继承
class Student{
// 构造函数
constructor(name, number){
this.name = name;
this.number = number;
// ...
}
// 方法
sayHi(){
console.log(`姓名:${this.name},学号:${this.number}`);
}
// ...
}
// 通过类声明对象(实例)
const Zhangsan = new Student('张三', 100);
console.log(Zhangsan.name);
Zhangsan.sayHi();
这里使用了模板字符串来拼接字符串,模板字符串是 ES6 中新增的语法,它可以很方便的拼接字符串,它的语法是使用反引号来包裹字符串,然后在字符串中使用${} 来引用变量。
同很多面向对象的语言一样,我们可以使用 extends 关键字来实现继承,然后使用 super 来继承属性。
class Person{
constructor(name){
this.name = name;
}
eat(){
console.log(`${this.name} eat something`);
}
}
class Student extends Person {
constructor(name,number){
super(name);
this.number = number;
}
sayHi(){
console.log(`姓名:${this.name},学号:${this.number}`);
}
}
const Zhangsan = new Student('张三', 100);
console.log(Zhangsan.name); // 张三
Zhangsan.sayHi(); // 姓名:张三,学号:100
Zhangsan.eat(); // 张三 eat something
class Teacher extends Person {
constructor(name,major){
super(name);
this.major = major;
}
teach(){
console.log(`${this.name} 教授 ${this.major}`);
}
}
const Wanglaoshi = new Teacher('王老师', '语文');
console.log(Wanglaoshi.name); // 王老师
Wanglaoshi.teach(); // 王老师 教授 语文
Wanglaoshi.eat(); // 王老师 eat something
原型
类型判断 instanceof
instanceof 是用来判断一个对象是否是某个类的实例,它的语法是 object instanceof constructor,其中 object 是要判断的对象,constructor 是构造函数。
Wanglaoshi instanceof Teacher; // true
Wanglaoshi instanceof Person; // true
Wanglaoshi instanceof Student; // false
Wanglaoshi instanceof Object; // true
[] instanceof Array; // true
[] instanceof Object; // true
{} instanceof Object; // true
原型
typeof Student // function
typeof Person // function
因此我们可以看出,这里的类实际上是使用 function 来实现的,这里的类语法只是语法糖,本质上还是基于原型的。
Student.prototype // {constructor: ƒ, sayHi: ƒ}
Person.prototype // {constructor: ƒ, eat: ƒ}
原型分为隐式原型和显示原型,隐式原型是对象的 __proto__ 属性,显示原型是函数的 prototype 属性。
console.log(Zhangsan.__proto__ === Student.prototype); // true
console.log(Zhangsan.__proto__); // {constructor: ƒ, sayHi: ƒ}
console.log(Student.prototype); // {constructor: ƒ, sayHi: ƒ}
- Student
| 属性 | 值 |
|---|---|
| prototype | 指向 Student.prototype |
- Zhangsan
| 属性 | 值 |
|---|---|
| name | 张三 |
| number | 100 |
__proto__ | 指向 Student.prototype |
Student.prototype
| 属性 | 值 |
|---|---|
| sayHi | ƒ sayHi() |
性质
- 每个 class 都由显式原型
prototype - 每个实例都有隐式原型
__proto__ - 实例的隐式原型指向类的显式原型
执行规则
获取属性 Zhangsan.name 或执行方法 Zhangsan.sayHi()时:
- 先在自身属性和方法中查找,找到的是自己的属性和方法,就直接使用
- 如果没有找到,就会沿着
__proto__这条链向上查找,直到找到为止,这就实现了继承
原型链
console.log(Person.prototype === Student.prototype.__proto__); // true
这个表达式表明,Student.prototype 的隐式原型指向 Person.prototype。
Zhangsan.hasOwnProperty('name'); // true
Zhangsan.hasOwnProperty('sayHi'); // false
hasOwnProperty 方法是用来判断一个属性是不是自身的属性,而不是继承的属性。
最终的原型链是这样的:
console.log(Object.hasOwnProperty('hasOwnProperty')); // true
instanceof 的原理 ⭐
const zs = new Student('张三',1234);
// 自己实现 intanceof
/**
* @ param L 待判断的
* @ param R 被匹配的
*/
function instance_of(L,R){
let O = R.prototype;
L = L.__proto__;
while(true){
// 查找到原型链顶端的 Object (object.__proto__ === null)
if(L === null){
return false;
}
if (O === L){
return true;
}
L = L.__proto__;
}
}
console.log(instance_of(zs,Person))
这是一个逐级向上查找的过程,直到找到 R.prototype 为止。如果最终没有找到,就返回 false。L===null 表示已经到了原型链的顶端的 object,还没有找到,所以返回 false。
重要提示
- class 是 ES6 的语法规范,由 ECMA 委员会发布
- ECMA 只指定了语法规范,不指定具体的实现
- 以上的原型链是 V8 引擎的实现,其他引擎的实现可能不同
例子
- 如何判断一个对象是不是数组?
答:使用 instanceof 来进行判断。
- 手写简易的 jQuery ,考虑插件和扩展性。
jQuery 最核心的功能就是选择 DOM 元素,然后对其进行操作。
<div id="app">hello world</div>
<p>你好</p>
<p>嘿嘿</p>
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector) // 查询 dom 元素
const length = result.length
for (let i = 0; i < length; i++) {
this[i] = result[i]
}
this.length = length
this.selector = selector
}
get(index) {
return this[index] // 返回 dom 元素
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i]
fn(elem) // 执行回调函数
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false) // 绑定事件
})
}
// 扩展
}
const $p = new jQuery('p')
console.log($p) // jQuery {0: p, 1: p, length: 2, selector: "p"}
console.log($p.get(1)) // <p>嘿嘿</p>
$p.on('click',()=>{
alert('clicked') // 点击 p 标签,弹出 clicked
})
我们可以通过原型链直接扩展 jQuery 的功能,如下:
// 插件,直接向原型中添加
jQuery.prototype.dialog = function (info){
alert(info)
}
$p.dialog('asd') // 弹出 asd
同时可以通过继承 jQuery 来扩展功能,如下:
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 扩展
}
- class 的原型本质,如何理解?
如上面所述。