接著上一篇 你不知道的JavaScript·第一部分
第一章: 關於this
this 到底是什麼
this 是在 執行時
繫結的,this的繫結和函式宣告的位置沒有任何關係,只卻取決於函式的 呼叫方式
。
function foo() {
this.bar(); // this指向window,相當於 window.bar()
}
function bar() {
console.log('---bar---');
}
foo()
複製程式碼
上段程式碼在瀏覽器下是能正常執行的,函式 bar 能被正常呼叫,因為函式 bar 是宣告在全域性的,全域性的 this 指向 window , 執行函式 foo 時也是在全域性(window)下執行的,所以函式內部的 this 也是指向 window 的,相當於在函式 foo 內部 是 window.bar()
,自然能正常呼叫執行。
但在嚴格模式下 'use strict' , 全域性 this 不指向 window,而是 undefined,所以在嚴格模式下上面程式碼會報錯
第二章: this全面解析
1、呼叫位置
一個很簡單的判斷 this 指向誰就看函式在程式碼的呼叫位置
例子1
下面這個例子第一章節分析的一樣,函式 foo 在全域性呼叫, 在瀏覽器下 this 指向 wwindow,當然要是考慮嚴格模式, this.a 會報錯 a is not defined
a = 1;
function foo() {
console.log(this.a); // 1
}
foo();
複製程式碼
例子2
下面例子雖然同一個函式 foo ,但卻被不同的物件呼叫,this 分別指向它的呼叫者
function foo() {
console.log(this.a);
}
var obj1 = {
foo: foo,
a: 1
}
var obj2 = {
foo: foo,
a: 2
}
obj1.foo() // 1
obj2.foo() // 2
複製程式碼
特殊例子3
下面例子雖然函式 foo 宣告在全域性,但被利用 call
、apply
、bind
顯式的繫結了物件,this 指向顯式繫結的物件
function foo() {
console.log(this.a);
}
var obj1 = {
a: 1
}
var obj2 = {
a: 2
}
var obj3 = {
a: 3
}
foo.call(obj1) // 1
foo.apply(obj2) // 2
foo.bind(obj3)() // 3
複製程式碼
例子4
下面例子使用了 new 繫結,this 指向了該物件
function foo(a) {
this.a = a
}
var bar = new foo(1)
console.log(bar.a);
複製程式碼
使用 new 來呼叫函式經歷了以下步驟:
1、建立或者說構造一個全新的物件
2、這個新物件會被執行[Prototype]連線
3、這個新物件會被繫結到函式呼叫的 this
4、如果這個函式沒有返回其他物件,那麼 new 表示式中的函式呼叫會自動返回這個新物件
小結:
經過上面的例子分析,判斷 this 的指向可以羅列幾個點,
1、看呼叫者,函式被誰呼叫 this 指向誰
2、是否有 call
、apply
、bind
顯式的改變了 this 的指向
3、函式是否是被 new 建立的,是的話 this 指向 new 出來的物件
為什麼 this 的指向不固定呢,因為 JavaScript 是‘解釋執行’ 語言,邊編譯邊執行的,this 的指向也就動態的。
第三章: 物件
物件再熟悉不過了,物件的基本要素就是屬性和方法,但有時候程式碼寫著寫著就忘了物件本來的特徵。
1、內建物件
String
var str = 'i am a string';
str.length ; // 13
str.charAt(3); // m
複製程式碼
字串經常使用,沒但發覺字串也會有屬性和方法,這個特性應該物件才有的呀。原來JavaScript 引擎自動把字串轉換成 String 物件,所以可以訪問屬性和方法。
Array
陣列也是 JavaScript 的內建物件,那既然是物件是不是也可以像物件那樣賦值和操作呢。
var arr = ['foo', 'bar'];
arr.baz = 'baz';
console.log(arr.baz);
複製程式碼
上面例子不像以往那樣運算元組,而是按物件的形式進行操作,是可行的。但這裡也是為了給大家解釋陣列也是物件這麼個基本概念,一般不將陣列當做普通鍵值物件來使用。
內建物件除了上面提到的 String,Array,還有 Number、Boolean、Object、Function、Date、RegExp、Error
2、屬性描述符
var obj = {
a: 1
}
複製程式碼
物件 obj 的屬性 a 就單單記錄一個數值 1 嗎,其實不是的,我們們用 getOwnPropertyDescriptor
列印下屬性 a 看下輸出資訊
console.log(Object.getOwnPropertyDescriptor(obj, 'a'));
// 輸出
{
value: 1,
writable: true,
configurable: true,
enumerable: true
}
複製程式碼
發現列印出來好幾個屬性,這幾個屬性來解釋一下
writable
writable 決定是否可以修改屬性值
var obj = {}
Object.defineProperty(obj, 'a', {
value: 1,
writable: false,
enumerable: true,
configurable: true
})
obj.a = 2
console.log(obj.a); // 1
複製程式碼
writable 屬性置為 false 就無法更改屬性的值了
configurable
configurable 屬性用來描述物件的屬性是否可配置,也就是 configurable = false,接下去程式碼想要將 configurable = true 將會報錯,因為物件的屬性已經不可配置了
enumerable
enumerable 屬性用來設定該物件屬性是否可列舉, enumerable = false 那麼該物件屬性將不在列舉中,也就是 for...in 將不會遍歷到該屬性
3、Getter和Setter
物件還有兩個隱藏的函式會被忽略,因為是 JavaScript 的預設操作,沒特殊用法的話就不會去修改它,如果重新修改了 Getter和Setter 隱藏函式,JavaScript會忽略它們的 value 和 writable 特性
下面一個例子對屬性 a 進行 Getter和Setter 重寫
var obj = {
get a() {
return this._a_
},
set a(val) {
this._a_ = val * 2
}
}
obj.a = 2
console.log(obj.a); // 4
複製程式碼
因為對賦值和取值時都會觸發相應的 get 和 set 方法,那就可以利用這個特性做一些比如訊息的釋出訂閱,事件通知等高階用法了
第五章: 原型
JavaScript 物件有一個特殊的 [Prototype] 屬性,幾乎所有物件在建立時 [Prototype] 屬性都會被賦予一個非空的值。
1、Prototype
var anotherObj = { a: 1}
var obj = Object.create(anotherObj)
console.log(obj.a); // 1
複製程式碼
Object.create()
會把一個物件的 [Prototype] 關聯到另一個物件上,上面例子物件 obj 的 [Prototype] 被關聯到了物件 anotherObj 上,當訪問物件 obj 的屬性 a 時,如果物件上不存在該屬性那麼 [Prototype] 鏈就會被遍歷,[Prototype] 鏈上找到對應的屬性就會返回。到哪裡才是 [Prototype] 鏈的‘盡頭’呢,[Prototype] 鏈最終會指向JavaScript 內建的 Object.prototype
2、"類"函式
function Foo() {
// ...
}
var a = new Foo()
複製程式碼
上面例子的 Foo 是一個"類"函式,或被叫做 "建構函式" ,為什麼叫做"類"函式而不直接叫做"類"呢,其實 JavaScript 中只有物件,它並沒有類,es6 的類也只是一個語法糖而已。
被習慣稱作"類"的原因是因為 JavaScript 有個 new 操作符,所以習慣的和其他語言一樣稱 new 後面的函式為一個類。new Foo() 只是間接完成了一個目的:一個關聯到其他物件的新物件。也就是 new 操作符的作用就是建立一個關聯物件而已。
Foo 其實也只是一個函式, 沒有 new 操作符, Foo() 也能正常執行,只是當且僅當使用 new 時,函式呼叫會變成 ‘建構函式呼叫’。
3、原型鏈
function Foo() {
// ...
}
Foo.prototype // {}
複製程式碼
所有的函式預設都會有一個名為 prototype
的共有並且不可列舉的屬性,prototype即為函式的原型
function Foo() {
// ...
}
var a = new Foo()
a.__proto__ // {}
複製程式碼
所有的物件預設都會有一個名為 __proto__
的共有並且不可列舉的屬性
prototype
和 __proto__
有什麼聯絡呢?
a.__proto__ === Foo.prototype
複製程式碼
每個物件的 __proto__
屬性指向函式的原型。
因為每個物件都有 __proto__
屬性指向函式的 prototype
所以往函式的 prototype
新增屬性或方法,每個物件都能訪問的到
function Foo() {
// ...
}
Foo.prototype.age = 233
var a = new Foo()
var b = new Foo()
a.age // 233
b.age // 233
複製程式碼
4、物件關聯
如果在第一個物件上沒有找到需要的屬性或者方法引用,JavaScript引擎會繼續在 [Prototype] 關聯的物件上進行查詢。同理,如果在後者中也沒有找到需要的引用就會繼續查詢它的 [Prototype] ,以此類推。這一系列物件的連結被稱為 ‘原型鏈’ 。
‘原型鏈’ 在上面已經提到和解釋了,這裡更加深入的去理解什麼是 ‘原型鏈’ 。
‘原型鏈’ 這種機制的本質其實是物件之間的關聯關係:物件關聯
var Obj1 = {
name: '曾田生',
setID: function(ID) {
console.log(ID)
}
}
var Obj2 = Object.create(Obj1)
Obj2.age = 233
var Obj3 = Object.create(Obj2)
console.log(Obj3.age) // 233
console.log(Obj3.name) // 曾田生
Obj3.setID('ABCD') // ABCD
console.log(Obj3)
複製程式碼
我們使用 Object.create 將一個個物件 關聯
起來,當訪問到自身物件沒有的方法或屬性時,就會去它關聯的物件查詢,這其實就是 [Prototype] 的機制。
花了兩個篇幅來整理 《你不知道的JavaScript·上卷》,希望各位看官有所收穫,感興趣的同學可以前往我的 github 檢視其它博文 ,歡迎 star