Es6 中引入了 class
關鍵字,但只是語法糖, js 仍然是一門基於原型的語言。jquery
當談到繼承時,js 只有一種結果:對象。es6
對象是動態的屬性包。函數
每一個對象都有一個私有屬性(非標準屬性:__proto__
,應經過 Object.getPrototypeOf()
獲取) ,指向它的構造函數的原型對象 (prototype
)——「它的構造函數的原型對象」也有一個本身的原型對象 (__proto__
) ,以此類推直到一個對象的原型對象爲 null
。性能
null
沒有原型,null
是原型鏈中最後一環。this
好比:spa
function Child(){
this.name = 'xiaoyu'
}
const child = new Child()
child.__proto__ === Child.prototype // > true
// 等同於:
Object.getPrototypeOf(child) === Child.prototype // > true
複製代碼
child.__proto__.__proto__.__proto__ // > null
複製代碼
基於原型鏈的繼承,包括繼承屬性和繼承方法(函數),其中函數的繼承與屬性繼承沒有差異,任何函數均可以添加到對象上做爲對象的屬性。prototype
Es6 引入了新的關鍵字實現 class
,除了 class
以外,還有 constructor
, static
, extends
,super
。3d
class Person {
constructor({name = 'xiaoyu', age = 18, sex = 0}){
Object.assign(this, {
name, age, sex
})
}
}
class Child extends Person {
constructor(options = {}) {
super(options)
this.task = options.task
this.canTravelAlone = false
}
}
class Baby extends Child {
constructor(options = {}) {
super(options)
this.food = 'neinei'
}
}
const baby = new Baby({age: 1})
baby // > Baby {name: "xiaoyu", age: 1, sex: 0, task: undefined, canTravelAlone: false, food: "neinei」}
const child = new Child({task: ‘study’, age: 10})
child // > Child {name: "xiaoyu", age: 10, sex: 0, task:’study’}
複製代碼
Object.create()
實現類式繼承單繼承:code
function Parent() {
this.x = 0
this.y = 0
}
Parent.prototype.move = function (x, y) {
this.x += x
this.y += y
console.log('Parent moved')
}
function Child() {
Parent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Parent
console.log(Child.prototype)
/* >
constructor: ƒ Parent()
__proto__: Object
*/
var child = new Child()
console.log(child instanceof Parent) // > true
child.move(1,1) // > Parent moved
複製代碼
多類混入繼承:orm
function Parent () {
this.name = 'dayu'
}
function AnotherParent () {
this.nickName = 'peppa'
}
function Child () {
Parent.call(this)
AnotherParent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
// Object.assign 把 AnotherParent 原型上的函數 copy 到 Child 原型上
Object.assign(Child.prototype, AnotherParent.prototype)
Child.prototype.constructor = Child
Child.prototype.play = function () {
console.log('play')
}
var child = new Child()
console.log(child)
/* >
Child {name: "dayu", nickName: "peppa"}
name: "dayu"
nickName: "peppa"
__proto__: Parent
constructor: ƒ Child()
play: ƒ ()
__proto__: Object
constructor: ƒ Parent()
__proto__: Object
*/
複製代碼
優點:能夠經過 Object.create(null)
來建立一個沒有原型的對象 缺陷:Object.create()
第二個參數使用之後,因爲每一個對象的描述符屬性都有本身的描述對象,以對象的格式處理成百上千個對象描述的時候,可能會形成嚴重的性能問題。
能夠使用 new
建立實例,以及 Constructor.prototype
鏈接到這個實例造成原型連接
function Child(){
this.name = 'xiaoyu'
this.age = 18
}
var child = new Child()
Child.prototype.age = 10
Child.prototype.task = 'play'
// 自有屬性
console.log(child.name) // > xiaoyu
// 訪問不到原型上的 age ,屬性遮蔽
console.log(child.age) // > 18
// 順着原型鏈向上查找,找到了 task 屬性
console.log(child.task) // > play
console.log(Child.prototype)
/* >
age: 10
task: "play"
constructor: ƒ Child()
__proto__:
constructor: ƒ Object()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
hasOwnProperty: ƒ hasOwnProperty()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toString: ƒ toString()
valueOf: ƒ valueOf()
toLocaleString: ƒ toLocaleString()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
*/
console.log(child)
/* >
name: "xiaoyu"
age: 18
__proto__:
age: 10
task: "play"
constructor: ƒ Child()
__proto__:
constructor: ƒ Object()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
hasOwnProperty: ƒ hasOwnProperty()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toString: ƒ toString()
valueOf: ƒ valueOf()
toLocaleString: ƒ toLocaleString()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
複製代碼
能夠看出,child.__proto__ === Child.prototype
。
任何函數的 __proto__
都是(window.)Object.prototype
,原型鏈上的屬性查找終止於 Object.prototype.__proto__
(null
),找不到則返回 undefined
。
child —> Child.prototype —> Function.prototype —> Object.prototype —> null
缺陷:這種方法強制在每一個對象中生成類似的信息,可能會給生成對象帶來並不想要的方法。
經過複製源對象的屬性,一個對象直接繼承另外一個對象。js 中,源對象的屬性一般被稱做 mixins
,從 es6 開始,js 使用 Object.assign()
來實現這個過程,es6 以前,一般使用 lodash/underscore 的 .extend()
和 jquery 的 $.exntend()
實現。
const name = {name: 'xiaoyu'}
const age = {age: 18}
const sex = {sex: 0}
const task = {task: 'study'}
const canTravelAlone = {canTravelAlone: false}
const food = {food: 'normal'}
const months = {months: 8}
const Person = (options) => {
return Object.assign({}, name, age, sex, options)
}
const Child = (options) => {
return Object.assign({}, name, age, sex, task, canTravelAlone, options)
}
const Baby = (options) => {
return Object.assign({}, name, months, sex, food, options)
}
const baby = Baby({food: 'neinei'})
baby // > {name: "xiaoyu", months: 8, sex: 0, food: "neinei"}
const child = Child()
child // > {name: "xiaoyu", age: 18, sex: 0, task: "study", canTravelAlone: false}
複製代碼
能夠發現,對象組合可以確保對象按需繼承,這和類式繼承不一樣,當繼承一個類時,類裏全部的屬性都會被繼承。
Object.create()
實現拼接繼承能夠使用 Object.create()
實現原型連接,或者與拼接繼承混用。
var o = {
a: 2,
m: function(){
return this.a + 1
}
}
console.log(o.m()) // > 3
複製代碼
// 原型鏈:o —> Object.prototype —> null
使用字面量建立的對象繼承了 Object.prototype
上的全部屬性:
Object.prototype
/* >
constructor: ƒ Object()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
hasOwnProperty: ƒ hasOwnProperty()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toString: ƒ toString()
valueOf: ƒ valueOf()
toLocaleString: ƒ toLocaleString()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
*/
// p 繼承自 o ,p 也有自身屬性 a
var p = Object.create(o)
p.a = 4
console.log(p.m()) // > 5
p.__proto__.m() // > 3
複製代碼
原型鏈 p —> a —> Object.prototype —> null
Js 中,任何函數均可以建立對象。若是一個函數既不是構造函數也不是 class ,並且這個函數返回一個不是經過 new
建立的對象,這個函數就是一個工廠函數。
function createBook(params = {}) {
return {
title: '我是一本書',
// 多是一個帶有參數的工廠函數
author: params.author
}
}
複製代碼
經過工廠函數建立對象並經過直接賦予屬性使用拼接繼承,這就是函數繼承的原理。
function createEbook(params = {}) {
return {
…createBook(),
cover: ‘xxxx.jpg’
}
}
createEbook() // > {title: "我是一本書", author: undefined, cover: "xxxx.jpg"}
複製代碼
原型鏈上查找屬性比較耗時,性能要求苛刻的狀況下應該注意
備註: hasOwnProperty 是 js 中惟一一個處理屬性不會遍歷原型鏈的方法。
試圖訪問不存在的屬性會遍歷整個原型鏈