前言
好的,又是我,嗯,文章的搬运工,最近因为立了一个flag,说要一星期内再次看完《JavaScript高级程序设计》,然后...两星期吧,再然后,emmmmm,三个星期吧,这不,再看一次,对原型,原型链,继承的实现等,又刷新了一次“三观”,再结合他人的文章,“强强联手”的情况下,当一次搬运工
我们先来说下原型与原型链
原型与原型链
构造函数 (constructor,内置的默认属性)
1 : 例子
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
alert(this.name)
}
}
var person1 = new Person('PDK', 28, 'Web Engineer');
var person2 = new Person('彭道宽', 23, '前端工程师');
// 上面的例子中 person1 和 person2 都是Person的实例
console.log(person1.constructor == Person) // true
console.log(person2.constructor == Person) // true
// 直接打印person1和person2对象,就会发现并没有发现有constructor属性
// 那为什么person1.constructor == Person 这个会是true,实际是因为在person1中没有找到constructor属性
// 顺着__proto__往上,找到了Person.prototype,而在这里才找到的constructor,而这个constructor是指向Person的,所以结果才会是true,但是这并不能说是实例上有一个constructor属性
复制代码
记住 :
-
person1 和 person2 都是 构造函数 Person 的实例
-
实例的构造函数属性都指向构造函数
原型对象
每定义一个对象时候,对象中都会包含一些预定义的属性,每个函数对象都会有一个prototype属性,这个属性指向函数的原型对象。
function Person() {}
Person.prototype.name = '彭道宽'
Person.prototype.age = 28
Person.prototype.job = 'Web Engineer'
Person.prototype.sayName = function() {
alert(this.name)
}
var person1 = new Person()
person1.sayName() // '彭道宽'
var person2 = new Person()
person2.sayName() // '彭道宽'
console.log(person1.sayName == person2.sayName); //true
复制代码
- 每一个对象都有__proto__属性, 但是只有函数对象才有 prototype 属性
什么是函数对象 ?
凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象,下面例子都是函数对象。。。
var f1 = function () {
name : '彭道宽',
age : 18
}
var f2 = new Function('彭道宽', 18)
复制代码
那什么是原型对象呢 ?
原型对象,顾名思义,它就是一个普通对象。原型对象就是 Person.prototype ,如果你还是害怕它,那就把它想想成一个字母 A: var A = Person.prototype
现在我们给A添加四个属性,name 、age 、 job 、 say,其实它还有一个默认的属性就是constructor。
这么说吧 : A 有一个默认的 constructor 属性,这个属性是一个指针,指向 Person。即:
Person.prototype.constructor = Person
复制代码
是不是和上边说的 ?
// 上边
person1.constructor == Person // true
// 这里
Person.prototype.constructor == Person // true
// 这里为什么person1会constructor属性?因为person1 是 Person的实例。实际上并没有constructor,在找的时候找不到constructor。
// 于是顺着__proto__往上找,由于person1是Person的实例,于是找到了Person.prototype,在这里找到了cosntructor,所以上边的公式才成立
// 注意
person1.constructor == Person.prototype.constructor // false
// person1.constructor 和 Person.prototype.constructor 是指针属性,只是同时指向 Person,并不是等于Person,所以是错误的
复制代码
那 Person.prototype 为什么有 constructor 属性??同理, Person.prototype(也就是A),也是Person 的实例。
// 第一步
let A = {}
// 第二步
A.__proto__ = Person.prototype
// 第三步
Person.call(A)
// 第四步
return A
// 原型对象(Person.prototype)是 构造函数(Person)的一个实例。
Person.prototype = A
复制代码
原型对象其实就是普通对象(但 Function.prototype 除外,它是函数对象,但它很特殊,他没有prototype属性(前面说到函数对象都有prototype属性))
function Person(){};
console.log(Person.prototype) //Person{}
console.log(typeof Person.prototype) //Object
console.log(typeof Function.prototype) // Function,这个特殊
console.log(typeof Object.prototype) // Object
console.log(typeof Function.prototype.prototype) //undefined
复制代码
proto
JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__ 的内置属性,用于指向创建它的构造函数的原型对象。
对象 person1 有一个 __proto__属性,创建它的构造函数是 Person,构造函数的原型对象是 Person.prototype ,所以:
person1.__proto__ == Person.prototype
复制代码
从这个图中可得到 :
Person.prototype.constructor = Person
person1.__proto__ == Person.prototype
person1.constructor == Person
复制代码
注意 : 这个连接存在于实例(person1)与构造函数(Person)的原型对象(Person.prototype)之间,而不是存在于实例(person1)与构造函数(Person)之间。
总结一下
// 概念
1 : Person 构造函数
2 : var person1 = new Person() , person1 是实例
3 : prototype是原型对象,只有Function Object(函数对象) 才存在
4 : __proto__是原型,每个对象都存在原型
5 : person1之所以有constructor属性,是因为它是Person的实例,它是new出来的对象,person1 的 constructor指向Person
// 公式
1 : Person.prototype.constructor == Person
2 : person1.constructor == Person
3 : person1.__proto__ == Person.prototype
复制代码
原型链
// 题目
1 : person1.__proto__ 是什么?
2 : Person.__proto__ 是什么?
3 : Person.prototype.__proto__ 是什么?
4 : Object.__proto__ 是什么?
5 : Object.prototype.__proto__ 是什么?
// 答案
1 : person1.__proto__ === Person.prototype (person1的构造函数Person)
2 : Person.__proto__ === Function.prototpye (Person的构造函数Function)
3 : Person.protyotype是一个普通对象,因为一个普通对象的构造函数都是Object
所以 Person.prototype.__proto__ === Object.prototype
4 : Object.__proto__ === Function.prototpye (Object的构造函数Function)
5 : Object.prototype 也有__proto__属性,但是它比较特殊,是null,null处于原型链的顶端。所以 : Object.prototype.__proto__ === null
复制代码
注意 :
- 原型链的形成是真正是靠__proto__ 而非prototype
自己写一下?
function Person() {
}
var p1 = new Person()
// 总结公式
1 : p1.constructor = Person
2 : Person.prototype.constructor = Person
3 : p1.__proto__ = Person.prototype
4 : Person.__proto__ = Function.prototype
5 : Person.constructor = Function
6 : Person.prototype.__proto__ = Object.prototype
7 : Object.__proto__ = Function.prototype
// Object 是函对象,是通new Function()创建的,所以Object.__proto__指向Function.prototype
8 :Function.prototype.__proto__ = Object.prototype
9 : Object.prototype.__proto__ = null
复制代码
我们再来说说面向对象与继承
面向对象
如何声明一个类 ?
ES5中,还没有类的概念,而是通过函数来声明,到了ES6,有了class关键词,则通过class来声明
// ES5
var Animal = function () {
this.name = 'Animal'
}
// ES6
class Animal {
constructor () {
this.name = 'Animal'
}
}
复制代码
如何创建对象 ?
-
字面量对象
-
显示的构造函数
-
Object.create
// 第一种方式: 字面量
var obj1 = {
name: '彭道宽'
}
var obj2 = new Object({
name: '彭道宽'
})
// 第二种方式: 构造函数
var Parent = function () {
this.name = name
}
var child = new Parent('彭道宽')
// 第三种方式: Object.create
var Parent = {
name: '彭道宽'
}
var obj4 = Object.create(Parent)
复制代码
ES6 的 Class
基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
// ES5
function Point (x, y) {
this.x = x
this.y = y
}
Point.prototype.toString = function () {
return `(${this.x}, ${this.y})` // (x, y)
}
var point = new Point(1, 2)
point.toString() // (1, 2)
// ES6 中利用 class 定义类
class Point {
constructor (x, y) {
this.x = x
this.y = y
}
toString () {
return `(${this.x}, ${this.y})`
}
}
var point = new Point(1, 2)
point.toString() // (1, 2)
复制代码
上面代码定义了一个“类”,可以看到里面有一个 constructor
方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5 的构造函数Point,对应 ES6 的Point类的构造方法。
class Point {
// ...
}
console.log(typeof Point) // 'function'
console.log(Point === Point.prototype.constructor) // true
复制代码
类的数据类型就是函数,类本身就指向构造函数。构造函数的 prototype
属性,在 ES6 的 “类” 上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。
class Point {
constructor () {
// ...
}
toString () {
// ...
}
toValue () {
// ...
}
}
// 等同于下边的代码
Point.prototype.constructor = function () { }
Point.prototype.toString = function () { }
Point.prototype.toValue = function () { }
复制代码
在类的实例上面调用方法,其实就是调用原型上的方法。
class Point {}
var point = new Point ()
console.log(point.constructor === Point.prototype.constructor) // true
复制代码
更多 Class 的理解,请看阮一峰老师的 ECMAScript 6 入门
继承的实现
下边就列举常用的几种继承方式,搞懂这几种,应该可以混过面试了,记住: 继承的本质就是原型链
-
原型链继承
-
借用构造函数继承
-
组合继承
-
原型式继承
-
寄生式继承
-
寄生组合式继承
-
ES6 的 Class 继承
原型链继承
利用原型,让一个引用类型继承另一个引用类型的属性和方法;
function Parent () {
this.property = true
}
Parent.prototype.getValue = function () {
return this.property
}
function Child () {
this.childProperty = false
}
Child.prototype = new Parent() // 将父类的实例赋给子类的prototype
Child.prototype.getChildValue = function () {
return this.childProperty
}
var ch1 = new Child()
console.log(ch1.getChildValue()) // false
console.log(ch1.getValue()) // true
复制代码
继承是通过创建 Parent 的实例,并将该实例赋给 Child.prototype 实现的。实现的本质是 重写原型对象
,代之以一个新类型的实例。换句换说,原来存在于 Parent 的实例中的所有属性和方法,现在也存在 Child.prototype 中了。最终结果是: ch1 指向 Child 的原型, Child 的原型指向 Parent 的原型, getValue() 方法仍在 Parent.prototype 上,而 property 位于 Child.property 中,这是因为: property 是一个实例属性,而 getValue() 是一个原型方法
ch1.constructor
现在不是指向 Child ,而是指向 Parent ,这是因为 Child .prototype 被重写的缘故。实际上,不是 Child 的原型的 constructor 属性被重写,而是 Child 的原型指向了另一个对象——Parent 的原型,而这个原型对象的 constructor 属性指向的是 Parent
// 可以这么理解
// 正常情况下
Child.prototype.constructor = Child
ch1.contructor = Child
// 但是现在 Child.prototype = new Parent() 将父类的实例赋给子类的prototype之后
Child.prototype= new Parent()
(new Parent()).contructor = Parent
Child.prototype.contructor = Parent
ch1.constructor = Parent
复制代码
那么原型链继承的问题有哪些呢?
原型链中的原型对象是共用的,子类无法通过父类创建私有属性, 比如你 new 两个子类 child1 和 child2 的时候,你改 child1 的属性,child2 也会跟着改变,比如下边的代码
function Parent () {
this.colors = ['red', 'yellow']
}
function Child () {
}
// 子类继承父类
Child.prototype = new Parent()
var ch1 = new Child()
ch1.colors.push('black')
console.log(ch1.colors) // ['red', 'yellow', 'black']
var ch2 = new Child()
console.log(ch2.colors) // ['red', 'yellow', 'black']
复制代码
你看,这就出问题了吧,因为在 Parent 构造函数中定义了一个 colors 属性,当通过原型链
继承了之后,Child.prototype 就变成了 Parent 的一个实例,因此它也拥有了一个它自己的 colors 属性——就跟专门创建了一个 Child.prototype.colors 一样,那么所有 Child 的实例都会共享这个colors属性,而 ch1 和 ch2 都是 Child 的实例,对 ch1.colors 的修改,在 ch2.colors 中也会反映出来
借用构造函数
为了解决上边 原型链继承 存在的问题,现在使用构造函数去继承,在子类的构造函数里执行父类的构造函数, 主要通过 call / apply
去改变 this
的指向,从而导致父类构造函数执行时的这些属性都会挂载到子类实例上去
function Parent () {
this.colors = ['red', 'yellow']
}
function Child () {
// 子类继承了父类
Parent.call(this)
}
var ch1 = new Child()
ch1.colors.push('black')
console.log(ch1.colors) // ['red', 'yellow', 'black']
var ch2 = new Child()
console.log(ch2.colors) // ['red', 'yellow']
复制代码
组合继承
将原型链和借用构造函数的技术组合在一起。背后的思路是: 使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数的服用,又能够保证每个实例都有它的属性
function Parent () {
this.name = '彭道宽' // 这叫实例属性
}
Parent.prototype.getName = function () { } // 这叫做原型属性
复制代码
function Parent (name) {
this.name = name
this.colors = ['red', 'yellow']
}
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.constructor = Child // 注意, 如果没有说明,那么Child.prototype.constructor 就会是指向 Parent
Child.prototype.sayAge = function () {
console.log(this.age)
}
var ch1 = new Child('彭道宽', 21)
ch1.colors.push('black')
ch1.sayName() // 彭道宽
ch1.sayAge() // 21
console.log(ch1.colors) // ['red', 'yellow', 'black']
var ch2 = new Child('PDK', 18)
ch1.sayName() // PDK
ch1.sayAge() // 18
console.log(ch1.colors) // ['red', 'yellow']
复制代码
原型式继承
ECMAScript5 新增Object.create()方法规范了原型式继承,这个方法接收两个参数 : 一个用作新对象原型的对象和一个为新对象定义额外属性的对象。
var Parent = {
name: 'PDK',
friends: ['a', 'b', 'c']
}
var ch1 = Object.create(Parent)
ch1.name = 'OB-1'
ch1.friends.push('d')
var ch2 = Object.create(Parent)
ch2.name = 'OB-2'
ch2.friends.push('e')
console.log(Parent.friends) // ['a', 'b', 'c', 'd', 'e']
console.log(Parent.name) // PDK
复制代码
Object.create()方法的第二个参数与 Object.defineProperties()
方法的第二个参数格式相同,每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。
寄生式继承
思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
function Parent(origin) {
var clone = Object.create(origin) // 通过调用函数来创建一个对象
clone.sayHi = function () {
console.log('hi')
}
return clone // 返回这个对象
}
var child = {
name: 'pdk'
}
var resClone = Parent(child)
resClone.sayHi() // "hi"
复制代码
寄生组合式继承
所谓的寄生组合式继承,就是通过借用构造函数
来继承属性,通过原型链
的混用来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后将结果指定给自类型的原型。跟组合式继承的区别在于,他不需要在一次实例中调用两次父类的构造函数。基本模式如下:
function inheritPrototype(Child, Parent) {
var prototype = Object.create(Parent.prototype) // 创建对象
prototype.constructor = Child // 增强对象
Child.prototype = prototype // 指定对象
}
复制代码
function Parent (name) {
this.name = name
this.colors = ['red', 'yellow']
}
Parent.prototype.sayName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name)
this.age = age
}
inheritPrototype(Child, Parent)
Child.prototype.sayAge = function () {
console.log(this.age)
}
var ch1 = new Child('彭道宽', 21)
console.log(ch1.sayage) // 21
复制代码
ES6 的 Class 继承
class Parent {
constructor (name) {
this.name = name
}
doing () {
console.log('parent doing something')
}
getName () {
console.log('parent name: ', this.name)
}
}
class Child extends Parent {
constructor (name, parentName) {
super(parentName)
this.name = name
}
sayName () {
console.log('child name: ', this.name)
}
}
var ch1 = new Child('son', 'father')
ch1.sayName() // child name: son
ch1.getName() // parent name: son
ch1.doing() // parent doing something
var parent = new Parent('father')
parent.getName() // parent name: father
复制代码
class 实现原理
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
在子类的构造函数中,只有调用 super
之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。
class parent { }
class Child extends Parent {
constructor () {
super()
}
}
复制代码
注意,super
虽然代表了父类 Parent 的构造函数,但是返回的是子类 Child 的实例,即 super 内部的 this
指的是 Child,因此 super()
在这里相当于Parent.prototype.constructor.call(this)。
class parent { }
class Child extends Parent { }
Child.__proto__ === Parent // 继承属性
Child.prototype.__proto__ === Parent.prototype // 继承方法
复制代码
extends实现原理
//原型连接
Child.prototype = Object.create(Parent.prototype)
// B继承A的静态属性
Object.setPrototypeOf(Child, Parent)
//绑定this
Parent.call(this)
复制代码
最后来两个思考题
function SuperType() {
this.colors = ['red', 'yellow']
}
function SubType() {
}
// 继承了SuperType
SubType.prototype = new SuperType()
var instance1 = new SubType() // intance.constructor = SuperType
instance1.colors.push('black')
console.log(instance1.colors) // ['red', 'yellow', 'black']
var instance2 = new SubType()
console.log(instance2.colors) // ['red', 'yellow', 'black']
// 这里多出几道题,理解一下原型和原型链
console.log(instance1.constructor)
console.log(SubType.prototype.constructor)
console.log(SubType.prototype.__proto__ == SuperType.prototype)
console.log(instance1.__proto__ == SubType.prototype)
console.log(SubType.__proto__ == SuperType.prototype)
console.log(SubType.__proto__ == Function.prototype)
console.log(SuperType.prototype.constructor == SuperType)
console.log(SuperType.__proto__ == Function.prototype)
console.log(SuperType.prototype.__proto__ == Object.prototype)
复制代码
function SuperType() {
this.colors = ['red', 'yellow']
}
function SubType() {
// 继承了SuperType
SuperType.call(this)
}
var instance1 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors) // ['red', 'yellow', 'black']
var instance2 = new SubType()
console.log(instance2.colors) // ['red', 'yellow']
// 思考一哈?
console.log(instance1.constructor)
console.log(SubType.prototype.constructor)
console.log(SubType.prototype.__proto__)
console.log(SubType.prototype.__proto__ == SuperType.prototype)
console.log(SubType.prototype.__proto__ == Object.prototype)
console.log(instance1.__proto__ == SubType.prototype)
console.log(SubType.__proto__ == SuperType.prototype)
console.log(SubType.__proto__ == Function.prototype)
console.log(SuperType.prototype.constructor == SuperType)
console.log(SuperType.__proto__ == Function.prototype)
console.log(SuperType.prototype.__proto__ == Object.prototype)
复制代码
相关链接
最详尽的 JS 原型与原型链终极详解: www.jianshu.com/p/dee9f8b14…
ES6 入门 - Class 继承: es6.ruanyifeng.com/#docs/class…
立下的flag: github.com/PDKSophia/r…