摘要:众所周知,是一门面向对象的语言,如果说针对面向对象来发问的话,我会想到两个问题,在中,类与实例对象是如何创建的,类与实例对象又是如何实现继承的。但是在中是指向的,因为每一个构造函数其实都是这个对象构造的,中子类的指向父类可以实现属性的继承。
众所周知,Javascript是一门面向对象的语言,如果说针对面向对象来发问的话,我会想到两个问题,在js中,类与实例对象是如何创建的,类与实例对象又是如何实现继承的。
面向对象 如何声明一个类ES5中,还没有类的概念,而是通过函数来声明;到了ES6,有了class关键字,则通过class来声明
// 类的声明 var Animal = function () { this.name = "Animal"; }; // es6中class的声明 class Animal2 { constructor () { this.name = "Animal2"; }如何创建对象
1.字面量对象
2.显示的构造函数
3.Object.create
// 第一种方式:字面量 var o1 = {name: "o1"}; var o2 = new Object({name: "o2"}); // 第二种方式:构造函数 var M = function (name) { this.name = name; }; var o3 = new M("o3"); // 第三种方式:Object.create var p = {name: "p"}; var o4 = Object.create(p);类与继承
如何实现继承?
继承的本质就是原型链
/** * 借助构造函数实现继承 */ function Parent1 () { this.name = "parent1"; } Parent1.prototype.say = function () { }; function Child1 () { Parent1.call(this); // 或Parent1.apply(this,arguments) this.type = "child1"; } console.log(new Child1(), new Child1().say());
重点是这句:Parent1.call(this); 在子类的构造函数里执行父类的构造函数,通过call/apply改变this指向,从而导致父类构造函数执行时的这些属性都会挂载到子类实例上去。
问题: 只能继承父类构造函数中声明的实例属性,并没有继承父类原型的属性和方法
/** * 借助原型链实现继承 */ function Parent2 () { this.name = "parent2"; this.play = [1, 2, 3]; } function Child2 () { this.type = "child2"; } Child2.prototype = new Parent2(); var s1 = new Child2(); var s2 = new Child2(); console.log(s1.play, s2.play); s1.play.push(4);
重点就是这句: Child2.prototype = new Parent2(); 就是说 new 一个父类的实例,然后赋给子类的原型 也就是说 new Child2().__proto__ === Child2.prototype === new Parent2()当我们在new Child2()中找不到属性/方法,顺着原型链就能找到new Parent2(),这样就实现了继承。
问题: 原型链中的原型对象是共用的,子类无法通过父类创建私有属性
比如当你new两个子类s1、s2的时候,改s1的属性,s2的属性也跟着改变
/** * 组合方式 */ function Parent3 () { this.name = "parent3"; this.play = [1, 2, 3]; } function Child3 () { Parent3.call(this); // 父类构造函数执行了 this.type = "child3"; } Child3.prototype = new Parent3(); // 父类构造函数执行了 var s3 = new Child3(); var s4 = new Child3(); s3.play.push(4); console.log(s3.play, s4.play);
组合式就是原型链+构造函数继承,解决了前两种方法的问题,但也有不足:子类实例化时,父类构造函数执行了两次,所以有了下面的组合继承的优化1
组合继承的优化1/** * 组合继承的优化1 * @type {String} */ function Parent4 () { this.name = "parent4"; this.play = [1, 2, 3]; } function Child4 () { Parent4.call(this); this.type = "child4"; } Child4.prototype = Parent4.prototype; var s5 = new Child4(); var s6 = new Child4(); console.log(s5, s6); console.log(s5 instanceof Child4, s5 instanceof Parent4); console.log(s5.constructor);
其实就是把原型链继承的那句 Child4.prototype = new Parent4(); 改为 Child4.prototype = Parent4.prototype; 这样虽然父类构造函数只执行了一次了,但又有了新的问题: 无法判断s5是Child4的实例还是Parent4的实例 因为Child4.prototype.constructor指向了Parent4的实例;如果直接加一句 Child4.prototype.constructor = Child4 也不行,这样Parent4.prototype.constructor也指向Child4,就无法区分父类实例了。
若要判断a是A的实例 用constructor组合继承的优化2(推荐)
a.__proto__.constructor === A
用instanceof则不准确, instanceof 判断 实例对象的__proto__ 是不是和 构造函数的prototype 是同一个引用。若A 继承 B, B 继承 C 在该原型链上的对象 用instanceof判断都返回ture
/** * 组合继承的优化2 */ function Parent5 () { this.name = "parent5"; this.play = [1, 2, 3]; } function Child5 () { Parent5.call(this); this.type = "child5"; } //注意此处,用到了Object.creat(obj)方法,该方法会对传入的obj对象进行浅拷贝 //这个方法作为一个桥梁,达到父类和子类的一个隔离 Child5.prototype = Object.create(Parent5.prototype); //修改构造函数指向 Child5.prototype.constructor = Child5
构造函数属性继承和建立子类和父类原型的链接
ES6实现继承引入了class、extends、super关键字,在子类构造函数里调用super()方法来调用父类的构造函数。
在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。
class Child6 extends Parent6 { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color + " " + super.toString(); // super代表父类原型,调用父类的toString() } }class实现原理
Class充当了ES5中构造函数在继承实现过程中的作用
有prototype属性,有__proto__属性,这个属性在ES6中的指向有一些主动的修改。
同时存在两条继承链:一条实现属性继承,一条实现方法继承。
class A extends B {} A.__proto__ === B; //继承属性 A.prototype.__proto__ === B.prototype; //继承方法
ES6的子类的__proto__是父类,子类的原型的__proto__是父类的原型。
但是在ES5中 A.__proto__是指向Function.prototype的,因为每一个构造函数其实都是Function这个对象构造的,ES6中子类的__proto__指向父类可以实现属性的继承。
只有函数有prototype属性,只有对象有__proto__属性 ;但函数也有__proto__属性,因为函数也是一个对象,函数的__proto__等于 Function.prototype。extends实现原理
//原型连接 Man.prototype = Object.create(Person.prototype); // B继承A的静态属性 Object.setPrototypeOf(Man, Person); //绑定this Person.call(this);
前两句实现了原型链上的继承,最后一句实现构造函数上的继承。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/93997.html
摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...
摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...
摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...
摘要:是完全的面向对象语言,它们通过类的形式组织函数和变量,使之不能脱离对象存在。而在基于原型的面向对象方式中,对象则是依靠构造器利用原型构造出来的。 JavaScript 函数式脚本语言特性以及其看似随意的编写风格,导致长期以来人们对这一门语言的误解,即认为 JavaScript 不是一门面向对象的语言,或者只是部分具备一些面向对象的特征。本文将回归面向对象本意,从对语言感悟的角度阐述为什...
摘要:之前,本质上不能算是一门面向对象的编程语言,因为它对于封装继承多态这些面向对象语言的特点并没有在语言层面上提供原生的支持。所以在中出现了等关键字,解决了面向对象中出现了问题。 ES6之前,javascript本质上不能算是一门面向对象的编程语言,因为它对于封装、继承、多态这些面向对象语言的特点并没有在语言层面上提供原生的支持。但是,它引入了原型(prototype)的概念,可以让我们以...
摘要:除了以上介绍的几种对象创建方式,此外还有寄生构造函数模式稳妥构造函数模式。 showImg(https://segmentfault.com/img/remote/1460000018196128); 面向对象 是以 对象 为中心的编程思想,它的思维方式是构造。 面向对象 编程的三大特点:封装、继承、多态: 封装:属性方法的抽象 继承:一个类继承(复制)另一个类的属性/方法 多态:方...
阅读 2314·2021-11-16 11:52
阅读 2302·2021-11-11 16:55
阅读 726·2021-09-02 15:41
阅读 2927·2019-08-30 15:54
阅读 3129·2019-08-30 15:54
阅读 2221·2019-08-29 15:39
阅读 1491·2019-08-29 15:18
阅读 911·2019-08-29 13:00