Repository navigation
js原型与继承 #2
Description
Activity
Js原型机制与继承
众所周知, JavaScript不是OOP(面向对象编程),但是JavaScript和面向对象语言又有一些相似之处.面向对象编程的三个基本特征:封装,继承,多态,
- 封装: 面向对象的语言就是抽象出类,Js也可以抽象出构造函数
- 继承: 面向对象的语言通过类的继承机制实现继承,Js是通过原型链实现继承
- 多态: Js支持多态的覆盖,不支持重载(函数同名,参数列表不同)
ES6更是直接支持了Class以及extends语法糖
原型机制
原型机制是Js的一大特色,原型机制提供了创建对象的更加简单的模式(克隆模式:找到一个对象复制一个一模一样的).JavaScript实现的这个克隆方法就是Object.create().
原型模式有以下的一些规则:- 所有的数据都是对象(细节查看Js一切皆对象)
- 原型模式创建对象的方式是通过找到一个对象作为原型去克隆而不是去实例化
- 自身无法处理的事情会交给自身的原型去处理(这是一个递归的过程)
原型
原型也是一个对象,通过这个对象实现属性方法的继承.我们可以看看prototype和__proto__这两个具体的概念.
- __proto__是chrome,firefox等浏览器内置的对象属性,这个属性指向的就是这个对象的原型.
- prototype首先声明这个属性只有函数才有,这个指向的就是函数的原型.
所以个人觉得,原型的概念应该是基于函数(构造函数)来谈的,而不是实例化的对象.总结一下上面的__proto__和prototype的关系应该是: 构造函数的prototype默认将是所有这个构造函数实例化出的所有对象的原型.
functrion A () {} var a = new A() A.prototype === a.__proto__
补充说明
Object.prototype.__proto__ !== Object.prototype.constructor.prototype
A.prototype === a.__proto__ // 结果为true的条件(a = new A())
a.constructor.prototype === a.__proto__ // 结果为true的条件(a = new A() && a.constructor === A)
获取原型
现代浏览器都支持了Object.getPrototypeOf(o)方法,这样皆可以获取到这个对象的原型了.
函数执行过程:- 接受的参数会被隐式的转换成object类型(转换规则)
- 转换后的参数如果不正常就中断执行退出
- 返回转换后的参数的原型
原型链
每个对象都有他的原型,然后原型又有原型,经历这样一个循环往复递归的过程(终止条件就是Object.prototype, Object.prototype的原型是null)就会形成一个链状的结构,这条链状的结构就是原型链.就是通过原型链实现的继承机制
继承
js主要是通过原型链来实现继承,但是在这个基础上,我们可以进行简单的改造来实现各种需求的不同继承方式.主要的类型有以下一些方式: 原型链 组合继承 原型继承 寄生式继承 寄生组合继承
原型链继承:
function Parent () { this.name = 'name' } Parent.prototype.sayName = function () { console.log(this.name) } function Child () {} Child.prototype = new Parent() var child = new Child() child.sayName()
上面的即一个最简单的原型链继承的例子,子类通过将原型指向父类的一个实例化来继承父类的属性和方法,但是这个方法最大的缺点就是:子类继承的都是一个引用,任何一个子类的实例化修改了父类的属性,将导致所有子类的实例化同步的改变了.
组合继承:
function Parent(name) { this.name = name } Parent.prototype.sayName = function () { console.log(this.name) } function Child(name, age) { Parent.call(this, name) this.age = age } Child.prototype = new Parent() child.prototype.sayAge = function () { console.log(this.age) } var child1 = new Child('sjf', 22) var child2 = new Child('sujunfei', 20) console.log(child1.name) child1.name = 'hello' console.log(child1.name) console.log(child2.name)
上面的就是组合模式:借用构造函数和原型链继承
- 通过借用构造函数把父类的属性copy一份到自身;
- 通过原型链实现共享方法.
这样就会避免原型链和借用构造函数单独的缺点了,但是这种模式的缺点就是父类和子类都会拥有相同的属性,就是说造成属性冗余(造成原因是将Child的prototype指向Parent的实例的时候又执行了一次)
寄生组合模式
function Parent (name) { this.name = name } Parent.prototype.sayName = function () { console.log(this.name) } function Child (name) { Parent.call(this, name) } function inherits (child, parent) { var prototype = parent.prototype prototype.constructor = child child.prototype = prototype } inherits(Child, Parent) var child1 = new Child('sjf') var child2 = new Child('sujunfei') console.dir(child1)
寄生组合就是在组合的基础上把继承分类的方法的方式要更改一下,将子类的prototype指向父类的实例改成copy一份父类的prototype,让后将子类的prototype指向这个copy的副本.这样就避免了多一次调用父类否早函数造成属性的冗余.

