JavaScript中全部變量都是對象,除了null和undefinedjavascript
JavaScript對象能夠做爲哈希表使用,主要用來保存命名的鍵和值的對應關係html
點操做符和中括號操做符java
中括號操做符在下面2種狀況下依然有效 1.動態設置屬性 2.屬性名不是一個有效的變量名數組
刪除屬性的惟一方法是使用delete操做符;設置屬性爲undefined或者null並不能真正的刪除屬性,只是移除了屬性和值的關聯瀏覽器
對象的屬性名可使用字符串或者普通字符(非關鍵詞)聲明。安全
注:簡單的使用Bar.prototype = Foo.prototype將會致使兩個對象共享相同的原型。閉包
function Foo(){app
this.value = 42;函數
}性能
Foo.ptototype = {
method : function() {}
};
function Bar() {}
//設置Bar的prototype屬性爲Foo的實例對象
Bar.prototype = new Foo();
Bar.prototype.foo = "Hello World";
//修正Bar.prototype.constructor爲Bar自己
Bar.prototype.constructor = Bar;
注:不要使用Bar.prototype = Foo, 由於這不會執行Foo的原型,而是指向函數Foo。所以原型鏈將會回溯到Function.prototype而不是Foo.prototype。
查找對象屬性時,JavaScript會向上遍歷原型鏈,直到找到給定名稱的屬性爲止。
到查找到達原型鏈的頂部(Object.prototype),可是仍然沒有找到指定的屬性,就會返回undefined。
當原型屬性用來建立原型鏈時,能夠把任何類型的值賦給它。然而將原子類型賦給prototype的操做將會被忽略。
若是一個屬性在原型鏈的頂端,則對於查找時間將帶來不利影響。
當使用 for - in 循環遍歷對象的屬性時,原型鏈上的全部屬性都將被訪問。
一個錯誤特性被常用,那就是擴展Object.prototype或者其餘內置類型的原型對象。
這種技術會破壞封裝。
擴展內置類型的惟一理由是爲了和新的JavaScript保持一致。好比Arrary.forEach。
函數聲明會在執行前被解析(hoisted),所以它存在於當前上下文的任意一個地方,即便在函數定義體的上面被調用也是對的。
foo();
function foo(){}
foo;
foo();
var foo = function(){};
因爲var定義了一個聲明語句,對變量foo的解析是在代碼運行以前,所以foo變量在代碼運行時已經被定義過了。
可是因爲賦值語句只在運行時執行,所以在相應代碼執行以前,foo的值缺省爲undefined。
hasOwnProperty是JavaScript中惟一一個處理屬性可是不須要查找原型鏈的方法。
JavaScript不會保護hasOwnProperty被非法佔用,所以若是一個對象碰巧存在這個屬性,就須要使用外部的hasOwnProperty函數來獲取正確的結果。
{}.hasOwnProperty.call(foo, 'bar');
當在所有範圍內使用this,它將會指向全局對象。(瀏覽器中運行的JavaScript腳本,這個全局對象是window)
foo();
此時this指向全局對象。
ES5 注意: 在嚴格模式下(strict mode),不存在全局變量。這種狀況下this將會是undefined。
4.3 方法調用
this指向調用該方法的對象
this指向新建立的對象。
call 或者 apply 方法時,函數內的 this將會被顯式設置爲函數調用的第一個參數,若爲null則指向全局對象
將一個方法賦值給一個變量。
var test = someObject.methodTest;
test();
此時,test就像一個普通的函數被調用;所以,函數內的this將再也不被指向到someObject對象。(晚綁定)
function Counter(start) {
var count = start;
return {
increment: function() {
count++;
},
get: function() {
return count;
}
}
}
var foo = Counter(4);
foo.increment();
foo.get(); // 5
這裏,Counter函數返回兩個閉包,函數increment和函數get。這兩個函數都維持着對外部做用域Counter的引用,所以總能夠訪問此做用域內定義的變量count。
由於JavaScript中不能夠對做用域進行引用或賦值,所以沒有辦法在外部訪問count變量。惟一的途徑就是經過那兩個閉包。
for(var i = 0; i < 10; i++ ){
setTimeout(function(){
console.log(i);
}, 1000);
}
當console.log被調用的時候,匿名函數保持對外部變量i的引用,此時for循環已經結束,i的值被修改爲10。
爲了獲得想要的結果,須要在每次循環中建立變量i的拷貝。
使用自執行匿名函數
for(var i = 0; i < 10; i++){
(function(e){
setTimeout(function(){
console.log(e);
}, 1000);
})(i);
}
JavaScript中每一個函數內都能訪問arguments。它維護着全部傳遞到這個函數中的參數列表。
經過var關鍵字定義arguments或者將arguments聲明爲一個形式參數,都將致使原生的arguments不會被建立。
arguments變量不是一個數組,儘管在語法上有個數組相關的屬性length。
Array.prototype.slice.call(arguments)
function foo(){
bar.apply(null, arguments);
}
function bar(a, b, c){
//some work
}
arguments對象總會被建立,除了兩個特殊狀況 - 做爲局部變量聲明和做爲形式參數。
使用arguments.callee會顯著的影響現代JavaScript引擎的性能。
ES5提示:在嚴格模式下,arguments.callee會報錯TypeError,由於它已經被廢除了。
在構造函數內部,this指向新建立的對象Object。這個新建立的對象的prototype被指向到構造函數的prototype。
被調用的函數沒有顯示的return表達式,則隱式的會返回this對象。
顯示的return表達式將會影響返回結果,但僅限於返回的是一個對象。
function Bar(){
return 2;
}
new Bar(); //返回新建立的對象 new Bar().constructor === Bar
function Foo(){
this.value = 2;
return {
foo : 1
};
}
new Foo(); //返回的對象 {foo:1}
function Foo(){
var obj = {};
obj.value = "blue";
var privateVal = 2;
obj.someMethod = function(value){
this.value = value;
}
obj.getPrivate = function(){
return privateVal;
}
return obj;
}
優勢:充分利用私有變量,比起使用構造函數方式不容易出錯。
缺點:佔用更多的內存,新建立的對象不能共享原型上的方法。
爲了實現繼承,工廠方法須要從另一個對象拷貝全部屬性,或者把一個對象做爲新建立對象的原型。
放棄原型鏈僅僅是由於防止遺漏new帶來的問題,這彷佛和語言自己的思想一想違背。
JavaScript不支持塊級做用域,而僅僅支持函數做用域。
注意: 若是不是在賦值語句中,而是在return表達式或者函數參數中,{...}
將會做爲代碼段解析, 而不是做爲對象的字面語法解析。若是考慮到自動分
號插入,這可能會致使一些不易察覺的錯誤。若是return對象的左括號和return不在一行上就會出錯。
兩種方式聲明,一個事做爲函數參數,另外一個是經過var關鍵字聲明。
JavaScript會提高變量聲明。這意味着var表達式和function聲明都將會被提高到當前做用域的頂部。
當訪問函數內的foo變量時,JavaScript會按照下面順序查找:
1.當前做用域內是否有var foo的定義。
2.函數形式參數是否有使用foo名稱的。
3.函數自身是否叫作foo。
4.回溯到上一級做用域,而後從 1 從新開始
經過當即執行的匿名函數解決命名衝突的問題。
(function(){
//do some work
})();
使用for - in循環,會查詢對象原型鏈上的全部屬性,所以須要使用hasOwnProperty函數來過濾,比普通for循環慢上好幾倍。
length屬性的getter方式會簡單的返回數組的長度,而setter方式會截斷數組。
var foo = [1, 2, 3, 4, 5, 6];
foo.length = 3;
foo; // [1, 2, 3]
foo.length = 6;
foo; // [1, 2, 3]
譯者注: 在 Firebug 中查看此時 foo 的值是: [1, 2, 3, undefined,
undefined, undefined] 可是這個結果並不許確,若是你在 Chrome 的控制檯
查看 foo 的結果,你會發現是這樣的: [1, 2, 3] 由於在 JavaScript 中
undefined 是一個變量,注意是變量不是關鍵字,所以上面兩個結果的意義是
徹底不相同的。
[1, 2, 3]; // 結果: [1, 2, 3]
new Array(1, 2, 3); // 結果: [1, 2, 3]
[3]; // 結果: [3]
new Array(3); // 結果: []
new Array('3') // 結果: ['3']
因爲只有一個參數傳遞到構造函數中(譯者注:指的是 new Array(3); 這種調
用方式),而且這個參數是數字,構造函數會返回一個 length 屬性被設置爲
此參數的空數組。 須要特別注意的是,此時只有 length 屬性被設置,真正的
數組並無生成。
等於操做符由兩個等號組成:==
JavaScript 是弱類型語言,這就意味着,等於操做符會爲了比較兩個值而進行強制類型轉換。
"" == "0" // false
0 == "" // true
0 == "0" // true
false == "false" // false
false == "0" // true
false == undefined // false
false == null // false
null == undefined // true
" \t\r\n" == 0 // true
此外,強制類型轉換也會帶來性能消耗,好比一個字符串爲了和一個數組進行比較,必須事先被強制轉換爲數字。
嚴格的等於操做符由三個等號組成:===
不想普通的等於操做符,嚴格的等於操做符不會進行強制類型轉換。
"" === "0" // false
0 === "" // false
0 === "0" // false
false === "false" // false
false === "0" // false
false === undefined // false
false === null // false
null === undefined // false
" \t\r\n" === 0 // false
雖然 == 和 === 操做符都是等於操做符,可是當其中有一個操做數爲對象時,行爲就不一樣了。
{} === {}; // false
new String('foo') === 'foo'; // false
new Number(10) === 10; // false
var foo = {};
foo === foo; // true
這裏等於操做符比較的不是值是否相等,而是是否屬於同一個身份;也就是說,
只有對象的同一個實例才被認爲是相等的。 這有點像 Python 中的 is 和 C
中的指針比較。
Value Class Type
-------------------------------------
"foo" String string
new String("foo") String object
1.2 Number number
new Number(1.2) Number object
true Boolean boolean
new Boolean(true) Boolean object
new Date() Date object
new Error() Error object
[1,2,3] Array object
new Array(1, 2, 3) Array object
new Function("") Function function
/abc/g RegExp object (function in Nitro/V8)
new RegExp("meow") RegExp object (function in Nitro/V8)
{} Object object
new Object() Object object
Class一列表示對象的內部屬性[[Class]]的值。
爲了獲取對象的[[Class]],咱們須要使用定義在 Object.prototype上的方法toString。
JavaScript 標準文檔中定義: [[Class]] 的值只多是下面字符串中的一個:
Arguments, Array, Boolean, Date, Error, Function, JSON, Math, Number, Object, RegExp, String。
JavaScript標準文檔只給出了一種獲取[[Class]]值的方法,那就是使用Object.prototype.toString。
function is(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas === type;
}
is('String', 'test'); // true
is('String', new String('test')); // true
ES5 提示: 在 ECMAScript 5 中,爲了方便,對 null 和 undefined 調用
Object.prototype.toString方法,其返回值由Object變成了Null和Undefined。
instanceof操做符用來比較兩個操做數的構造函數。只有在比較自定義的對象時纔有意義。若是用來比較內置類型,將會和typeof操做符同樣用處不大。
function Foo() {}
function Bar() {}
Bar.prototype = new Foo();
new Bar() instanceof Bar; // true
new Bar() instanceof Foo; // true
// 若是僅僅設置Bar.prototype爲函數Foo自己,而不是Foo構造函數的一個實例
Bar.prototype = Foo;
new Bar() instanceof Foo; // false
new String('foo') instanceof String; // true
new String('foo') instanceof Object; // true
'foo' instanceof String; // false
'foo' instanceof Object; // false
new Number(10) === 10; // False, 對象與數字的比較
Number(10) === 10; // True, 數字與數字的比較
new Number(10) + 0 === 10; // True, 因爲隱式的類型轉換
使用內置類型Number做爲構造函數將會建立一個新的Number對象,而在不使用new關鍵字的Number函數更像是一個數字轉換器.
最好的選擇是把要比較的值顯式的轉換爲三種可能的類型之一。
'' + 10 === '10'; // true
將一個值加上空字符串能夠輕鬆轉換爲字符串類型。
+'10' === 10; // true
使用一元的加號操做符,能夠把字符串轉換爲數字。
其餘字符串轉換爲數字的經常使用方法:
+'010' === 10
Number('010') === 10
parseInt('010', 10) === 10 // 用來轉換爲整數
+'010.2' === 10.2
Number('010.2') === 10.2
parseInt('010.2', 10) === 10
經過使用否操做符兩次,能夠把一個值轉換爲布爾型。
!!'foo'; // true
!!''; // false
!!'0'; // true
!!'1'; // true
!!'-1' // true
!!{}; // true
!!true; // true
在任何狀況下咱們都應該避免使用eval函數。99.9%使用eval的場景都有不使用eval的解決方案。
eval 也存在安全問題,由於它會執行任意傳給它的代碼,在代碼字符串未知或者是來自一個不信任的源時,絕對不要使用eval函數。
undefined是一個值爲undefined的類型。
下面的狀況會返回 undefined 值:
訪問未修改的全局變量 undefined。
因爲沒有定義 return 表達式的函數隱式返回。
return 表達式沒有顯式的返回任何內容。
訪問不存在的屬性。
函數參數沒有被顯式的傳遞值。
任何被設置爲 undefined 值的變量。
因爲全局變量undefined只是保存了undefined類型實際值的副本,所以對它賦新值不會改變類型undefined的值。
爲了不可能對 undefined 值的改變,一個經常使用的技巧是使用一個傳遞到匿名包裝器的額外參數。在調用時,這個參數不會獲取任何值。
var undefined = 123;
(function(something, foo, undefined) {
// 局部做用域裏的 undefined 變量從新得到了 `undefined` 值
})('Hello World', 42);
另一種達到相同目的方法是在函數內使用變量聲明。
var undefined = 123;
(function(something, foo) {
var undefined;
...
})('Hello World', 42);
JavaScript 中的 undefined 的使用場景相似於其它語言中的 null,實際上JavaScript 中的 null 是另一種數據類型。
它在 JavaScript 內部有一些使用場景(好比聲明原型鏈的終結Foo.prototype = null ),可是大多數狀況下均可以使用 undefined 來代替。
16.setTimeout 和 setInterval
基於JavaScript引擎的計時策略,以及本質上的單線程運行方式,因此其它代碼的運行可能會阻塞此線程。所以無法確保函數會在setTimeout指定的時刻被調用。
做爲第一個參數的函數將會在全局做用域中執行,所以函數內的 this 將會指向這個全局對象。
function Foo() {
this.value = 42;
this.method = function() {
// this 指向全局對象
console.log(this.value); // 輸出:undefined
};
setTimeout(this.method, 500);
}
new Foo();
注意: setTimeout的第一個參數是函數對象,一個常犯的錯誤是這樣的setTimeout(foo(), 1000) , 這裏回調函數是foo的返回值,而不是foo自己。
大部分狀況下,這是一個潛在的錯誤,由於若是函數返回undefined,setTimeout也不會報錯。
當回調函數的執行被阻塞時,setInterval 仍然會發布更多的毀掉指令。在很小的定時間隔狀況下,這會致使回調函數被堆積起來。
function foo(){
// 阻塞執行 1 秒
}
setInterval(foo, 100);
上面代碼中,foo 會執行一次隨後被阻塞了一分鐘。
在 foo 被阻塞的時候,setInterval 仍然在組織未來對回調函數的調用。 所以,當第一次 foo 函數調用結束時,已經有10次函數調用在等待執行。
最簡單也是最容易控制的方案,是在回調函數內部使用 setTimeout 函數
function foo(){
// 阻塞執行 1 秒
setTimeout(foo, 100);
}
foo();
能夠經過將定時時產生的 ID 標識傳遞給clearTimeout或者clearInterval函數來清除定時。
setTimeout 和 setInterval 也接受第一個參數爲字符串的狀況。 這個特性絕對不要使用,由於它在內部使用了 eval。
詳情可見:JavaScript祕密花園