原型是個玄學,學了好久的js都沒有搞清楚究竟是個啥,老是隻知其一;不知其二,今天系統的總結下(按我的理解),歡迎批評指正。
說到原型,那麼咱們搞清楚這些名詞先:構造函數、原型、實例、__proto__、new操做等。html
// 構造函數
function Foo(name) {
// 私有屬性
var age = 1
// 公有屬性
this.name = name
}
// 原型上的屬性
Foo.prototype.getName = function() {
return this.name
}
// 靜態屬性
Foo.id = 123
// 實例foo
var foo = new Foo('Tom')
foo.name // Tom
foo.age // undefined
foo.getName() // Tom
Foo.id // 123
複製代碼
問:爲何foo能訪問到name、getName, 訪問不到age?
答:es6
// 模擬 new 操做
function myNew(Foo){
var obj = {}
// 解釋爲何能訪問 getName
obj.__proto__ = Foo.prototype
// 將構造函數裏的公有屬性,強制綁定到obj, 解釋問什麼能訪問 name
Foo.call(obj)
return obj
}
複製代碼
其基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。設計模式
實例 foo 能訪問到構造函數 Foo 裏的公有屬性和原型上的屬性, 實例 parent 能訪問到構造函數 Parent 裏的公有屬性和原型上的屬性, 將構造函數Foo的原型指向實例parent,則 foo 也能夠訪問 parent 所能訪問的內容, 從而實現繼承。bash
function Parent(){
this.aaa = 'aaa'
this.books = ['1', '2']
}
Parent.prototype.getAAA = function(){
return this.aaa
}
// 這個時候會覆蓋以前的 Foo.prototype.getName, 解決:子類原型上自定義的方法後移
Foo.prototype = new Parent()
Foo.prototype.getName = function() {
return this.name
}
var foo = new Foo('Tom')
foo.getAAA() // 'aaa'
複製代碼
原型關係:app
缺點:1. 父類Parent裏的公有引用數據類型屬性,會互相影響;2. 沒法向父類傳參。var foo1 = new Foo('Tom')
var foo2 = new Foo('Tom1')
foo1.books.push('3')
foo2.books // ['1','2','3']
複製代碼
解決父類Parent裏的公有引用數據類型屬性,會互相影響
缺點:沒法訪問Parent.prototype上的內容。函數
function Parent(name){
this.books = ['1','2']
}
function Foo(name) {
Parent.call(this,name)
}
var foo1 = new Foo('Tom1')
var foo2 = new Foo('Tom2')
foo1.books.push('3')
foo2.books // ['1','2']
複製代碼
Parent裏的公有引用數據類型屬性互不影響,也可訪問Parent.prototype上的內容。
缺點:要調用兩次父類構造函數,而且books會存在於foo和Foo.prototype上性能
function Parent(){
this.books = ['1','2']
}
function Foo() {
Parent.call(this) // 第二次調用 new Foo() 時。
}
// 子類的原型指向父類的實例
Foo.prototype = new Parent() // 第一次調用。
var foo = new Foo()
複製代碼
原型關係: ui
藉助第三方構造函數F實現繼承, 本質上是經過__proto__牽橋搭線。this
function inherit(o) {
function F(){}
F.prototype = o
return new F()
}
obj = {
age: 10
}
me = inhreit(obj) // me.__proto__指向F.prototype也就是o
// 等同於
const me = Object.create(obj);
// Object.create原理
const me = Object.create(obj); ===> me.__proto__ = obj
複製代碼
經過借用構造函數來繼承屬性(call),經過原型鏈來繼承方法。相比較組合繼承,則其基本思路是:沒必要爲了指定子類的原型而調用父類的構造函數(避免調用兩次父類構造函數),而是將父類原型的副本放到子類原型上。es5
function inherit(o) {
function F(){}
F.prototype = o
return new F()
}
function inheritPrototype(Child, Parent) {
var p = inherit(Parent.prototype) // p.__proto__ = Parent.prototype
p.constructor = Child
Child.prototype = p
}
function Parent(age){
this.age = age
}
function Child(name, age) {
this.name = name
Parent.call(this, age)
}
inheritPrototype(Child, Parent)
// 爲避免被覆蓋,定義子類原型上的方法,要寫在 inheritPrototype 以後
Child.prototype.getAge = function() {
return this.age
}
var child = new Child('Tom', 12)
child.getAge() // 12
複製代碼
以上全部繼承方式,原型上的引用數據類型被更改時會互相影響。 使用約定 --- 通常原型上只用來存方法,而不存數據,來規避。
// es6
class Point {
// 靜態屬性
static id = 1
// 私有屬性,約定用_加以區分,但實例仍然能夠訪問
_count = 1
// 原型上的方法
constructor() {
// 公有屬性
this.x = 1
}
toString() {
// ...
}
toValue() {
// ...
}
}
const point = new Point()
point.hasOwnProperty('x') // true
point.hasOwnProperty('_count') // true
// class裏的方法 等同於
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
// es5 的方法
function Point() {
this.toValue = function() {}
}
// toValue方法在實例 point 上,而不是 Point.prototype 上。
var point = new Point()
複製代碼
相比於es5, es6多作了這步操做Child.__proto__ = Parent(子類的__proto__指向父類)。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
Bar.__proto__ === Foo // true
複製代碼
A.prototype.constructor.call(this)
(A是父類),ES5 的繼承,實質是先創造子類的實例對象this(肯定this指向),而後再將父類的方法添加到this上面(Parent.apply(this))。ES6 的繼承機制徹底不一樣,實質是先將父類實例對象的屬性和方法,加到this上面(因此必須先調用super方法),而後再用子類的構造函數修改this(肯定this指向)。
class A {
}
class B extends A {
}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
// 等同於
class A {
}
class B {
}
// B 的實例繼承 A 的實例
Object.setPrototypeOf(B.prototype, A.prototype);
// 等同於
B.prototype = Object.create(A.prototype)
// B 繼承 A 的靜態屬性
Object.setPrototypeOf(B, A);
// 等同於
B = Object.create(A)
const b = new B();
// ------------------------------------------------------
// setPrototypeOf 原理
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
複製代碼