標(biāo)簽: js 還有原型鏈要更新呀
作用域
- 作用域是查找變量的一套規(guī)則
- 如果查找的目的是對(duì)變量進(jìn)行賦值卖氨,則進(jìn)行LHS查詢绳姨;如果查找的目的是獲取變量的值满哪,則進(jìn)行RHS查詢。Left/Right Hand Sidei,變量在等號(hào)左邊或等號(hào)右邊
- LHS查詢:賦值操作躯枢,如=操作符、調(diào)用函數(shù)時(shí)傳參
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,引用一個(gè)未被定義的變量剥纷。
- TypeError,對(duì)結(jié)果的操作不合理呢铆,如引用一個(gè)undefined類型的值的屬性
- SyntaxError晦鞋,語(yǔ)法錯(cuò)誤
- 如何判斷當(dāng)前是否在嚴(yán)格模式下:函數(shù)直接調(diào)用時(shí)this指向運(yùn)行時(shí)環(huán)境的全局對(duì)象。在非嚴(yán)格模式下棺克,瀏覽器中全局對(duì)象為Window悠垛,Nodejs控制臺(tái)中全局對(duì)象為Global;在嚴(yán)格模式下娜谊,全局對(duì)象是undefined确买。因此可判斷如下
var isStrict = (function(){return !this;}());
console.log('isStrict ' + isStrict);
- 編譯。傳統(tǒng)編譯語(yǔ)言纱皆,代碼執(zhí)行前需要編譯拇惋,包括三個(gè)過程:詞法分析(將代碼拆分為詞法單元)周偎、語(yǔ)法解析(將詞法單元數(shù)組轉(zhuǎn)換為語(yǔ)法樹)、代碼生成(生成可執(zhí)行代碼)
詞法作用域VS動(dòng)態(tài)作用域
- 編譯的詞法分析階段知曉各標(biāo)識(shí)符及如何聲明的撑帖,因此可以在執(zhí)行過程中進(jìn)行查找蓉坎。詞法作用域意味著作用域在書寫代碼時(shí)函數(shù)聲明的位置決定。
eval with- 詞法作用域又叫靜態(tài)作用域胡嘿,和動(dòng)態(tài)作用域的區(qū)別是動(dòng)態(tài)作用域是在運(yùn)行時(shí)而非定義時(shí)確定的蛉艾。詞法作用域關(guān)注函數(shù)在何處聲明,動(dòng)態(tài)作用域關(guān)注函數(shù)從何處調(diào)用衷敌,其作用域鏈?zhǔn)腔谶\(yùn)行時(shí)的調(diào)用棧
- wiki:大多數(shù)現(xiàn)在程序設(shè)計(jì)語(yǔ)言都是采用靜態(tài)作用域規(guī)則勿侯,如C/C++、C#缴罗、Python助琐、Java、JavaScript面氓;采用動(dòng)態(tài)作用域的語(yǔ)言有Pascal兵钮、Emacs Lisp、Common Lisp(兼有靜態(tài)作用域)舌界、Perl(兼有靜態(tài)作用域)掘譬。C/C++是靜態(tài)作用域語(yǔ)言,但在宏中用到的名字呻拌,也是動(dòng)態(tài)作用域葱轩。
// 詞法作用域:
function foo() {
print a; // 輸出2
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
// 動(dòng)態(tài)作用域
function foo() {
print a; // 輸出3而不是2
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
函數(shù)作用域
- 最小特權(quán)原則
- 在軟件設(shè)計(jì)中,應(yīng)該最小限度地暴露必要內(nèi)容藐握,而將其隱藏起來靴拱,比如某個(gè)模塊或?qū)ο蟮腁PI設(shè)計(jì)。
- 避免暴露
私有
的變量或函數(shù)猾普,避免其被有意或無意地以非預(yù)期的方式使用缭嫡。
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均應(yīng)是doSomething的內(nèi)部?jī)?nèi)容,改進(jìn):
function doSomething(a){
function doSomethingElse(e){
return e - 1;
}
var b;
b = a + doSomethingElse(a * 2);
console.log(b * 3);
}
doSomething(2); // 15
- 立即執(zhí)行函數(shù)表達(dá)式IIFE 的兩種形式
(function(){console.log('a');})() // a
// 第一個(gè)()將函數(shù)變成表達(dá)式抬闷,第二個(gè)()立即執(zhí)行這個(gè)函數(shù)
+function(){console.log('a');}() // a
// + 也會(huì)將函數(shù)變成一個(gè)表達(dá)式妇蛀,js會(huì)解析成運(yùn)算(bootstrap的js源碼采用此種方式),第二個(gè)()立即執(zhí)行這個(gè)函數(shù)
js中神奇的 +
new Date(); // Fri Aug 05 2016 16:58:08 GMT+0800 (CST)
+ new Date; // 1470387494773 (一個(gè)時(shí)間戳)
// 時(shí)間戳:unix時(shí)間笤成,從`協(xié)調(diào)時(shí)間時(shí)`1970年1月1日0時(shí)0分0秒起至現(xiàn)在的總秒數(shù)
- IIFE進(jìn)階1:當(dāng)作函數(shù)調(diào)用并傳參评架,提高可讀性,改進(jìn)代碼風(fēng)格炕泳。
var a = 2;
(function IIFE(global){
var a = 1;
console.log(a); // 1
console.log(global.a); // 2
})(window);
- IIFE進(jìn)階2:倒置代碼的運(yùn)行順序纵诞,將需要運(yùn)行的代碼放在第二位,在IIFE執(zhí)行后當(dāng)作參數(shù)傳遞進(jìn)去
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
}));
塊級(jí)作用域
let ES6變量聲明
- 隱式綁定在所在塊級(jí)作用域
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
- 顯式創(chuàng)建一個(gè)塊級(jí)作用域
var foo = true;
if (foo) {
{
let bar = foo * 2;
console.log(bar); // 2
} // 一個(gè)顯式的塊
console.log(bar); // ReferenceError: bar is not defined
}
console.log(bar); // ReferenceError: bar is not defined
- let聲明的變量在塊級(jí)作用域中不會(huì)提升
- 垃圾回收培遵,let塊級(jí)作用域中的內(nèi)容執(zhí)行完后被銷毀
- let循環(huán)
for(let i=0;1<10;i++) {
console.log(i);
}
console.log(i); // ReferenceError: i is not defined
- let將i綁定在for循環(huán)的塊中浙芙,不會(huì)污染全局變量登刺,并且將其重新綁定在循環(huán)的每一次迭代中。
const ES6變量聲明
- const聲明的變量只在聲明時(shí)賦值嗡呼,不可修改纸俭,否則會(huì)報(bào)錯(cuò)
- const聲明的變量必須在聲明時(shí)就賦值。
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)
- 理解提升:引擎會(huì)在解釋js代碼前首先進(jìn)行編譯南窗,編譯的一部分工作就是找到所有聲明揍很,并用合適的作用域?qū)⑺麄儼饋怼K新暶鲿?huì)在代碼執(zhí)行前被處理万伤。
- 編譯階段聲明會(huì)被提升窒悔,賦值或其他運(yùn)算邏輯留在原地在執(zhí)行過程中被處理。
- 函數(shù)聲明提升的同時(shí)敌买,包含了實(shí)際函數(shù)的隱藏值简珠。
foo(); // undefined;
function foo() {
console.log(a);
var a = 1;
}
// 此時(shí)foo()可正常調(diào)用,foo()函數(shù)內(nèi)部也有變量提升
- 函數(shù)表達(dá)式和變量聲明類似虹钮,變量標(biāo)識(shí)符會(huì)提升聋庵,賦值沒有被提升
foo(); // TypeError: foo is not a function
var foo = function() {
console.log(a);
var a = 2;
}
// 此時(shí)報(bào)錯(cuò)不是ReferenceError,因?yàn)榇藭r(shí)變量標(biāo)識(shí)符foo被提升到了頂部,var foo;但賦值未被提升芜抒,此時(shí)foo的值是undefined,對(duì)一個(gè)undefined值進(jìn)行函數(shù)調(diào)用是非法操作珍策,因此拋出TypeError異常
- 提升中函數(shù)優(yōu)先
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;是重復(fù)聲明被忽略托启。
foo(); // 此時(shí)再調(diào)用foo()值為2宅倒,第二次定義的函數(shù)表達(dá)式覆蓋了前面的函數(shù)聲明。
- 一個(gè)函數(shù)聲明會(huì)被提升到所在作用域的頂部
foo(); // TypeError: foo is not a function
var a = true;
if (a) {
function foo(){console.log(11);}
} else {
function foo(){console.log(22);} //
}
// 老版本中兩個(gè)foo函數(shù)會(huì)被提升屯耸,第二個(gè)foo函數(shù)會(huì)覆蓋第一個(gè)拐迁,因此foo()執(zhí)行結(jié)果是22;新版本中if塊內(nèi)僅提升函數(shù)聲明疗绣,但未包含實(shí)際函數(shù)的隱藏值线召,因此foo()執(zhí)行結(jié)果是TypeError
作用域閉包
- 閉包:內(nèi)部函數(shù)可以訪問外部函數(shù)作用域,并持有對(duì)原始詞法作用域的引用多矮。
- 經(jīng)典例子:循環(huán)與閉包
for(var i=0;i<5;i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000);
}
- 輸出五次5的原因:循環(huán)中的五個(gè)函數(shù)在每次迭代中分別定義缓淹,但它們被封閉在一個(gè)共享的全局作用域,因此共享一個(gè)i的引用塔逃。
- 解決方法是給每次循環(huán)的函數(shù)創(chuàng)建一個(gè)單獨(dú)的作用域
for(var i=0;i<5;i++) {
(function() {
var j = i;
setTimeout(function timer() {
console.log(j);
}, j*1000);
})()
}
// IIFE為每次迭代創(chuàng)建一個(gè)新的作用域讯壶,延遲函數(shù)的回調(diào)可以將新的作用域封閉在每個(gè)迭代內(nèi)部,每個(gè)迭代都有一個(gè)正確的變量湾盗。
or
for(let i=0;i<5;i++) { // i在每次迭代都會(huì)聲明
setTimeout(function timer() {
console.log(i);
}, i*1000);
}
or
for(var i=0;i<5;i++) {
let j = i; // 創(chuàng)建塊級(jí)作用域
setTimeout(function timer() {
console.log(i);
}, i*1000);
}
this
- 隱式傳遞一個(gè)對(duì)象引用
- 一個(gè)函數(shù)被調(diào)用時(shí)伏蚊,會(huì)創(chuàng)建一個(gè)執(zhí)行上下文,包含函數(shù)在哪里被調(diào)用格粪,函數(shù)的調(diào)用方式躏吊,傳入的參數(shù)等氛改,this是其中的一個(gè)屬性,是函數(shù)被調(diào)用時(shí)發(fā)生的綁定比伏。this既不指向函數(shù)本身也不指向函數(shù)的詞法作用域胜卤。
- 直接調(diào)用 new dot(.) call apply bind
function fo(){this.x='x';};
// 直接調(diào)用
fo(); // ()代表立即執(zhí)行
x; // 'x' this指向全局對(duì)象window
var fn = function(){'use strict';this.x='x';};
x; // ReferenceError: x is not defined,此時(shí)全局對(duì)象是undefined;
// new調(diào)用
var foo = new fo();
foo.x; // 'x'
// dot(.)調(diào)用
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的第一個(gè)參數(shù)
對(duì)象
- javascript六種類型:string number boolean null undefined object
- 內(nèi)置對(duì)象凳怨,對(duì)象子類型:string number boolean object function array date regexp error
- ES6增加了可計(jì)算屬性名瑰艘,用[]包裹,eg:
var prefix = foo;
var myObject = {[prefix + 'bar']: 'hello', [prefix + 'baz']: 'world'};
myObject['foobar']; // hello
myObject['foobaz']; // world
- 數(shù)組也是對(duì)象肤舞,但是最好用對(duì)象存儲(chǔ)鍵值對(duì)紫新,用數(shù)組存儲(chǔ)數(shù)組下標(biāo)值對(duì);
// 給數(shù)組定義數(shù)字形式的屬性名的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操作符檢查屬性是否在對(duì)象中或prototype原型鏈中李剖;hasOwnProperty僅檢查屬性是否在對(duì)象中芒率。
- 屬性描述符 ES5
- getOwnpropertyDescriptor查詢對(duì)象的屬性的屬性描述符,格式:Object.getOwnpropertyDescriptor(對(duì)象名,'屬性名');
Object.getOwnPropertyDescriptor(myObject,'a');
// Object {
// value: 2,
// writable: true, 可寫篙顺,可修改屬性的值value
// enumerable: true, 可枚舉偶芍,出現(xiàn)在對(duì)象屬性的遍歷中
// configurable: true 可配置,可用defineProperty修改屬性描述符德玫,刪除一個(gè)屬性delete myObject.a; if false,除value和writable外的特性不能被改變匪蟀。
// }
- defineProperty添加一個(gè)新屬性或修改一個(gè)已有屬性(的屬性描述符),格式:Object.defineProperty(對(duì)象名,'屬性名',{});
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存在且可正常訪問宰僧,但不出現(xiàn)在屬性的遍歷中
myObject.propertyIsEnumerable('b'); // false
// propertyIsEnumerable檢查給定的屬性名存在于對(duì)象中(不包括原型鏈中)&& 可枚舉的
Object.keys(myObject); // ['a'] 返回可枚舉屬性
Object.getOwnPropertyNames(myObject); // ['a','b'] 返回所有屬性
- 數(shù)組遍歷注意事項(xiàng):數(shù)組上應(yīng)用for..in循環(huán)時(shí)不僅包含所有數(shù)值索引材彪,且包含所有可枚舉屬性;因此遍歷數(shù)組最好使用傳統(tǒng)for循環(huán)或forEach循環(huán)琴儿。ES6中新增for..of循環(huán)遍歷數(shù)據(jù)結(jié)構(gòu)段化。數(shù)組有內(nèi)置的@@iterator,因此for..of可直接遍歷數(shù)組造成,而不能直接遍歷對(duì)象显熏。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