執行上下文分爲全局上下文(在瀏覽器中全局上下文爲window對象),和函數上下文。全局上下文存儲的即是全局變量和方法。而在函數裏面定義的變量和方法則儲存在函數上下文裏面。 當代碼執行時,遇到變量,首先會在當前做用域裏面找,沒有的話則會到父級做用域,直到找到爲止或者到達全局做用域javascript
var color = 「blue」;
function changeColor(){
var anotherColor = 「red」;
function swapColors(){
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
//color, anotherColor, and tempColor are all accessible here }
//color and anotherColor are accessible here, but not tempColor
swapColors();
}
//only color is accessible here changeColor();
複製代碼
上面的代碼有三個執行上下文:全局上下文,changeColor()
的函數上下文,swapColors()
的函數上下文。全局上下文的color
變量在swapColors()
函數裏面被訪問到,正是由於做用域鏈的關係,做用域鏈是向上的而這不是向下的,因此全局上下文沒有辦法訪問到tempColor
這個變量java
javascript在es6以前是沒有塊級做用域的es6
if (true) {
var color = 'blue'
}
console.log(color)
複製代碼
在if裏面建立的color是能夠在全局上下文訪問到的,es6以後可使用let關鍵字來限制變量的範圍。面試
自由變量將從做用域鏈中去尋找,可是 依據的是函數定義時的做用域鏈,而不是函數執行時,以上這個例子就是閉包。閉包主要有兩個應用場景:算法
function F1() {
var a = 100
return function () {
console.log(a)
}
}
function F2(f1) {
var a = 200
console.log(f1())
}
var f1 = F1()
F2(f1)
複製代碼
以上結果輸出的100,由於函數定義時a的值已是100了。瀏覽器
若是在函數內部分配一個之前沒有被定義的變量的值,它會自動成爲全局範圍的一部分。bash
function myFunction() {
myVariable = 'JavaScript';
}
myFunction();
console.log(myVariable); //JavaScript
複製代碼
this是什麼呢,this其實就是函數調用時的執行上下文 func(p1, p2)
執行這個代碼,等價於func.call(undefined, p1, p2)
, 裏面的context爲undefined,當context 就 null 或者 undefined,那麼 window 對象就是默認的 context(嚴格模式下默認 context 是 undefined),那麼這時候的this就是等價於winodow。 若是是執行obj.f1(p1)
,至關於執行obj.f1.call(obj, p1)
,這時候的this就是obj的執行上下文閉包
規則:在非嚴格模式下,默認綁定的this
指向全局對象,嚴格模式下this
指向undefinedapp
function foo() {
console.log(this.a); // this指向全局對象
}
var a = 2;
foo(); // 2
function foo2() {
"use strict"; // 嚴格模式this綁定到undefined
console.log(this.a);
}
foo2(); // TypeError:a undefined
複製代碼
默認綁定規則如上述栗子,書中還提到了一個微妙的細節:dom
function foo() {
console.log(this.a); // foo函數不是嚴格模式 默認綁定全局對象
}
var a = 2;
function foo2(){
"use strict";
foo(); // 嚴格模式下調用其餘函數,不影響默認綁定
}
foo2(); // 2
複製代碼
因此:對於默認綁定來講,決定this綁定對象的是函數體是否處於嚴格模式,嚴格指向undefined,非嚴格指向全局對象。
一般不會在代碼中混用嚴格模式和非嚴格模式,因此這種狀況很罕見,知道一下就能夠了,避免某些變態的面試題挖坑。
規則:函數在調用位置,是否有上下文對象,若是有,那麼this就會隱式綁定到這個對象上。
function foo() {
console.log(this.a);
}
var a = "Oops, global";
let obj2 = {
a: 2,
foo: foo
};
let obj1 = {
a: 22,
obj2: obj2
};
obj2.foo(); // 2 this指向調用函數的對象
obj1.obj2.foo(); // 2 this指向最後一層調用函數的對象
// 隱式綁定丟失
let bar = obj2.foo; // bar只是一個函數別名 是obj2.foo的一個引用
bar(); // "Oops, global" - 指向全局
複製代碼
隱式綁定丟失:
隱式綁定丟失的問題:實際上就是函數調用時,並無上下文對象,只是對函數的引用,因此會致使隱式綁定丟失。
一樣的問題,還發生在傳入回調函數中,這種狀況更加常見,而且隱蔽,相似:
test(obj2.foo); // 傳入函數的引用,調用時也是沒有上下文對象。
複製代碼
就像咱們上面看到的,若是單純使用隱式綁定確定沒有辦法獲得指望的綁定,幸虧咱們還能夠在某個對象上強制調用函數,從而將this綁定在這個對象上。
規則:咱們能夠經過apply
、call
、bind
將函數中的this
綁定到指定對象上。
function foo() {
console.log(this.a);
}
let obj = {
a: 2
};
foo.call(obj); // 2
複製代碼
傳入的不是對象:
若是你傳入了一個原始值(字符串,布爾類型,數字類型),來當作this的綁定對象,這個原始值轉換成它的對象形式。
若是你把null
或者undefined
做爲this的綁定對象傳入call
/apply
/bind
,這些值會在調用時被忽略,實際應用的是默認綁定規則。
書中提到:在js中,實際上並不存在所謂的'構造函數',只有對於函數的'構造調用'。
new的時候會作哪些事情:
規則:使用構造調用的時候,this會自動綁定在new期間建立的對象上。
function foo(a) {
this.a = a; // this綁定到bar上
}
let bar = new foo(2);
console.log(bar.a); // 2
複製代碼
若是在某個調用位置應用了多條規則,如何肯定哪條規則生效?
obj.foo.call(obj2); // this指向obj2 顯式綁定比隱式綁定優先級高。
new obj.foo(); // thsi指向new新建立的對象 new綁定比隱式綁定優先級高。
複製代碼
顯式綁定和new綁定沒法直接比較(會報錯),默認綁定是不該用其餘規則以後的兜底綁定因此優先級最低,最後的結果是:
顯式綁定 > 隱式綁定 > 默認綁定
new綁定 > 隱式綁定 > 默認綁定
function foo() {
return () => {
console.log(this.a);
};
}
let obj1 = {
a: 2
};
let obj2 = {
a: 22
};
let bar = foo.call(obj1); // foo this指向obj1
bar.call(obj2); // 輸出2 這裏執行箭頭函數 並試圖綁定this指向到obj2
複製代碼
從上述栗子能夠得出,箭頭函數的this規則:
一般,函數的做用域及其全部變量都會在函數執行結束後被銷燬。可是,在建立了一個閉包之後,這個函數的做用域就會一直保存到閉包不存在爲止。 閉包有三個特性:
1.函數嵌套函數
2.函數內部能夠引用外部的參數和變量
3.參數和變量不會被垃圾回收機制回收
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
// 釋放對閉包的引用
add5 = null;
add10 = null;
複製代碼
閉包的做用,就是保存本身私有的變量,經過提供的接口(方法)給外部使用,但外部不能直接訪問該變量。
當咱們須要在模塊中定義一些變量,並但願這些變量一直保存在內存中但又不會「污染」全局的變量時,就能夠用閉包來定義這個模塊。
閉包的缺點:閉包的缺點就是常駐內存,會增大內存使用量,使用不當很容易形成內存泄露。
函數套函數就是閉包嗎?:不是!,當一個內部函數被其外部函數以外的變量引用時,纔會造成了一個閉包。
對象模式 函數內部定義個一個對象,對象中綁定多個函數(方法),返回對象,利用對象的方法訪問函數內的數據
function createPerson() {
var __name__ = "";
return {
getName: function () {
return __name__;
},
setName: function( value ) {
// 若是不姓張就報錯
if ( value.charAt(0) === '張' ) {
__name__ = value;
} else {
throw new Error( '姓氏不對,不能取名' );
}
}
}
}
var p = createPerson();
p.set_Name( '張三丰' );
console.log( p.get_Name() );
p.set_Name( '張王富貴' );
console.log( p.get_Name() );
複製代碼
函數模式 函數內部定義一個新函數,返回新函數,用新函數得到函數內的數據
function foo() {
var num = Math.random();
function func() {
return mun;
}
return func;
}
var f = foo();
// f 能夠直接訪問這個 num
var res1 = f();
var res2 = f();
複製代碼
沙箱模式 沙箱模式就是一個自調用函數,代碼寫到函數中同樣會執行,可是不會與外界有任何的影響,好比jQuery
(function () {
var jQuery = function () { // 全部的算法 }
// .... // .... jQuery.each = function () {}
window.jQuery = window.$ = jQuery;
})();
$.each( ... )
複製代碼
回收機制
在javascript中,若是一個對象再也不被引用,那麼這個對象就會被垃圾回收機制回收; 若是兩個對象互相引用,而再也不被第3者所引用,那麼這兩個互相引用的對象也會被回收。
變量聲明提高
js 代碼在運行前都會進行 AST 解析,函數申明默認會提到當前做用域最前面,變量申明也會進行提高。但賦值不會獲得提高。
參考 https://juejin.im/post/5b3715def265da59af40a630#heading-2