JavaScript 中,萬物皆物件!但物件也是有區別的。分為普通物件和函式物件,Object ,Function 是JS自帶的函式物件。
怎麼區分?其實很簡單,凡是通過 new Function() 建立的物件都是函式物件,其他的都是普通物件。Function Object 也都是通過 New Function()建立的。
建構函式
建構函式可用來建立特定型別的物件。像Object和Array這樣的原生建構函式,在執行時會自動出現在執行環境中。
function Person() {
}
var person = new Person();
person.name = 'Kevin';
console.log(person.name) // Kevin
複製程式碼
按照國際慣例,建構函式始終都應該以一個大寫字母開頭,而非建構函式則應該以一個小寫字母開頭。
要建立Person的新例項,必須使用new操作符。一般是經歷以下四個步驟:
- 1、建立一個新物件;
- 2、將建構函式的作用域賦給新物件(此this的指向了這個新物件)
- 3、為這新物件新增建構函式的屬性
- 4、返回新物件
上面例子中,person 就用有一個constructor (建構函式)屬性,該屬性是指向Person。
原型物件
無論什麼時候,只要建立一個新函式,就會根據一組特定的規則為該函式建立一個prototype屬性,這個屬性指向函式的原型物件。普通物件沒有 prototype,但有 __proto__
屬性。
如上面例子,Person.prototype 指向了原型象,而 Person.prototype.constructor 又指回了Person。
而 person 只是一個物件例項。
而person可以訪問儲存在原型中的值,但卻不能重寫原型中的值。如果我們在 person 中新增一個屬性,而該屬性與例項原型中的一個屬性同名,那個person中的屬性會暫時遮蔽原型中的屬性,刪除後,還是讀回原型中的屬性
function Person() {
}
Person.prototype.name = 'Perty';
var person = new Person();
person.name = 'Kevin';
console.log(person.name) // Kevin
delete person.name
console.log(person.name) // Perty
複製程式碼
解析器的操作是:
- “例項person有name屬性嗎?”
- “有” —— 於是就讀取例項中的name屬性,輸出 Kevin
刪除後,再執行時:
- “例項person有name屬性嗎?”
- “沒有”
- “person的原型有name屬性嗎?”
- “有” —— 於是就讀取原型中的name的屬性,輸出 Perty
原型鏈
在建立物件(不論是普通物件還是函式物件)的時候,都有一個叫做 __proto__
的內建屬性,用於指向建立它的函式物件的原型物件 prototype。以上面的例子為例:
console.log(person.__proto__ === Person.prototype) // true
複製程式碼
同樣的,Person.prototype 也同樣有 proto 屬性,它指向建立它的函式物件(Object)的prototype
console.log(Person.prototype === Object.prototype) // true
複製程式碼
繼續,Object.prototype物件也有__proto__屬性,但它比較特殊,為null
console.log(Object.prototype.__proto__) // null
複製程式碼
我們把這個有 __proto__
串起來的直到 Object.prototype.__proto__
為null的鏈叫做原型鏈。
person.__proto__ ==> Person.prototype.__proto__ ==> Object.prototype.__proto__ ==> null
最佳例項zepto
讀過zepto的原始碼都瞭解到$其實是一個函式,同時在 $ 身上又掛了很多屬性和方法。
以下程式碼:
<p id="p1">段落1</p>
<p id="p2">段落2</p>
<p id="p3">段落3</p>
<script type="text/javascript" src="js/zepto-1.1.6.js"></script>
複製程式碼
$('p')
返回的是類陣列的一樣東西。
我們先一步步來分析一下它究竟是不是陣列:
var arr = [1,2,3];
var $p = $('p');
// 對比1
arr.__proto__.constructor === Array; // true
$p.__proto__.constructor === Array; // false
// 對比2
arr instanceof Array; // true
$p instanceof Array; // false
複製程式碼
從原型指向中,可以看出$p真的只是類似陣列一樣的陣列。而陣列是沒有addClass
,removeClass
等等的屬性。
我們可以看console中,執行console.log($p.__proto__)
,就可以看$p
可呼叫的方法(這也是平時在使用zepto時,忘記api方法名時,可以即時查出來)
同理,我們檢視陣列下面的方法console.log(arr.__proto__)
可以看到在arr
中是沒有addClass
,removeClass
等等的屬性的。
那我們也為arr
建立一個addClass
方法試試:
var arr = [1,2,3]
arr.__proto__ = {
addClass: function () {
console.log(123);
}
};
arr.addClass(); // 123
複製程式碼
此時我們再去拿arr來做第一次的那幾個驗證,得到的結果就和之前的$p一樣了,即arr此時也稱了一個不是陣列的陣列
這樣就可以成功建立了addClass
方法,而這個解析器的操作是:
- 建立一個新的陣列例項
[1,2,3]
- 新例項新增一個屬性方法
addClass
執行arr.addClass()
時:
- “例項arr有addClass屬性嗎?”
- “有” —— 於是就讀取例項中的addClass屬性,輸出 123
這裡只是淺析zepto的基本設計,也是瞭解zepto設計原理的開始,想要更深入理解zepto,請移步zepto