总结
- 在大多数浏览器的ES5实现中,每个对象都有__proto__属性,指向对应的构造函数的prototype。
- Class作为构造函数语法糖,同时有prototype属性和__proto__属性
- 子类的__proto__属性表示构造函数的继承,总是指向父类
- 子类的prototype属性的__proto__属性表示方法的继承,总是指向父类的prototype
抛一个问题
- 都说JS继承特殊,到底特殊在哪里?与传统的面向对象编程语言(如 Java、C++ 等)的继承有啥不同?
深入JS继承 看看内置的类型的指向
Function.__proto__ === Function.prototype //true 特殊
// 其实Function.prototype就是一个对象,而对象实例的__proto__指向Object.prototype
Function.prototype.__proto__ === Object.prototype //true
Object.__proto__ === Function.prototype //true
Array.__proto__ === Function.prototype //true
String.__proto__ === Function.prototype //true
Number.__proto__ === Function.prototype //true
Boolean.__proto__ === Function.prototype //true
Symbol.__proto__ === Function.prototype //true
BigInt.__proto__ === Function.prototype //true
Error.__proto__ === Function.prototype //true
// 除了上述内置函数 还有Math、Set、Map、WeakSet、WeakMap、Date、RegExp、JSON、Promise...
({}).__proto__ === Object.prototype //true
([]).__proto__ === Array.prototype //true
("123").__proto__ === String.prototype //true
(123).__proto__ === Number.prototype //true 会形成包装类,和new Number(123)一样
(true).__proto__ === Boolean.prototype //true
Symbol(123).__proto__ === Symbol.prototype // true
BigInt(123).__proto__ === BigInt.prototype // true
Error(123).__proto__ === Error.prototype //true
// Note: Error(123).__proto__ === new Error(123).__proto__
Object.prototype.__proto__ === null //true
Array.prototype.__proto__ === Object.prototype //true
String.prototype.__proto__ === Object.prototype //true
Number.prototype.__proto__ === Object.prototype //true
Boolean.prototype.__proto__ === Object.prototype //true
Symbol.prototype.__proto__ === Object.prototype //true
BigInt.prototype.__proto__ === Object.prototype //true
Error.prototype.__proto__ === Object.prototype //true
TypeError.prototype.__proto__ === Error.prototype
// Note: JavaScript 中的内置错误类型有以下七种:
// 1. Error(错误):所有错误类型都继承自 Error 类型。
// 2. EvalError(eval 错误):eval() 函数执行时发生错误时抛出的错误类型。
// 3. RangeError(范围错误):当一个值超出有效范围时抛出的错误类型。
// 4. ReferenceError(引用错误):当尝试访问未定义的变量时抛出的错误类型。
// 5. SyntaxError(语法错误):当代码存在语法错误时抛出的错误类型。
// 6. TypeError(类型错误):当尝试使用错误的数据类型时抛出的错误类型,比如使用一个非函数类型的值进行函数调用。
// 7. URIError(URI 错误):当在使用全局函数 encodeURI() 或 decodeURI() 时,传入的参数无效时抛出的错误类型。
那到底特殊在哪里
// 有了上面基础做铺垫来看看class
class Object{} // function Object(){}
class Function extends Object{} // function Function(){}
// class默认
Function.__proto__ === Object
Function.Prototype.__proto__ === Object.prototype
// 实际上
Function.__proto__ === Function.prototype === Object.__proto__
Function.Prototype.__proto__ === Object.prototype
// 但是 Object === Object.__proto__ // ???
// 答案肯定是false 这也是我在全文第一行代码标志特殊的原因
很显然上述的class写法肯定是错的,因为JS内置的方法(Object,String,Number...)的继承是ES6之前的写法,此时都没有class,不要搞混。
Object.prototype = {...}
Function.prototype = Function.__proto__ = function(){}
function Object{}
function Function{}
Function.prototype.__proto__ === Object.prototype
// 但是它并不影响下层的class的指向
// 除了继承Function的函数(Object,String,Number...)比较特殊外 下层都遵循class的规则(语法糖)
// function Error{} 内置方法
// class TypeError extends Error{} 内置class TypeError继承
TypeError.__proto__ === Error
TypeError.prototype.__proto__ === Error.prototype
// 至于下层遵循class的规则(语法糖)到底是啥?感兴趣的话可以用babel编译一下
// 我这里可以简单贴一段代码
// 编译前
function A{} // 看清楚这里是函数 就算是class 最后还是会被转化为function
class B extends A {}
// 编译后
function A() {}
var B = /*#__PURE__*/function (_A) {
_inherits(B, _A); // 这个函数里面实现继承
var _super = _createSuper(B);
function B() {
_classCallCheck(this, B);
return _super.apply(this, arguments);
}
return _createClass(B);
}(A);
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function")
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true },
})
// B.prototype.__proto__ = A.prototype
Object.defineProperty(subClass, "prototype", { writable: false })
// B.__proto__ = A
if (superClass) _setPrototypeOf(subClass, superClass)
}
总结一下: JS继承特殊的原因就是内置的方法扩展功能都是基于Function 去扩展的,es6之前什么原型链继承 构造函数继承 组合继承 原型式继承 寄生式继承 寄生组合式继承...这些都是围绕着基于函数的原型链特性({proto => prototype}),这使得继承在 JavaScript 中更加灵活和自由, 尽管es6有了class的,本质上还是通过函数去实现继承的。
一图胜千言
基础用法
class Foo{
constructor(){
//return this 默认
}
}
class Point{
constructor (x,y,color){
this.x = x;
this.y = y;
this.color = color;
}
}
class ColorPoint extends Point{
constructor (x,y,color){
// this.color = color; //不写super会报错
super(x,y,color);
console.log(this); //ColorPoint {x: 25, y: 8, color: "green"}
}
toString(){
// return this.color + " " + super.toString;
console.log(this);
}
}
let cp = new ColorPoint(25,8,'green');
//实例cp
cp instanceof ColorPoint //true
cp instanceof Point //true
Object.getPrototypeOf 方法可以用来从子类上获取父类
Object.getPrototypeOf(ColorPoint) === Point; //true;
Object.getPrototypeOf(cp) //Point {constructor: ƒ, toString: ƒ}
// super关键字
// super这个关键字既可以当做函数用,也可以当做对象用,两种情况的用法不同
// 第一种情况,super作为函数调用时代表父类的构造函数。ES6要求,子类构造函数必须执行一次super函数
class A{
constructor(){
console.log(this)
console.log(new.target.name); //new.target指向当前所new的函数
}
}
class B extends A{
constructor(){
super(); //这里的super相当于A.prototype.contructor.call(this);
} // A.prototype.contructor.call(B);
}
new A(); //A
new B(); //B 这里说明了super函数内部的this指向的是B
第二种情况,super作为对象时在普通方法中指向父类的原型对象,在静态方法中指向父类
子类B中的super.P()就是讲super当做一个对象使用,这时super在普通方法之中指向A.prototype
class A{
P(){
return 2;
}
}
class B extends A{
constructor(){
console.log(super()); // super执行返回实例 B{}
console.log(super.P()); // 这里的super.P()相当于 A.prototype.P()
// console.log(super);//报错
}
}
let b = new B(); //2
// 由于super指向父类的原型对象,所以定义在父类的实例上的方法或属性是无法通过super调用的
class pittle{
constructor(){
this.y = 1;
}
print(){
console.log(this.x);
}
}
class ann extends pittle{
constructor(){
super();
this.x = 2;
console.log(super.y);//undefined
}
}
let a = new ann();
// 如果属性定义在父类的原型对象上,super就可以取到
class A{}
A.prototype.x = 2;
class B extends A{
constructor(){
super();
console.log(super.x); //2
}
}
// ES6规定,通过super调用父类的方法时,super会绑定子类的this
class A{
constructor(){
this.x = 1;
}
print(){
console.log(this.x);
}
}
class B extends A{
constructor(){
super();
this.x = 2;
}
m(){
super.print(); //实际上执行的是 super.print.call(this)
}
}
let b = new B();
b.m() //2
// 由于super绑定子类的this,因此通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例属性
class A{
constructor (){
this.y = 1;
}
}
class B extends A{
constructor(){
super();
// this.x = 2;
super.x = 3; //赋值时等同于this.x = 3
console.log(super.x); //undefined 读取时等同于A.prototype.x
console.log(this.x); //3
}
}
let b = new B();
super作为对象用在静态方法中指向父类,而不是父类原型对象
class Parent{
static myMathod(msg){
console.log("static" + msg);
}
myMethod(msg){
console.log('instance' + msg);
}
}
class Child extends Parent{
static myMethod(msg){
super.myMathod(msg);
}
myMethod(msg){
super.myMathod(msg);
}
}
Child.myMathod(1); //static1
// prototype && __proto__
// 在大多数浏览器的ES5实现中,每个对象多有__proto__属性,指向对应的构造函数的prototype属性。
// Class作为构造函数语法糖,同时有prototype属性和__proto__属性
// 子类的__proto__属性表示构造函数的继承,总是指向父类
// 子类的prototype属性的__proto__属性表示方法的继承,总是指向父类的prototype
class A{}
class B extends A{}
B.__proto__ = A //true
B.prototype.__proto__ = A.prototype
A.__proto__ === Function.__proto__;//true
A.__proto__ === Function.prototype //true
Function.prototype === Function.__proto__ //true 函数比较特殊
Array.__proto__ === Array.prototype //false
Array.__proto__ === Object.__proto__ //true
A.prototype === Function.prototype //false
A.prototype === Function.__proto__ //false
A.__proto__ //ƒ () { [native code] }
Function.prototype //ƒ () { [native code] }
Function.__proto__ //ƒ () { [native code] }
A.prototype //{constructor: ƒ}
A.prototype.__proto__ === Object.prototype //true
Object.__proto__ === Object.prototype //false;
Function.__proto__.__proto__ === Object.prototype //true
Function.prototype.__proto__ === Object.prototype //true
Function.__proto__ === Object.__proto__ //true
Object.__proto__.__proto__ === Object.prototype //true
函数实例化对象指向这个函数的prototype
({}).__proto__ === Object.prototype //true
([]).__proto__ === Array.prototype //true
Array.__proto__ === Object.__proto__ //true
String.__proto__ === Object.__proto__ //true
Array.prototype.__proto__ === Object.prototype //true
// class静态属性
// 旧写法
class Foo(){
//...
}
Foo.prop = 1;
// 新写法
class Foo{
static prop = 1;
}
// Class 实例属性
// 旧写法
class MyClass{
constructor(){
this.myProp = 42;
console.log(this.myProp);//42
}
}
// 新写法
class MyClass{
myProp = 42;
constructor(){
console.log(this.myProp);//42
}
}
// B继承A 那么如果B没写constructor 默认就是constructor(a,b){super(a,b)}
class A {constructor(a,b){this.a = a;this.b = b}}
class B extends A{}
var b = new B(123,234)
console.log(b);//B {a: 123, b: 234}
Object.toString === Object.prototype.toString //false
Number.toString === Object.toString //true
数组重写了toString
Array.toString === Object.toString //true
Array.prototype.toString === Array.toString //false