学习react的时候遇到了class方式的继承语法,原理和代码的解释很详细,值得一读。

原型
每个函数(构造函数)都有一个 prototype 属性,指向该函数(构造函数)的原型对象。实例没有 prototype 属性,但是有 __proto__ 属性。函数同时有 prototype 和 __proto__ 属性。

function Person(name) {
this.name = name;
} let person = new Person('xiaoming'); person.__proto__ === Person.prototype; // true // 因为函数也是对象,所以也有 __proto__ 属性,指向 Function.prototype
Person.__proto__ === Function.prototype //true

由字面量创建的普通对象是Object的实例,
由 function 关键字声明的函数是Function的实例

let obj = {};
obj.__proto__ === Object.prototype; // true function fn() {};
fn.__proto__ === Function.prototype; // true

__proto__ 属性虽然在ECMAScript 6语言规范中标准化,但是不推荐被使用,现在更推荐使用Object.getPrototypeOfObject.getPrototypeOf(obj) 也可以获取到obj对象的原型

Object.getPrototypeOf(person) === person.__proto__; // true

原型链
当访问一个对象的属性时,先在对象的本身找,找不到就去对象的原型上找,如果还是找不到,就去对象的原型(原型也是对象,也有它自己的原型)的原型上找,如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回 undefined。

这条由对象及其原型组成的链就叫做原型链。

原型链存在的意义就是继承:访问对象属性时,在对象本身找不到,就在原型链上一层一层找。说白了就是一个对象可以访问其他对象的属性。

继承存在的意义就是属性共享:好处有二:一是代码重用,字面意思;二是可扩展,不同对象可能继承相同的属性,也可以定义只属于自己的属性

原型链的顶端

Object 类型
Object.prototype.__proto__ 是原型链的顶端了,指向 null。

let obj = {};
obj.__proto__ === Object.prototype; //true
Object.prototype.__proto__ === null; //true

Function 类型

Array.__proto__ === Function.prototype; //true
Object.__proto__ === Function.prototype; // true

同时,由于 Function 也是一个对象,所以其也有 __proto__ 属性,规定其属性指向 Function.prototype

对象都是被构造函数创建的,函数对象的构造函数就是 Function ,注意这里 F 是大写。

函数的最大的构造函数为 Function,其也有 prototype 属性,由于 Function.prototype 为一个对象,所以 Function.prototype.__proto__ 指向 Object.prototype。

function fn() {};
fn.__proto__ === Function.prototype;
Function.prototype.__proto__ === Object.prototype; // true
Function.__proto__ === Function.prototype; // true

instanceOf 的原理

作用:判断实例对象与构造函数之间是否为继承关系
原理:判断实例对象的 __proto__ 属性和构造函数的 prototype 属性,是否为同一个引用。
注意点:如果存在多层继承关系,instanceof 会一直沿着原型链往上找。

function Person () {};
let person = new Person();
person instanceof Person; // true
person instanceof Object; // true person.__proto__ === Person.prototype; // true
Person.prototype.__proto__ === Object.prototype; // true

由上面可以看出,如果用 instanceof 是无法准确的判断出实例对象是否直接继承自该构造函数。此时需要使用 constructor。

person.__proto__.constructor === Person; // true
person.__proto__.constructor === Object; // false

小结:
Object.prototype.__proto__ === null;
Array.__proto__ === Function.prototype;
Object.__proto__ === Function.prototype;
Function.prototype.__proto__ === Object.prototype;
Function.__proto__ === Function.prototype;
instanceof 不能准确的判断实例是否直接继承于构造函数

继承

1.原型链法

基本原理是:将父类的实例赋值给子类的原型。

Son.prototype = new Father()

// 父类
function Staff() {
this.company = 'tianchuang';
this.list = [];
}
// 父类的原型
Staff.prototype.getComName = function() {
return this.company;
}; // 子类
function Coder(name, skill) {
this.name = name;
this.skill = skill;
} // 继承 Staff
Coder.prototype = new Staff(); // 因为子类原型的指向已经变了,所以需要把子类原型的contructor指向子类本身
Coder.prototype.constructor = Coder; // 给子类原型添加属性
Coder.prototype.getInfo = function() {
return {
name: this.name,
skill: this.skill
};
}; let coder = new Coder('小明', 'javascript'); coder.getInfo(); // {name: '小明', skill: 'javascript'}
coder.getComName(); // 'tianchuang'

这种继承方式的缺点:

子类的实例可以访问父类的私有属性,子类的实例还可以更改该属性,这样不安全。

let coder1 = new Coder('zhangsan', 'python');
let coder2 = new Coder('liutian', 'java');
coder1.list; // []
coder1.list.push(1); //[1]
coder2.list // [1]

2. 借用构造函数

原理:在子类构造函数中,使用call来将子类的this绑定到父类中去

// 父类
function Staff() {
this.company = 'tianchuang';
this.list = [];
}
// 父类的原型
Staff.prototype.getComName = function() {
return this.company;
}; // 子类
function Coder(name, skill) {
Staff.call(this);
this.name = name;
this.skill = skill;
} let coder = new Coder('xiaoming', 'java');
let coder2 = new Coder('zhaosan', 'c'); coder.getComName(); // Uncaught TypeError: coder.getComName is not a function
coder.list; //[]
coder.list.push(1); //[1]
coder2.list; //[]

优点:

借用构造函数法可以解决原型中引用类型值被修改的问题;

缺点:

只能继承父对象的实例属性和方法,不能继承父对象原型属性和方法

组合继承

将原型继承和借用构造函数两种方式组合起来

// 父类
function Staff() {
this.company = 'tianchuang';
this.list = [];
}
// 父类的原型
Staff.prototype.getComName = function() {
return this.company;
}; // 子类
function Coder(name, skill) {
Staff.call(this); // 第一次调用
this.name = name;
this.skill = skill;
} Coder.prototype = new Staff();// 第二次调用 Coder.prototype.constructor = Coder; let coder = new Coder('xiaoming', 'java');
let coder2 = new Coder('zhaosan', 'c'); coder.getComName();
coder.list; //[]
coder.list.push(1); //[1]
coder2.list; //[]

优点:

可以保证每个函数有自己的属性,可以解决原型中引用类型值被修改的问题;
子类的实例可以继承父类原型上面的属性和方法

缺点:

在实例化子类的过程中,父类构造函数调用了两次

寄生组合式继承(推荐)
所谓寄生继承:通过 Object.create() 将子类的原型继承到父类的原型上

// 父类
function Staff() {
this.company = 'tianchuang';
this.list = [];
}
// 父类的原型
Staff.prototype.getComName = function() {
return this.company;
}; // 子类
function Coder(name, skill) {
Staff.call(this);
this.name = name;
this.skill = skill;
} Coder.prototype = Object.create(Staff.prototype); Coder.prototype.constructor = Coder; let coder = new Coder('xiaoming', 'java');
let coder2 = new Coder('zhaosan', 'c'); coder.getComName(); // 'tianchuang'
coder.list; //[]
coder.list.push(1); //[1]
coder2.list; //[]

《JavaScript 高级程序设计》中对寄生组合式继承的夸赞是:

这种方式的高效率体现在纸雕用了一次父类构造函数,并且因此避免了在父类的prototype 上面创建不必要的、多余的属性。
同时,原型链还能保持不变,可以正常使用 instanceof 和 isPrototypeOf 。
开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

class extend 继承
ES6 中有了类的概念,可以通过 class 声明一个类,通过 extends 关键字来实现继承关系。

class 与 ES5 构造函数的主要区别:

class 只能通过 new 来调用,而构造函数则可以直接调用;
class 内部所有定义的方法,都是不可枚举的(non-enumerable)

class Parent {
constructor(name) {
this.name = name;
}
static say() {
return 'hello';
}
} class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类的 constructor(name)
this.age = age;
}
} var child1 = new Child('kevin', '18'); console.log(child1);

值得注意的是:
super 关键字表示父类的构造函数,相当于 ES5 的 Parent.call(this)。
子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工,如果不调用 super 方法,子类就得不到 this 对象。
正是因为这个原因,在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。

子类的 __proto__ 属性指向父类
因此,子类可以继承父类的静态方法。

我们会发现,相比寄生组合式继承,ES6 的 class 多了一个 Object.setPrototypeOf(Child, Parent) 的步骤,即 Child.__proto__ = Parent。

最新文章

  1. 基于Oracle安装Zabbix
  2. liunx关闭防火墙
  3. sprint3
  4. mybatis入门基础(九)----逆向工程
  5. Asp.Net MVC+BootStrap+EF6.0实现简单的用户角色权限管理7
  6. Linq动态条件
  7. Json数据可视化
  8. MySQL安装(以程序的方式启动)zip版
  9. 转:Hprose for php(一)——快速入门
  10. android小知识之圆角ListView
  11. A - 小Y上学记——修学分
  12. AJAX扩展-POST传递参数并跳转页面
  13. 1021. Deepest Root (25) -并查集判树 -BFS求深度
  14. 通过日志来看Spring跨库更新操作的事务
  15. Letter Combinations of a Phone Number - LeetCode
  16. 泛型 Generic 类型擦除引起的问题及解决方法
  17. VMware克隆虚拟机后网络不能正常使用的解决方法
  18. centos 6.3 kickstart 装机卡在 unsupported hardware detected 页面
  19. js 时间毫秒
  20. 『NiFi 学习之路』资源 —— 资料汇总

热门文章

  1. angularJS 初始化
  2. 数据采集之js埋点
  3. 深入Java线程管理(一):线程的实现方式
  4. springmvc 过滤器和拦截器
  5. Vue 路由的嵌套使用
  6. Koa2 遇到Method Not Allowed 获取不到返回值
  7. spring boot + thymeleaf 乱码问题
  8. v-for(:key)绑定index、id、key的区别
  9. js基础——对象和数组
  10. SELECT command denied to user ''@'%' for column 'xxx_id' in table 'users_xxx' 权限问题