標簽: js 還有原型鏈要更新呀
作用域
- 作用域是查找變量的一套規則
- 如果查找的目的是對變量進行賦值,則進行LHS查詢;如果查找的目的是獲取變量的值,則進行RHS查詢。Left/Right Hand Sidei,變量在等號左邊或等號右邊
- LHS查詢:賦值操作,如=操作符、調用函數時傳參
function foo(e) {
var b = a;
return a + b;
}
var c = foo(2);
- 例子中LHS查詢有三處:b=a c=foo(2) 把2賦給a(隱式變量分配)
- RHS查詢有四處:foo(2) =a a b
- 異常
- ReferenceError,引用一個未被定義的變量。
- TypeError,對結果的操作不合理,如引用一個undefined類型的值的屬性
- SyntaxError,語法錯誤
- 如何判斷當前是否在嚴格模式下:函數直接調用時this指向運行時環境的全局對象。在非嚴格模式下,瀏覽器中全局對象為Window,Nodejs控制臺中全局對象為Global;在嚴格模式下,全局對象是undefined。因此可判斷如下
var isStrict = (function(){return !this;}());
console.log('isStrict ' + isStrict);
- 編譯。傳統編譯語言,代碼執行前需要編譯,包括三個過程:詞法分析(將代碼拆分為詞法單元)、語法解析(將詞法單元數組轉換為語法樹)、代碼生成(生成可執行代碼)
詞法作用域VS動態作用域
- 編譯的詞法分析階段知曉各標識符及如何聲明的,因此可以在執行過程中進行查找。詞法作用域意味著作用域在書寫代碼時函數聲明的位置決定。
eval with- 詞法作用域又叫靜態作用域,和動態作用域的區別是動態作用域是在運行時而非定義時確定的。詞法作用域關注函數在何處聲明,動態作用域關注函數從何處調用,其作用域鏈是基于運行時的調用棧
- wiki:大多數現在程序設計語言都是采用靜態作用域規則,如C/C++、C#、Python、Java、JavaScript;采用動態作用域的語言有Pascal、Emacs Lisp、Common Lisp(兼有靜態作用域)、Perl(兼有靜態作用域)。C/C++是靜態作用域語言,但在宏中用到的名字,也是動態作用域。
// 詞法作用域:
function foo() {
print a; // 輸出2
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
// 動態作用域
function foo() {
print a; // 輸出3而不是2
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
函數作用域
- 最小特權原則
- 在軟件設計中,應該最小限度地暴露必要內容,而將其隱藏起來,比如某個模塊或對象的API設計。
- 避免暴露
私有
的變量或函數,避免其被有意或無意地以非預期的方式使用。
function doSomething(a){
b = a + doSomethingElse(a * 2);
console.log(b * 3);
}
function doSomethingElse(e){
return e - 1;
}
var b;
doSomething(2); // 15
- 上例中b doSomethisElse均應是doSomething的內部內容,改進:
function doSomething(a){
function doSomethingElse(e){
return e - 1;
}
var b;
b = a + doSomethingElse(a * 2);
console.log(b * 3);
}
doSomething(2); // 15
- 立即執行函數表達式IIFE 的兩種形式
(function(){console.log('a');})() // a
// 第一個()將函數變成表達式,第二個()立即執行這個函數
+function(){console.log('a');}() // a
// + 也會將函數變成一個表達式,js會解析成運算(bootstrap的js源碼采用此種方式),第二個()立即執行這個函數
js中神奇的 +
new Date(); // Fri Aug 05 2016 16:58:08 GMT+0800 (CST)
+ new Date; // 1470387494773 (一個時間戳)
// 時間戳:unix時間,從`協調時間時`1970年1月1日0時0分0秒起至現在的總秒數
- IIFE進階1:當作函數調用并傳參,提高可讀性,改進代碼風格。
var a = 2;
(function IIFE(global){
var a = 1;
console.log(a); // 1
console.log(global.a); // 2
})(window);
- IIFE進階2:倒置代碼的運行順序,將需要運行的代碼放在第二位,在IIFE執行后當作參數傳遞進去
var a = 2;
(function IIFE(def){
def(window);
console.log(a); // 2
}(function def(global){
var a = 1;
console.log(a); // 1
console.log(global.a); // 2
}));
塊級作用域
let ES6變量聲明
- 隱式綁定在所在塊級作用域
var foo = true;
if (foo) {
var bar = foo * 2;
console.log(bar); // 2
}
console.log(bar); // 2
var foo = true;
if (foo) {
let bar = foo * 2;
console.log(bar); // 2
}
console.log(bar); // ReferenceError: bar is not defined
- 顯式創建一個塊級作用域
var foo = true;
if (foo) {
{
let bar = foo * 2;
console.log(bar); // 2
} // 一個顯式的塊
console.log(bar); // ReferenceError: bar is not defined
}
console.log(bar); // ReferenceError: bar is not defined
- let聲明的變量在塊級作用域中不會提升
- 垃圾回收,let塊級作用域中的內容執行完后被銷毀
- let循環
for(let i=0;1<10;i++) {
console.log(i);
}
console.log(i); // ReferenceError: i is not defined
- let將i綁定在for循環的塊中,不會污染全局變量,并且將其重新綁定在循環的每一次迭代中。
const ES6變量聲明
- const聲明的變量只在聲明時賦值,不可修改,否則會報錯
- const聲明的變量必須在聲明時就賦值。
var foo = true;
if(foo) {
var a = 2;
const b = 3;
const c = {};
const d; // SyntaxError: Missing initializer in const declaration
a = 3;
b = 4; // TypeError: Assignment to constant variable
c.a = 1;
console.log(c); // {a: 1}
console.log(c.a); // 1
}
console.log(a); // 3
console.log(b); // ReferenceError: b is not defined
提升(hoisting)
- 理解提升:引擎會在解釋js代碼前首先進行編譯,編譯的一部分工作就是找到所有聲明,并用合適的作用域將他們包裹起來。所有聲明會在代碼執行前被處理。
- 編譯階段聲明會被提升,賦值或其他運算邏輯留在原地在執行過程中被處理。
- 函數聲明提升的同時,包含了實際函數的隱藏值。
foo(); // undefined;
function foo() {
console.log(a);
var a = 1;
}
// 此時foo()可正常調用,foo()函數內部也有變量提升
- 函數表達式和變量聲明類似,變量標識符會提升,賦值沒有被提升
foo(); // TypeError: foo is not a function
var foo = function() {
console.log(a);
var a = 2;
}
// 此時報錯不是ReferenceError,因為此時變量標識符foo被提升到了頂部,var foo;但賦值未被提升,此時foo的值是undefined,對一個undefined值進行函數調用是非法操作,因此拋出TypeError異常
- 提升中函數優先
foo(); // 1
var foo;
function foo() {
console.log(1);
}
foo = function() {
console.log(2);
}
引擎解釋:
function foo() {
console.log(1);
}
foo();
foo = function() {
console.log(2);
}
// var foo;是重復聲明被忽略。
foo(); // 此時再調用foo()值為2,第二次定義的函數表達式覆蓋了前面的函數聲明。
- 一個函數聲明會被提升到所在作用域的頂部
foo(); // TypeError: foo is not a function
var a = true;
if (a) {
function foo(){console.log(11);}
} else {
function foo(){console.log(22);} //
}
// 老版本中兩個foo函數會被提升,第二個foo函數會覆蓋第一個,因此foo()執行結果是22;新版本中if塊內僅提升函數聲明,但未包含實際函數的隱藏值,因此foo()執行結果是TypeError
作用域閉包
- 閉包:內部函數可以訪問外部函數作用域,并持有對原始詞法作用域的引用。
- 經典例子:循環與閉包
for(var i=0;i<5;i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000);
}
- 輸出五次5的原因:循環中的五個函數在每次迭代中分別定義,但它們被封閉在一個共享的全局作用域,因此共享一個i的引用。
- 解決方法是給每次循環的函數創建一個單獨的作用域
for(var i=0;i<5;i++) {
(function() {
var j = i;
setTimeout(function timer() {
console.log(j);
}, j*1000);
})()
}
// IIFE為每次迭代創建一個新的作用域,延遲函數的回調可以將新的作用域封閉在每個迭代內部,每個迭代都有一個正確的變量。
or
for(let i=0;i<5;i++) { // i在每次迭代都會聲明
setTimeout(function timer() {
console.log(i);
}, i*1000);
}
or
for(var i=0;i<5;i++) {
let j = i; // 創建塊級作用域
setTimeout(function timer() {
console.log(i);
}, i*1000);
}
this
- 隱式傳遞一個對象引用
- 一個函數被調用時,會創建一個執行上下文,包含函數在哪里被調用,函數的調用方式,傳入的參數等,this是其中的一個屬性,是函數被調用時發生的綁定。this既不指向函數本身也不指向函數的詞法作用域。
- 直接調用 new dot(.) call apply bind
function fo(){this.x='x';};
// 直接調用
fo(); // ()代表立即執行
x; // 'x' this指向全局對象window
var fn = function(){'use strict';this.x='x';};
x; // ReferenceError: x is not defined,此時全局對象是undefined;
// new調用
var foo = new fo();
foo.x; // 'x'
// dot(.)調用
var aa = {x: 'aaa',fo: fo};
aa.x; // 'aaa'
aa.fo; // function fo(){this.x='x';};
aa.fo();
aa.x; // 'x'
// call
var b = {x: 'bb'};
b.x; // 'bb'
b.fo(); // TypeError: b.fo is not a function
fo.call(b);
b.x; // 'x'
// apply bind 用法和call相似,this均指向fo.call(args),args的第一個參數
對象
- javascript六種類型:string number boolean null undefined object
- 內置對象,對象子類型:string number boolean object function array date regexp error
- ES6增加了可計算屬性名,用[]包裹,eg:
var prefix = foo;
var myObject = {[prefix + 'bar']: 'hello', [prefix + 'baz']: 'world'};
myObject['foobar']; // hello
myObject['foobaz']; // world
- 數組也是對象,但是最好用對象存儲鍵值對,用數組存儲數組下標值對;
// 給數組定義數字形式的屬性名的bug
var myArray = ['hello', 'world', 42];
myArray.baz = 'bar';
myArray.length; // 3
myArray.baz; // 'bar'
myArray['3'] = 'test';
myArray.length; // 4
myArray; // ['hello', 'world', 42, 'test']
- 檢查屬性是否存在
var myObject = { a: 2 };
myObject['b']; // 2
myObject.b; // 2
'a' in myObject; // true
myObject.hasOwnProperty('a'); // true
// in操作符檢查屬性是否在對象中或prototype原型鏈中;hasOwnProperty僅檢查屬性是否在對象中。
- 屬性描述符 ES5
- getOwnpropertyDescriptor查詢對象的屬性的屬性描述符,格式:Object.getOwnpropertyDescriptor(對象名,'屬性名');
Object.getOwnPropertyDescriptor(myObject,'a');
// Object {
// value: 2,
// writable: true, 可寫,可修改屬性的值value
// enumerable: true, 可枚舉,出現在對象屬性的遍歷中
// configurable: true 可配置,可用defineProperty修改屬性描述符,刪除一個屬性delete myObject.a; if false,除value和writable外的特性不能被改變。
// }
- defineProperty添加一個新屬性或修改一個已有屬性(的屬性描述符),格式:Object.defineProperty(對象名,'屬性名',{});
Object.defineProperty(myObject,'b',{
value: 3,
writable: true,
configurable: true,
enumerable: true
}); // Object {a: 2, b: 3}
Object.getOwnPropertyDescriptor(myObject,'b');
// Object {value: 3, writable: true, enumerable: true, configurable: true}
- 枚舉屬性 enumerable
var myObject = {};
Object.defineProperty(myObject,'a',{
value: 2,
enumerable: true
});
Object.defineProperty(myObject,'b',{
value: 3,
enumerable: false
});
myObject.b; // 3
'b' in myObject; // true
myObject.hasOwnProperty('b'); // true
for(var k in myObject) {
console.log(k+': '+myObject[k]);
} // a: 2
// myObject.b存在且可正常訪問,但不出現在屬性的遍歷中
myObject.propertyIsEnumerable('b'); // false
// propertyIsEnumerable檢查給定的屬性名存在于對象中(不包括原型鏈中)&& 可枚舉的
Object.keys(myObject); // ['a'] 返回可枚舉屬性
Object.getOwnPropertyNames(myObject); // ['a','b'] 返回所有屬性
- 數組遍歷注意事項:數組上應用for..in循環時不僅包含所有數值索引,且包含所有可枚舉屬性;因此遍歷數組最好使用傳統for循環或forEach循環。ES6中新增for..of循環遍歷數據結構。數組有內置的@@iterator,因此for..of可直接遍歷數組,而不能直接遍歷對象。P122
var myArray = ['hello','world',42];
myArray.baz = 'bar';
myArray['3']='test';
myArray; // ["hello", "world", 42, "test"]
for(var k in myArray) {console.log(k,myArray[k]);}
// 0 hello 1 world 2 42 3 test baz bar
for(vari=0;i<myArray.length;i++){console.log(i,myArray[i])};
// 0 hello 1 world 2 42 3 test
myArray.forEach(function(e){console.log(e)});
// hello world 42 test
for(var v of myArray){console.log(v);};
// hello world 42 test
for(var v of myObject){console.log(v);};
// TypeError: myObject[Symbol.iterator] is not a function