詳解js原型,建構函式以及class之間的原型關係
在建構函式建立的時候,系統預設的幫建構函式建立並關聯一個物件 這個物件就是原型
作用
在原型中的所有屬性和方法,都可以被和其關聯的建構函式創建出來的所有的物件共享
訪問原型
建構函式名.prototype 例項化的物件.__proto __
原型的簡單使用
- 利用物件的動態特性為原型物件增加成員
- 直接替換原型物件(jq核心方法的實現 就是使用原型替換的思想)
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) 複製程式碼


1. prototype
含義: 是一個函式的屬性,這個屬性是一個指標,指向一個物件 作用: 建構函式呼叫 訪問該建構函式所關聯的原型物件
2.proto
含義: 是一個物件擁有的內建屬性,是js內部使用尋找原型鏈的屬性,通過該屬性可以允許例項物件直接訪問到原型
3. constructor
含義:原型物件的constructor 指向其建構函式,如果替換了原型物件之後,這個constructor屬性就不準確,需要手動補充一下

原型鏈

建構函式以及js原生Object物件之間的原型關係

原型的注意事項
- 當物件在訪問屬性和方法的時候,會現在自身查詢,如果沒有才回去原型中找。(一級一級傳遞 形成了原型鏈)
- 替換原型物件的時候,替換之前建構函式建立的物件A和替換之後建立的物件B,A和B的原型是不一致的。
- 物件能夠訪問的原型,就是在物件建立的那一刻,和建構函式關聯的那個原型
擴充套件以及延伸

建構函式
在很多程式語言中,如java,objectC,c++等,都存在類的概念,類中有私有屬性,私有方法等,通過類來實現面對物件的繼承,但是,在ES5以及以前中不像上面這幾種語言一樣,有嚴格的類的概念。js通過建構函式以及原型鏈來實現繼承。
特點
- 首字母必須為大寫,用來區分普通函式
- 內部使用的this物件,來指向即將要生成的例項物件
- 使用new 關鍵字來生成例項物件(下面為new關鍵字的具體實現)
var obj = new Date() // 可以分解為 var obj = {}; obj.__proto__ = Date.prototype; Base.call(obj) 複製程式碼
缺點
- 所有的例項物件都可以繼承構造器函式中的屬性和方法,但是同一個物件例項之間,無法共享屬性。
- 如果方法在建構函式內部,每次new一個例項物件的時候,都會建立內部的這些方法,並且不同的例項物件之間,不能共享這些方法,造成了資源的浪費(於是有了原型這個概念)
實現方式 (簡單列舉幾種)
建構函式模式(自定義建構函式)
建構函式與普通函式的區別
//建構函式 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' //普通函式 function egPerson (name,age) { this.name = name; this.age = age; this.sayName = function () { alert(this.name); } } egPerson('alice','23'); //this-->window window.sayName();//'alice' 複製程式碼
工廠模式
function CreatePerson(name, age, gender){ var obj = {}; obj.name = name; obj.age = age; obj.gender = gender; //由於是函式呼叫模式,所以this打印出來是window console.log(this); return obj; } var p = CreatePerson("小明", 18, "male");// 呼叫方式是函式的呼叫方式 複製程式碼
寄生模式
function CreatePerson(name, age, gender){ var obj = new Object(); obj.name = name; obj.age = age; obj.gender = gender; //這裡的this指向new 創建出來的物件 console.log(this); return obj; } var p = new CreatePerson("小明", 18, "male");// 呼叫方式是函式的呼叫方式 複製程式碼
動態原型模式
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 複製程式碼
js實現繼承的方式: 混入式繼承,原型繼承以及經典繼承,ES6的Class也可以實現繼承
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 + ')'; }; var p = new Point(1, 2); // ES6 //定義類 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } 複製程式碼
特點
- class中的constructor函式相當於ES5中的建構函式(宣告屬性以及靜態方法,這種類建立屬性和建立方法參照上面動態原型模式的建構函式。個人感覺有很多相似之處
- 類中定義方法的時候,前面不加function,後面不加,可被例項物件也就是子類繼承的方法 定義在類的prototype屬性中。
- 類的內部定義的所有方法都是不可列舉的
- 類和模組內部預設採用嚴格模式
- 子類繼承父類以後,必須在constructor中呼叫時super方法,否則不能新建例項,因為子類沒有屬於自己的this物件,而是繼承了父類的this物件對其進行加工
類中的原型鏈關係
每一個物件都有__proto__屬性,指向對應的建構函式的prototype屬性。Class 作為建構函式的語法糖,同時有prototype屬性和__proto__屬性,因此同時存在兩條繼承鏈。
- 子類的__proto__屬性,表示建構函式的繼承,總是指向父類。
- 子類prototype屬性的__proto__屬性,表示例項方法的繼承,總是指向父類的prototype屬性。
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 繼承 A 的靜態屬性 Object.setPrototypeOf(B, A); const b = new B(); Object.setPrototypeOf = function (obj, proto) { obj.__proto__ = proto; return obj; } 複製程式碼
作為一個物件,子類(B)的原型(__proto__屬性)是父類(A);
作為一個建構函式,子類(B)的原型物件(prototype屬性)是父類的原型物件(prototype屬性)的例項。
Object.create(A.prototype); // 等同於 B.prototype.__proto__ = A.prototype; 複製程式碼
例項的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 複製程式碼
類中this指向問題
類的方法內部含有this,預設指向類的例項。但是當類中的例項方法提取出來使用的時候,this指向執行時所在環境。
解決方法(新版react中,在宣告繫結方法的時候 三種方式與此相同)
class Logger { constructor() { this.printName = this.printName.bind(this); } // ... } class Logger { constructor() { this.printName = (name = 'there') => { this.print(`Hello ${name}`); }; } // ... } 複製程式碼
ES5與ES6 實現繼承的區別
-
在ES5中,繼承實質上是子類先建立屬於自己的this,然後再將父類的方法新增到this(也就是使用Parent.apply(this)的方式
-
而在ES6中,則是先建立父類的例項物件this,然後再用子類的建構函式修改this。