轉(zhuǎn)載自:https://juejin.im/post/5bf5580c51882511a852881f
詳解js原型懒豹,構(gòu)造函數(shù)以及class之間的原型關(guān)系
原型
概念
在構(gòu)造函數(shù)創(chuàng)建的時候,系統(tǒng)默認的幫構(gòu)造函數(shù)創(chuàng)建并關(guān)聯(lián)一個對象 這個對象就是原型
作用
在原型中的所有屬性和方法侦锯,都可以被和其關(guān)聯(lián)的構(gòu)造函數(shù)創(chuàng)建出來的所有的對象共享
訪問原型
構(gòu)造函數(shù)名.prototype 實例化的對象.__proto __
原型的簡單使用
- 利用對象的動態(tài)特性為原型對象增加成員
- 直接替換原型對象(jq核心方法的實現(xiàn) 就是使用原型替換的思想)
function Person (name) {
this.name = name
}
Person.prototype.fn = function () {
console.log('hello world')
console.log(this.name)
}
var p = new Person('小紅')
p.fn()
p.name = '曉麗'
p.fn()
console.log(Person.prototype)
console.log(p)
復(fù)制代碼
<figure style="display: block; margin: 22px auto; text-align: center;">[圖片上傳失敗...(image-170216-1543396429106)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
</figure>
<figure style="display: block; margin: 22px auto; text-align: center;">[圖片上傳失敗...(image-1a592d-1543396429106)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
</figure>
1. prototype
含義: 是一個函數(shù)的屬性,這個屬性是一個指針秦驯,指向一個對象 作用: 構(gòu)造函數(shù)調(diào)用 訪問該構(gòu)造函數(shù)所關(guān)聯(lián)的原型對象
2. proto
含義: 是一個對象擁有的內(nèi)置屬性尺碰,是js內(nèi)部使用尋找原型鏈的屬性,通過該屬性可以允許實例對象直接訪問到原型
3. constructor
含義:原型對象的constructor 指向其構(gòu)造函數(shù),如果替換了原型對象之后译隘,這個constructor屬性就不準確亲桥,需要手動補充一下
<figure style="display: block; margin: 22px auto; text-align: center;">[圖片上傳失敗...(image-8b6869-1543396429106)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
</figure>
原型鏈
<figure style="display: block; margin: 22px auto; text-align: center;">[圖片上傳失敗...(image-91c7f0-1543396429106)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
</figure>
構(gòu)造函數(shù)以及js原生Object對象之間的原型關(guān)系
<figure style="display: block; margin: 22px auto; text-align: center;">[圖片上傳失敗...(image-60d83d-1543396429106)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
</figure>
原型的注意事項
- 當(dāng)對象在訪問屬性和方法的時候,會現(xiàn)在自身查找固耘,如果沒有才回去原型中找题篷。(一級一級傳遞 形成了原型鏈)
- 替換原型對象的時候,替換之前構(gòu)造函數(shù)創(chuàng)建的對象A和替換之后創(chuàng)建的對象B厅目,A和B的原型是不一致的番枚。
- 對象能夠訪問的原型法严,就是在對象創(chuàng)建的那一刻,和構(gòu)造函數(shù)關(guān)聯(lián)的那個原型
擴展以及延伸
<figure style="display: block; margin: 22px auto; text-align: center;">[圖片上傳失敗...(image-fa4be7-1543396429106)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
</figure>
構(gòu)造函數(shù)
在很多編程語言中葫笼,如java深啤,objectC,c++等,都存在類的概念路星,類中有私有屬性溯街,私有方法等,通過類來實現(xiàn)面對對象的繼承洋丐,但是苫幢,在ES5以及以前中不像上面這幾種語言一樣,有嚴格的類的概念垫挨。js通過構(gòu)造函數(shù)以及原型鏈來實現(xiàn)繼承韩肝。
特點
- 首字母必須為大寫,用來區(qū)分普通函數(shù)
- 內(nèi)部使用的this對象九榔,來指向即將要生成的實例對象
- 使用new 關(guān)鍵字來生成實例對象(下面為new關(guān)鍵字的具體實現(xiàn))
var obj = new Date()
// 可以分解為
var obj = {};
obj.__proto__ = Date.prototype;
Base.call(obj)
復(fù)制代碼
缺點
- 所有的實例對象都可以繼承構(gòu)造器函數(shù)中的屬性和方法哀峻,但是同一個對象實例之間,無法共享屬性哲泊。
- 如果方法在構(gòu)造函數(shù)內(nèi)部剩蟀,每次new一個實例對象的時候,都會創(chuàng)建內(nèi)部的這些方法切威,并且不同的實例對象之間育特,不能共享這些方法,造成了資源的浪費(于是有了原型這個概念)
實現(xiàn)方式 (簡單列舉幾種)
構(gòu)造函數(shù)模式(自定義構(gòu)造函數(shù))
構(gòu)造函數(shù)與普通函數(shù)的區(qū)別
//構(gòu)造函數(shù)
function Egperson (name,age) {
this.name = name;
this.age = age;
this.sayName = function () {
alert(this.name);
}
}
var person = new Egperson('mike','18'); //this-->person
person.sayName(); //'mike'
//普通函數(shù)
function egPerson (name,age) {
this.name = name;
this.age = age;
this.sayName = function () {
alert(this.name);
}
}
egPerson('alice','23'); //this-->window
window.sayName(); //'alice'
復(fù)制代碼
工廠模式
function CreatePerson(name, age, gender){
var obj = {};
obj.name = name;
obj.age = age;
obj.gender = gender;
//由于是函數(shù)調(diào)用模式先朦,所以this打印出來是window
console.log(this);
return obj;
}
var p = CreatePerson("小明", 18, "male"); // 調(diào)用方式是函數(shù)的調(diào)用方式
復(fù)制代碼
寄生模式
function CreatePerson(name, age, gender){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.gender = gender;
//這里的this指向new 創(chuàng)建出來的對象
console.log(this);
return obj;
}
var p = new CreatePerson("小明", 18, "male"); // 調(diào)用方式是函數(shù)的調(diào)用方式
復(fù)制代碼
動態(tài)原型模式
function Person(name, age, job) {
//屬性
this.name = name;
this.age = age;
this.job = job;
//方法
if(typeof this.sayName != "function") {
//所有的公有方法都在這里定義
Person.prototype.sayName = function() {
alert(this.name);
}缰冤;
Person.prototype.sayJob = function() {
alert(this.job);
};
Person.prototype.sayAge = function() {
alert(this.age);
};
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); //Nicholas
person2.sayName(); //Greg
復(fù)制代碼
js實現(xiàn)繼承的方式: 混入式繼承,原型繼承以及經(jīng)典繼承喳魏,ES6的Class也可以實現(xiàn)繼承
Class 詳解
基本上棉浸,ES6 的class可以看作只是一個語法糖,它的絕大部分功能刺彩,ES5 都可以做到迷郑,新的class寫法只是讓對象原型的寫法更加清晰、更像面向?qū)ο缶幊痰恼Z法而已创倔。
// ES5
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
// ES6
//定義類
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
復(fù)制代碼
特點
- class中的constructor函數(shù)相當(dāng)于ES5中的構(gòu)造函數(shù)(聲明屬性以及靜態(tài)方法嗡害,這種類創(chuàng)建屬性和創(chuàng)建方法參照上面動態(tài)原型模式的構(gòu)造函數(shù)。個人感覺有很多相似之處
- 類中定義方法的時候畦攘,前面不加function霸妹,后面不加,可被實例對象也就是子類繼承的方法 定義在類的prototype屬性中念搬。
- 類的內(nèi)部定義的所有方法都是不可枚舉的
- 類和模塊內(nèi)部默認采用嚴格模式
- 子類繼承父類以后抑堡,必須在constructor中調(diào)用時super方法,否則不能新建實例朗徊,因為子類沒有屬于自己的this對象首妖,而是繼承了父類的this對象對其進行加工
類中的原型鏈關(guān)系
每一個對象都有proto屬性,指向?qū)?yīng)的構(gòu)造函數(shù)的prototype屬性爷恳。Class 作為構(gòu)造函數(shù)的語法糖有缆,同時有prototype屬性和proto屬性,因此同時存在兩條繼承鏈温亲。
- 子類的proto屬性棚壁,表示構(gòu)造函數(shù)的繼承,總是指向父類栈虚。
- 子類prototype屬性的proto屬性袖外,表示實例方法的繼承,總是指向父類的prototype屬性魂务。
class A {
}
class B extends A {
}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
復(fù)制代碼
類的繼承內(nèi)部實現(xiàn)
class A {
}
class B {
}
// B 的實例繼承 A 的實例
Object.setPrototypeOf(B.prototype, A.prototype);
// B 繼承 A 的靜態(tài)屬性
Object.setPrototypeOf(B, A);
const b = new B();
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
復(fù)制代碼
作為一個對象曼验,子類(B)的原型(proto屬性)是父類(A);
作為一個構(gòu)造函數(shù)粘姜,子類(B)的原型對象(prototype屬性)是父類的原型對象(prototype屬性)的實例鬓照。
Object.create(A.prototype);
// 等同于
B.prototype.__proto__ = A.prototype;
復(fù)制代碼
實例的 proto 屬性
子類實例的proto屬性的proto屬性,指向父類實例的proto屬性孤紧。也就是說豺裆,子類的原型的原型,是父類的原型号显。
var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');
p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true
復(fù)制代碼
類中this指向問題
類的方法內(nèi)部含有this臭猜,默認指向類的實例。但是當(dāng)類中的實例方法提取出來使用的時候押蚤,this指向運行時所在環(huán)境获讳。
解決方法(新版react中,在聲明綁定方法的時候 三種方式與此相同)
class Logger {
constructor() {
this.printName = this.printName.bind(this);
}
// ...
}
class Logger {
constructor() {
this.printName = (name = 'there') => {
this.print(`Hello ${name}`);
};
}
// ...
}
復(fù)制代碼
ES5與ES6 實現(xiàn)繼承的區(qū)別
在ES5中活喊,繼承實質(zhì)上是子類先創(chuàng)建屬于自己的this丐膝,然后再將父類的方法添加到this(也就是使用Parent.apply(this)的方式
而在ES6中,則是先創(chuàng)建父類的實例對象this钾菊,然后再用子類的構(gòu)造函數(shù)修改this帅矗。