整理的知識點不全面但是很實用。 主要分三塊: (1)JS代碼預解析原理(包括三個段落); (2)函數相關(包括 函數傳參,帶參數函數的調用方式,閉包); (3)面向對象(包括 對象創建、原型鏈,數據類型的檢測,繼承)。 JS代碼預解析原理 函數相關(包括 函數傳參,帶參數函數的調用方式,閉包) 面向 ...
整理的知識點不全面但是很實用。
主要分三塊:
(1)JS代碼預解析原理(包括三個段落);
(2)函數相關(包括 函數傳參,帶參數函數的調用方式,閉包);
(3)面向對象(包括 對象創建、原型鏈,數據類型的檢測,繼承)。
JS代碼預解析原理
/****************** JS代碼預解析原理 ******************/
/*
JS代碼預解析、變數作用域、作用域鏈等 應該能作為學習JS語言的入門必備知識。
下邊給出些簡要解釋和一些典型的代碼段,若要瞭解更多,能從網上搜索到更多相關示例。
引用網上的一段有關 “JS的執行順序” 的解釋:
如果一個文檔流中包含多個script代碼段(用script標簽分隔的js代碼或引入的js文件),它們的運行順序是:
步驟1. 讀入第一個代碼段(js執行引擎並非一行一行地執行程式,而是一段一段地分析執行的)
步驟2. 做語法分析,有錯則報語法錯誤(比如括弧不匹配等),並跳轉到步驟5
步驟3. 對var變數和function定義做“預解析”(永遠不會報錯的,因為只解析正確的聲明)
步驟4. 執行代碼段,有錯則報錯(比如變數未定義)
步驟5. 如果還有下一個代碼段,則讀入下一個代碼段,重覆步驟2
步驟6. 結束
*/
// 下邊給出 三段覺得比較典型的代碼示例:
/********** 一:基本的幾條語句 **********/
alert(num); // undefined
var num = 0;
alert(str); // 錯誤:str未定義
str = "string";
alert(func); // undefined
var func = function (){ alert('exec func'); }
test(); // exec test
alert(test()); // 先exec test 後undefined
function test(){ alert('exec test'); }
/********** 二:函數名與變數名相同 **********/
//var mark = 1;
function mark(x) {
return x * 2;
}
var mark;
alert(mark); // function mark(x) { return x * 2; }
// 去掉前邊的var mark = 1;則會返回1
/********** 三:把第二段包括在語句塊中 **********/
// 當有條件時候(代碼包含在條件語句塊里)
if (false) {
var mark1 = 1;
function mark1() {
alert("exec mark1");
}
//var mark1;
alert(mark1);
}
alert(mark1);
mark1();
// 由於解析瀏覽器解析不同,這段代碼在不同瀏覽器里執行的結果不一致,具體原因可從網上查找答案
函數相關(包括 函數傳參,帶參數函數的調用方式,閉包)
/****************** 函數相關 ******************/
/********** 一:函數傳參 **********/
/*
編程語言大概都有 值類型與引用類型 的區別,JS也不例外。
原始類型:undefined null number boolean 均為值類型。
string比較特殊,因為它是不可改變的,String類定義的方法都不能改變字元串的內容。
function object array 這三種為引用類型。
*/
/* JavaScript 函數傳遞參數時,是值傳遞。
ECMAScript 中,所有函數的參數都是按值來傳遞的。
基本類型值的傳遞和基本類型變數複製一致(採用在棧內新建值),
引用類型值的傳遞和引用類型變數的複製一致(棧記憶體放的是指針,指向堆中同一對象)。
具體參考:http://www.xiaoxiaozi.com/2010/03/05/1719/
*/
function setName(obj){
//obj拷貝了person的值(person是一個對象的引用地址),所以obj也指向了person所指向的對象。
obj.name = "xiaoxiaozi";
obj = {}; // 讓obj 指向了另一個對象
obj.name = "admin";
}
var person = {};
setName(person);
alert(person.name); // xiaoxiaozi
/********** 二:帶參數函數的調用方式 **********/
/* 在DOM不同版本中,函數調用方式不太一樣。標準推薦的是addEventListener和attachEvent
這兩種方式有很多資料可查。但是有些已經不被推薦的函數調用仍舊有實際應用,相關資料發現的不多。
這裡主要討論這些函數調用方式
*/
var g = "全局變數";
function show(str) {
alert("my site: " + str);
}
setTimeout("show(g);",100); // g是全局變數,函數正確執行
function t() {
var url = "www.xujiwei.cn";
var num = 2;
//setTimeout("alert("+url+")", 3000); // 解析錯誤,www未定義
//setTimeout("alert("+num+")", 3000); // 解析正確,註意與上句對比
//setTimeout("show('url');", 2000); // url
//setTimeout("show("+ url +");", 2000); // 解析錯誤,www未定義
//setTimeout("show(url);", 2000); // 解析錯誤,url未定義
//setTimeout('"show("+ url +");"', 2000); // 解析錯誤,url未定義
//setTimeout("show('"+ url +"');", 2000); // 正確
//setTimeout(function(){show(url);},1000); // 正確
}
t();
/* 結論:
諸如onclick="xx();"等函數調用方式,在雙引號內的內容直接解析為js語句執行。
若調用的函數帶有參數,註意對比以上各種寫法,保證傳遞進去的參數為正確的。
*/
/********** 三:閉包 **********/
/*
閉包,幾乎是每個學習JS的朋友都要討論的問題,因此各種相關資料應有盡有。
它的作用很大,但也有弊端,例如如果使用不當,容易引起記憶體泄漏等問題,因此有不少人
提倡少用閉包。
這裡列出閉包的一種經典應用,一個有爭議的應用。
*/
function test1() { //通過閉包,每次能傳入不同的j值。
for (var j = 0; j < 3; j++) {
(function (j) {
setTimeout(function () { alert(j) }, 3000);
})(j);
}
}
test1();
/* 這個是閉包的典型應用 */
(function tt() {
for (var i = 1; i < 4; i++) {
document.getElementById("b" + i).attachEvent("onclick",
new Function('alert("This is button' + i + '");')); // 在IE中測試
}
})() // 立即執行函數,一個文件是否只能有一個?把上邊函數寫成立即執行出問題,怎麼回事?
/* 這個問題出現在論壇里,有很多爭議
有說是new Function動態生成個閉包結構的函數,所以能保存外部變數。
有說是跟閉包無關,new Function,就是新定義了一個function,
i的值也作為這個新的function的參數固化在其內部了。
*/
面向對象(包括 對象創建、原型鏈,數據類型的檢測,繼承)
/****************** 面向對象 ******************/
/********** 一:對象創建、原型鏈 **********/
/* 討論 構造函數(類方式)創建對象 ,深入理解這些內容,是很重要的
*/
function MyFunc() { }; //定義一個空函數
var anObj = new MyFunc(); //使用new操作符,藉助MyFun函數,就創建了一個對象
// 等價於:
function MyFunc() { };
var anObj = {}; //創建一個對象
anObj.__proto__ = MyFunc.prototype;
MyFunc.call(anObj); //將anObj對象作為this指針調用MyFunc函數
/*
用 var anObject = new aFunction() 形式創建對象的過程實際上可以分為三步:
第一步:建立一個新對象(anObject);
第二步:將該對象內置的原型對象(__proto__)設置為構造函數prototype引用的那個原型對象;
第三步:將該對象作為this參數調用構造函數,完成成員設置等初始化工作。
對象建立之後,對象上的任何訪問和操作都只與對象自身及其原型鏈上的那串對象有關,
與構造函數再扯不上關係了。
換句話說,構造函數只是在創建對象時起到介紹原型對象和初始化對象兩個作用。
原型鏈:(參考:http://hi.baidu.com/fegro/blog/item/41ec7ca70cdb98e59152eed0.html)
每個對象(此處對象應該僅指大括弧括起來的object,不包括function、array。待驗證?)
都會在其內部初始化一個屬性,就是__proto__,當我們訪問一個對象的屬性時,
如果這個對象內部不存在這個屬性,那麼他就會去__proto__里找這個屬性,
這個__proto__又會有自己的__proto__,於是就這樣 一直找下去,也就是我們平時所說的原型鏈的概念。
*/
/* 理解了對象創建的原理,可試著分析下邊兩個示例的結果 */
var yx01 = new function() {return "圓心"};
alert(yx01); // [object Object]
var yx02 = new function() {return new String("圓心")};
alert(yx02); // “圓心”
/* 解釋:
"圓心"是基本的字元串類型,new String("圓心")則創建了一個string對象。
只要new表達式之後的構造函數返回一個引用對象(數組,對象,函數等),都將覆蓋new創建的對象,
如果返回一個原始類型(無 return 時其實為 return 原始類型 undefined),
那麼就返回 new 創建的對象。
參考:http://www.planabc.net/2008/02/20/javascript_new_function/
*/
/********** 二:數據類型的檢測 **********/
/* 判斷數據類型可能想到的方法:
constructor、typeof、instanceof、Object.prototype.toString.call()
*/
/***** 1、通過constructor屬性 *****/
var myvar= new Array("a","b","c","d");
function A(){}
myvar.constructor = A;
var c = myvar.constructor;
alert(c); // function A(){}
//可見,通過constructor屬性獲取類型的方法很容易被修改,不應該用來判斷類型。
/***** 2、通過typeof *****/
/*
typeof是一個操作符,而不是個函數。
typeof的實際應用是用來檢測一個對象是否已經定義或者是否已經賦值。
如if(typeof a!="undefined"){},而不要去使用if(a)因為如果a不存在(未聲明)則會出錯。
typeof檢測對象類型時一般只能返回如下幾個結果:
number,boolean,string,function,object,undefined。
對於Array,Null,自定義對象 等使用typeof一律返回object,
這正是typeof的局限性。
*/
var num = new Number(1);
var arr = [1,2,3];
alert(typeof num); //object 而不是number
alert(typeof arr); //object 而不是Array
alert(typeof null); // object
/***** 3、通過 instanceof *****/
/* 用instanceof操作符來判斷對象是否是某個類的實例。
如果obj instanceof Class返回true,那麼Class的原型與obj原型鏈上的某個原型是同一個對象,
即obj要麼由Class創建,要麼由Class的子類創建。
*/
function t(){};
t.prototype = Array.prototype;
//t.prototype = [];
var x = new t();
alert(x instanceof t);//彈出true
alert(x instanceof Array);//彈出true
alert(x instanceof Object);//彈出true
/*
由此可知,通過 instanceof 判斷數據類型也不可靠。
因為一個對象(此處x)的原型鏈可以很長,每個原型的類型可以不同。
另外在iframe內也會容易出錯:
即有個頁面定義了一個數組a,頁面又嵌套了一個IFrame,在Iframe裡面通過 top.a instanceof Array, 是返回false的。
這個說明 父頁面和內嵌iframe里的對象是不同的,不能混合在一起使用。
改成top.a instanceof top.Array 就會返回true
*/
/***** 4、通過 Object.prototype.toString.call() *****/
/*
Object.prototype.toString.call() 作用是:
1、獲取對象的類名(對象類型)。
2、然後將[object、獲取的類名]組合併返回。
可應用於判斷Array,Date,Function等類型的對象
*/
var num = new Number(1);
var arr = [1,2,3];
alert(Object.prototype.toString.call(num)); // [object Number]
alert(Object.prototype.toString.call(arr)); // [object Array]
// 擴展示例:(apply等價於call)
window.utils = {
toString: Object.prototype.toString,
isObject: function (obj) {
return this.toString.apply(obj) === '[object Object]';
},
isFunction: function (obj) {
return this.toString.apply(obj) === '[object Function]';
},
isArray: function (obj) {
return this.toString.apply(obj) === '[object Array]';
}
}
function A() { }
window.utils.isFunction(A); //true
window.utils.isObject(new A()); //true
window.utils.isArray([]); //true
/*
jQuery等框架 就是用這個方法判斷對象的類型的,因此可以把這種方法作為權威的判斷方法。
但是,如果重寫了Object.prototype.toString方法,這時候再用來判斷數據類型可能就會出錯,
所以,一般不要去重寫Object.prototype.toString方法。
*/
/********** 三:繼承 **********/
/*
JS繼承和閉包一樣,幾乎是每個想深入學習JS的朋友都要討論的問題,因此各種相關資料應有盡有。
JS繼承代碼的版本非常多,但原理都是一樣的,核心都是利用了prototype對象。
為了和其他面向對象語言的風格相似,大多數都採用“類式”風格模擬。
繼承的詳細原理不再贅述,網上有許多資料介紹。
這裡給出一個示例:Jquery作者John Resig寫的繼承。
(其中的詳細註釋是來自某個博客,不知道是誰原創,這裡私自轉帖出來)
*/
(function () {
// initializing變數用來標示當前是否處於類的創建階段,
// - 在類的創建階段是不能調用原型方法init的
// - 我們曾在本系列的第三篇文章中詳細闡述了這個問題
// fnTest是一個正則表達式,可能的取值為(/\b_super\b/ 或 /.*/)
// - 對 /xyz/.test(function() { xyz; }) 的測試是為了檢測瀏覽器是否支持test參數為函數的情況
// - 不過我對IE7.0,Chrome2.0,FF3.5進行了測試,此測試都返回true。
// - 所以我想這樣對fnTest賦值大部分情況下也是對的:fnTest = /\b_super\b/;
var initializing = false, fnTest = /xyz/.test(function () { xyz; }) ? /\b_super\b/ : /.*/;
// 基類構造函數
// 這裡的this是window,所以這整段代碼就向外界開闢了一扇窗戶 - window.Class
this.Class = function () { };
// 繼承方法定義
Class.extend = function (prop) {
// 這個地方很是迷惑人,還記得我在本系列的第二篇文章中提到的麽
// - this具體指向什麼不是定義時能決定的,而是要看此函數是怎麼被調用的
// - 我們已經知道extend肯定是作為方法調用的,而不是作為構造函數
// - 所以這裡this指向的不是Object,而是Function(即是Class),那麼this.prototype就是父類的原型對象
// - 註意:_super指向父類的原型對象,我們會在後面的代碼中多次碰見這個變數
var _super = this.prototype;
// 通過將子類的原型指向父類的一個實例對象來完成繼承
// - 註意:this是基類構造函數(即是Class)
initializing = true;
var prototype = new this();
initializing = false;
// 我覺得這段代碼是經過作者優化過的,所以讀起來非常生硬,我會在後面詳解
for (var name in prop) {
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function (name, fn) {
return function () {
var tmp = this._super; // 這裡是必要的,第91行註釋代碼可說明之。
this._super = _super[name];
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// 這個地方可以看出,Resig很會偽裝哦
// - 使用一個同名的局部變數來覆蓋全局變數,很是迷惑人
// - 如果你覺得拗口的話,完全可以使用另外一個名字,比如function F()來代替function Class()
// - 註意:這裡的Class不是在最外層定義的那個基類構造函數
// 這裡的Class和上邊的window.Class函數不一樣,這裡是window.Class內部的函數局部變數
function Class() {
// 在類的實例化時,調用原型方法init
if (!initializing && this.init)
this.init.apply(this, arguments);
}
// 子類的prototype指向父類的實例(完成繼承的關鍵)
Class.prototype = prototype; // Class指代上邊的Class,並非一開始的window.Class
// 修正constructor指向錯誤
// 是否可用Class.prototype.constructor = Class;來修正???
Class.constructor = Class;
// 子類自動獲取extend方法,arguments.callee指向當前正在執行的函數
Class.extend = arguments.callee;
return Class;
};
})();