学习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.getPrototypeOf,Object.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。

原型,原型链,给予原型和class的继承的更多相关文章

  1. JS原型、原型链、构造函数、实例与继承

    https://cloud.tencent.com/developer/article/1408283 https://cloud.tencent.com/developer/article/1195 ...

  2. js中的原型对象链

    由于原型对象也是一个对象,它也有自己的原型对象并继承对象中的属性,这就是原型对象链:对象继承其原型对象,而原型对象继承它的原型对象,以此类推. 我们创建的每一个函数都有一个prototype(原型)属 ...

  3. JavaScript原型&原型链

    原型&原型对象 先来一段简单的代码: function Fun(name) { this.name = name } var obj = new Fun('obj') JavaScript中的 ...

  4. JavaScript ES5类 原型 原型链 组合、原型、寄生式继承

    ES5类 原型  原型链 继承 JavaScript中,原型是相对于构造函数(类)的叫法(或者说概念),原型链是相对于构造函数(类)的实例对象的叫法. 对于JavaScript对象,如果在对象自身上找 ...

  5. 原型和JS内置对象

    原型 1.定义 每一个对象都有原型 原型仍然是一个对象 模拟实现面向对象的继承性 2.原型链 对象的原型还有原型 对象除了可以使用自有属性还可以继承原型上的属性 3.获取原型 对象.__proto__ ...

  6. js中的原型,原型链和继承

    在传统的基于Class的语言如Java.C++中,继承的本质是扩展一个已有的Class,并生成新的Subclass. 由于这类语言严格区分类和实例,继承实际上是类型的扩展.但是,JavaScript最 ...

  7. JS原型--原型链

    构造函数-->原型--->prototype-->__proto__-->constructor-->原型链 构造函数    什么是构造函数?我理解构造函数就是可以用来生 ...

  8. js的原型/原型链/构造函数

    js里一切皆对象.有js自己内部的对象,还有用户自定义的对象.所有的对象都是从原型上衍生出来的. 原型本身也是对象,原型链的最高层就是Object. 两个重要的属性:prototype, __prot ...

  9. js 原型原型链

    个人的理解,有什么不正确的请指教,共同学习 //声明一个构造函数Personfunction Person(name,age){ this.name = name; this.age = age;} ...

随机推荐

  1. ccf-201403-3有趣的命令行

    傻逼题,要是考试只能得0分.. 提供几组傻逼数据,这要是在真实的生活中一定是错的... 还是要好好读题吧,全凭自己的感觉就得0分 albw:x 4 ls -a docu Case 1: -a ls - ...

  2. git比较两个版本之间的区别

    查看当前没有add 的内容修改: git diff 查看已经add 没有commit 的改动 git diff --cached 查看当前没有add和commit的改动: git diff HEAD ...

  3. js如何获取地址栏上的参数id

    直接上js代码   方法1: 父页面跳转地址栏带id>>>>window.location.href='${ctxAdmin}/study/checkForm?id='+id+ ...

  4. H3C 会话层、表示层和应用层

  5. C#循环语句练习(二)

    1.求1!+2!+...+n! 所有阶乘的和 2.求100以内的质数 (1)第一种做法 (2)另一种做法 3. 总数=幼兔+小兔+成兔成兔=上个月的小兔+上个月的成兔小兔=上个月的幼兔幼兔=这个月的成 ...

  6. Python--day72--ajax简介

    ajax的基本结构: <script> $("#b1").on("click", function () { $.ajax({ url: " ...

  7. 2019-8-31-jekyll-在博客添加流程图

    title author date CreateTime categories jekyll 在博客添加流程图 lindexi 2019-08-31 16:55:59 +0800 2018-2-13 ...

  8. 在 CentOS 7.3 上安装 nginx 服务为例,说明在 Linux 实例中如何检查 TCP 80 端口是否正常工作

    CentOS 7.3 这部分以在 CentOS 7.3 上安装 nginx 服务为例,说明在 Linux 实例中如何检查 TCP 80 端口是否正常工作. 登录 ECS 管理控制台,确认实例所在安全组 ...

  9. Flex AIR使用ADT命令打包 ipa

    1. 配置环境变量. 2. 测试adt命令 3. 将ShepherdPhone0815.mobileprovision和 my.p12文件都放入编译好的工程目录下,如下图: 4.切换到上述编译好的目录 ...

  10. java多异常处理

    声明异常时尽可能声明具体异常类型,方便更好的处理; 方法声明几个异常就对应有几个catch块; 若多个catch块中的异常出现继承关系,父类异常catch块放在最后; 在catch语句块使用Excep ...