1.創建對象的幾種方式
1.對象字面量模式
直接使用{}
定義鍵值對:
const obj = { key: 'value' };
2.Object()
構造函數模式
使用內置構造函數(較少使用):
const person = new Object();
console.log(person)//輸出 {}
3.構造函數模式
通過函數模擬類,結合new
關鍵字創建實例:
function Person(name) { this.name = name; }
const person = new Person('John');
console.log(person)//輸出{name:"join"}
2.原型與原型鏈
1.構造函數
實例有一個屬性
constructor
指向構造函數,實例擁有構造函數內部定義的屬性和方法,構造函數相當于一個模板藍圖
function User(){this.age = 18this.login = function (){console.log("登錄成功")}
}
user1 = new User()
user1 = new User()
console.log(user1.age) //18
console.log(user1.constructor == user2.constructor) //true
2.顯式原型
在js中,函數對象有一個特殊的屬性叫做
顯式原型(prototype)
,它指向另一個對象,這個對象(User.prototype)被稱為原型對象
原型對象是用來共享屬性和方法
我們可以看下面的代碼
- 在構造函數內部定義方法
function User(){this.age = 18this.login = function (){console.log("登錄成功")}
}
user1 = new User()
user2 = new User()
console.log(user1)// { age: 18, login: [Function (anonymous)] }
console.log(user1.age == user2.age) //true
console.log(user1.login == user2.login) //false 因為指向了不同內存地址
每個實例在堆內存中都有自己獨立的方法。上面代碼中,創建2個實例就會創建2個login
方法。
- 在原型上定義方法
function User(){this.age = 18}
User.prototype.login = function(){ console.log("登錄成功")}
user1 = new User()
user2 = new User()
console.log(user1)//{ age: 18 }
console.log(user1.login == user2.login)//true
console.log(user1.login())//登錄成功
創建出來的實例對象上本身并沒有login
方法
這個login
方法在內存中只存在一份,它被分配在堆內存中,并且掛載到User.prototype
這個對象上,如果在當前對象中沒有獲取到就會去它的原型上面獲取。
- 二者比較
特性 | 定義在原型上 User.prototype.login= function() | 定義在構造函數內部 this.login = function() |
---|---|---|
方法存儲位置 | 原型對象(僅存一份) | 每個實例獨立存儲(每次new 都創建新函數) |
內存占用 | ? 極低 (1000個實例共享1個方法) | ? 爆炸式增長 (1000個實例創建1000個獨立方法) |
3.隱式原型
在js中,每個對象(除Object.create(null) 創建外)都有一個
__proto__
屬性(左右兩個短下劃線),這個被稱為隱式原型
,
隱式原型是js中所有對象的核心繼承機制,直接決定了原型鏈的運作方式,__proto__
存在的意義在于為原型鏈查找提供方向。隱式原型指向該對象的構造函數原型對象(即User.prototype
)但通過 Object.create()
創建的對象會直接指向參數對象。
1.通過構造函數創建的實例對象,實例對象的隱式原型指向構造函數的顯式原型
function User(){this.age = 18}
User.prototype.login = function(){ console.log("登錄成功")}
user1 = new User()
console.log(user.__proto__ == User.prototype)//true
2.通過Object.create()創建的實例對象,隱式原型指向參數對象
const animal = { type: 'unknown' };
// 參數對象 = animal
const dog = Object.create(animal);
// dog.__proto__ 直接指向 animal(而非默認的 Object.prototype)
console.log(dog.type); // 'unknown' ?
console.log(dog.__proto__ === animal); // true ?
- 通過手動設置的原型實現繼承
我們知道,從一個對象上獲取屬性,如果在當前對象中沒有獲取到就會去它的原型上面獲取:
//user1.__proto__ = User.prototype
var obj1 = {age:"18"}
var obj = {name:"john"};
obj.__proto__ = obj1
console.log(obj.name)//john
console.log(obj.age)//"18"
構造函數 、實例、原型對象之間的關系圖
4.原型鏈
原型鏈(Prototype Chain)是JavaScript中實現繼承的機制。
每個對象都有一個原型(__proto__屬性),而原型本身也是一個對象,它也有自己的原型,這樣一層一層形成的鏈式結構就是原型鏈。當訪問一個對象的屬性或方法時,如果對象自身沒有該屬性,就會沿著原型鏈向上查找,直到找到該屬性或到達原型鏈的頂端(null)為止。
function Person() {}
const p = new Person();// 原型鏈驗證
console.log(p.__proto__ == Person.prototype); // L1 true
console.log(Person.prototype.__proto__ == Object.prototype);// L2 true
console.log(Object.prototype.__proto__ == null); // L3 true
原型繼承關系圖
注:圖中涉及到的部分知識點文中未提及
3.結語
本文旨在簡單的理解原型和原型鏈來對抗環境檢測和還原關鍵加密邏輯,實際場景中網站會通過檢測原生對象原型鏈來判斷是否處于瀏覽器環境。
比如某網站會檢查navigator.plugins的原型鏈長度判斷是否爬蟲;
用自定義Error對象,捕獲錯誤后檢查stack屬性是否包含原生Error原型的方法名等等;
如需進一步理解原型鏈可參考文章:
參考文章