最近两周面试了将近30个人,其中绝大部分都是有3年以上的前端工作经验,但是很多人对js的原型和继承的概念都搞不懂,更不用说更深层次的问题了。这也是我写这篇文章的原因吧,同时也是有必要自己做一个知识总结。
Javascript里的类
ES6为我们带来了定义类的keyword class
,但这只不过是给程序员少些几行代码的语法糖而已,原型还是必须要掌握的知识。
类从理论上来说就是 数据 + 方法 的集合,那么可以很简单地写出下面的代码
function Person(name) {
return {
name: name,
say() {
console.log(`Hello, I'm ${this.name}`)
}
}
}
let person = Person('jolyne');
写完后就会发现这种“类”很难实现继承,这时就可以借助原型链来实现。
原型链
原型链特性主要有两点:
一是可以通过对象上的原型不断往上查找属性,数组、字符串等对象携带的方法就是通过这种方式实现的。
let a = {};
// 操作对象的原型也可以通过Object.getPrototypeOf和Object.setPrototypeOf
a.__proto__ = {
__proto__: {
__proto__: { value: 1 }
}
}
// 这里会一直往上查找__proto__
a.value === 1;
还有一点就是 new
这个keyword会创建 __proto__
。
function A() {}
let a = new A();
// __proto__会引用构造函数的prototype
a.__proto__ === A.prototype;
// A.prototype === Object.prototype
a.__proto__.__proto__ === Object.prototype;
顺带一提instanceof
就是通过比较原型来判断的
[] instanceof Object
// ==>
[].__proto__.__proto__ === Object.prototype
另外还有一个比较有意思的点
function foo() {}
// 函数的__proto__指向了一个内部实现
foo.__proto__ // => native code
// 不过依旧是继承自Object
foo.__proto__.__proto__ === Object.prototype
原型的继承
通过原型链的特性就能得到实现继承的基本思路了
// base
function A() {}
A.prototype.foo = function() {}
// child
function B() {}
// B.prototype = { __proto__: A.prototype };
B.prototype = Object.create(A.prototype);
// B.prototype因为会被修改,所以需要是一个新的对象
B.prototype.bar = function() {}
let b = new B();
b.foo === A.prototype.foo;
这样就实现了基于原型的继承,通常我们会将方法挂在原型上,因为它的定义是同个类下所有对象共享的而且通常不受外部影响。那么对于每个对象上特有的数据要怎么办呢?
最简单的方式就是在子类的构造函数里调用父类
// base
function A(value) {
this.a = value;
}
// child
function B(value) {
A.call(this, value);
this.b = value;
}
静态变量继承
静态变量同样可以通过原型链的方式继承,因为静态变量是绑定在构造函数上的,只需将构造函数加入到原型链中就能查找
function A() {}
A.staticField = function() {}
function B() {}
B.__proto__ = A;
B.staticField === A.staticField;
总结
基本原理就是这么些了,看完这篇你也就能看懂Typescript和Babel如何对类做转换了,有些细节我没讲到,比如容错检查、constructor设置。