你不知道的JavaScript(上)筆記
阿新 • • 發佈:2018-12-29
1. 作用域
-
1.1 概念
作用域是一套規則,用於確定如何在當前作用域以及巢狀的子作用域中,根據識別符號名稱,進行變數查詢。
-
1.2 LHS和RHS(查詢型別)
LHS:左查詢,對變數進行賦值 RHS:右查詢,對變數進行引用
function fn(a) { // a = 2(隱式變數分配)是LHS var b = 3; // LHS return a + b; // RHS } fn(2); // fn() 是LHS
2. 詞法作用域
-
2.1 概念
詞法作用域是定義在詞法階段的作用域。
-
2.2 EVAL和WITH(修改作用域,儘量不要使用,消耗效能)
eval: 執行傳入的引數,嚴格模式下無效
function fn(str) { eval(str); console.log(a); // 嚴格模式下報錯 ReferenceError: a is not defined } fn('var a = 2'); // 2
with: 可以將一個沒有或有多個屬性的物件處理為一個完全隔離的詞法作用域, 但在這個塊內正常的var宣告,並不會被限制在這個塊的作用域中,而是會被新增到with所處的函式作用域中。
function fn(obj) { with(obj) { obj.a = 22 } } var obj1 = { a: 1 }, obj2 = { b: 3 }; fn(obj1); // obj1.a = 22 console.log(a); // a is not defined fn(obj2); // obj.a = undefined console.log(a); // a = 22 (非嚴格模式下,作用域對a進行了LHS查詢,但在obj2和全域性作用域中都未找到,便自動建立了一個全域性變數a)
3 函式作用域和塊作用域
-
3.1 隱藏內部實現(最小暴露原則)
好處:私有化內部變數和函式;規避命名衝突。 壞處:汙染該函式或物件所在的作用域;必須顯示呼叫。 解決:匿名自執行函式。
-
3.2 立即執行函式表示式(IIFE)
使用:(function() {...})() / (function() {}()) 傳參:(function() {})(window,...)
-
3.3 塊級作用域
let:宣告一個作用域被限制在塊級中的變數、語句或者表示式。(沒有變數宣告提升) const: 宣告建立一個常量的只讀引用(變數識別符號不能重新分配),作用域可以是全域性或本地宣告的塊。
4 提升
-
4.1 概念
包括變數和函式在內的所有宣告都會在任何程式碼被執行前首先被處理。 只有宣告本身會被提升,而賦值或其他執行邏輯會留在原地。
-
4.2 變數宣告提升
a = 2; var a; var a; => a = 2; console.log(a); // 2 // ------------------------------------------------------ console.log(b); // undefined var b; var b = 2; => console.log(b); b = 2;
-
4.3 函式宣告提升(函式表示式不會提升)
fn(); // 1 function fn() { console.log(1) } // --------------------------------------------------------------- fn2(); // TypeError: fn2 is not a function (此時fn2 = undefined) var fn2 = function() { console.log(2) } // --------------------------------------------------------------- fn3(); // 3.1 functoin fn3() { // 函式宣告提升 優先於 變數宣告提升 var fn3; console.log(3.1) function fn3() { } console.log(3.1) => fn3() // 3.1 } fn3 = function() { fn3 = function() { console.log(3.2) console.log(3.2) }; } fn3(); // 3.2 fn3(); // 3.2( 函式表示式無提升,會覆蓋前面宣告的函式(注意函式呼叫位置!)) // --------------------------------------------------------------- fn4(); // 4.2 // 普通塊內部的函式宣告,通常會被提升到所在作用域頂部,不受條件判斷控制(應儘量避免這種宣告方法) if (true) { function fn4() { console.log(4.1) } } else { function fn4() { console.log(4.2) } }
5 作用域閉包
-
5.1 概念
當函式可以記住並訪問所在的詞法作用域時,就產生了閉包。 MDN: 閉包是由函式以及建立該函式的詞法環境組合而成。這個環境包含了這個閉包建立時所能訪問的所有區域性變數。
function outer() { var a = 0; function inner() { a++; console.log(a); } return inner; } var add = outer(); add(); // 1 add(); // 2 add(); // 3
-
5.2 迴圈與閉包
例:分別每隔一秒每次一個列印1-5。
for (var i = 1; i <= 5; i ++) { setTimeout(function() { console.log(i) }, 1000 * i) } // 結果是每秒一次的頻率輸出五次6。(迴圈事件結束後,setTimeout才會執行;每次迴圈都引用了i,但i在所處作用域中只有一個,而此時i = 6。) // 使用閉包-------------------------------------------------------------------- for (var i = 1; i <= 5; i++) { (function(j) { setTimeout(function() { console.log(j) }, j * 1000) })(i) } // 新建一個變數------------------------------------------------------------------ for (var i = 1; i <= 5; i ++) { var a = 1; setTimeout(function() { console.log(a++) }, 1000 * i) } // 使用let(每輪迴圈都會根據上輪迴圈的值,重新宣告i,)----------------------------- for (let i = 1; i <= 5; i++) { setTimeout(function() { console.log(i) }, 1000 * i) }
-
5.3 模組
-
5.3.1 概念
必須有外部的封閉函式,該函式必須被至少呼叫一次(每次呼叫都會建立一個新的模組例項); 封閉函式必須返回至少一個內部函式,這樣函式才能在私有作用域中形成閉包,並且可以訪問或修改私有的狀態。
function common() { var str = 'string in common'; function doSomething() { console.log(str) } return { // 也可直接返回一個內部函式 doSomething: doSomething }; } var fn = common(); fn.doSomething(); // string in common
-
5.3.2 將模組轉換成單例模式
var common = (function() { var str = 'string in common'; function doSomething() { console.log(str) } return { // 也可直接返回一個內部函式 doSomething: doSomething }; })(); common.doSomething(); // string in common
-
5.3.3 ES6的模組化
a.js function hello(name) { return 'hello' + name + '!' } export hello; --------------------------------- b.js function () {}
-
6 this
-
6.1 概念
this是在函式被呼叫時發生的繫結,指向什麼完全取決於函式在哪裡被呼叫。
-
6.2 四種繫結規則
-
6.2.1 預設繫結
被直接使用,不帶任何修飾符的函式引用(獨立呼叫)。只能使用預設繫結,此時 this 預設指向全域性物件。 注意(嚴格模式下時,全域性物件無法使用預設繫結)
function foo() { function foo() { console.log(this.a); => "use strict" } console.log(this.a) var a = 1; } foo(); // 1 var a = 2; foo(); // TypeError: Cannot read property 'a' of undefined //----------------------------------------------------------------- 疑問:這裡的 foo()是執行在嚴格模式下,怎麼還能打印出來this.a? 書中原話:雖然 this 的繫結規則完全取決於呼叫位置, 但是隻有 foo() 執行在非 strict mode 下時,預設繫結才能繫結到全域性物件; 嚴格模式下與 foo()的呼叫位置無關。 (PS:還是沒懂。。。) function foo() { console.log( this.a ); } var a = 2; (function(){ "use strict"; foo(); // 2 })();
-
6.2.2 隱式繫結
函式的呼叫位置是否有上下文物件。
function foo() { console.log( this.a ); } var obj2 = { a: 42, foo: foo }; var obj1 = { a: 2, obj2: obj2 }; obj1.obj2.foo(); // 42 (PS:物件屬性鏈中只有最後一層會影響函式的呼叫位置)
隱式丟失:被隱式繫結的函式會丟失繫結物件,從而應用到預設繫結規則。
var obj = { a: "obj inner", foo: function() { console.log(this.a); } }; var bar = obj.foo; // (PS:這裡實際上引用的foo函式本身,所以呼叫bar()時,實際是獨立呼叫) var a = "global"; bar(); // "global" setTimeout(obj.fn, 100); // "global" (原因同上) // -----------------------------------------------------------------------------
-
6.2.3 顯式繫結
直接指定this的繫結物件。(apply/call/bind)
function foo(a,b,c) { console.log(this.a, a, b, c); } var obj = { a: 123 } foo.call(obj, 1,2,3) // 123 1 1 2 3 foo.call(obj, [1,2,3]) // 123 [1,2,3] undefined undefined foo.apply(obj, [1,2,3]); // 123 1 2 3 foo.bind(obj, 1, 2, 3)(); // 123 1 2 3 (PS:apply和call的唯一區別是傳參,apply需要傳一個數組,call需要列舉所有引數; bind會返回一個硬繫結的新函式)
API呼叫的上下文(函式提供了一個可選引數,作為執行的上下文,等同於bind(...))。 例:array.forEach(callback(currentValue[, index[, array]]), [, thisArg])
function foo(el) { console.log( el, this.id ); } var obj = { id: "awesome" }; var arr = [1, 2, 3]; arr.forEach(foo, obj); // 1 "awesome" 2 "awesome" 3 "awesome" arr.forEach(foo.bind(obj)); // 1 "awesome" 2 "awesome" 3 "awesome"
-
6.2.4 new繫結
-