目錄
-
一、JS的解析與執行過程
- 預處理階段
- 執行階段
-
二、作用域
- 塊作用域
- 函數作用域
- 動態作用域
- 詞法作用域
-
三、閉包
- 什么是閉包
- 閉包的好處
-
四、函數與對象
- 對象
-
函數
-
原型(prototype)
-
this
-
new的理解
-
五、封裝
-
六、繼承
-
七、多態
-
八、項目實戰minijQuery
一、JS的解析與執行過程
1.1、預處理階段
注意:js預處理階段會掃描所有var聲明的變量,把var聲明的變量或函數存放到詞法作用域里,如果是變量初始值為“undefined”,如果是函數則指向函數;
全局(window)
詞法作用域(Lexical Environment):頂級的Lexical Environment是window;
1、先掃描函數聲明后掃描變量(var聲明);
2、處理函數聲明有沖突,會覆蓋;處理變量聲明時有沖突,會忽略。
函數
詞法作用域(Lexical Environment):每調用一次,產生一個Lexical Environment;
1、先函數的參數:比如arguments(函數內部對象,代表函數實參,可通過下標獲取調用函數時傳的實參);
2、先掃描函數聲明后掃描變量(var聲明);
3、處理函數聲明有沖突,會覆蓋;處理變量聲明時有沖突,會忽略。
1.2、執行階段
1、給預處理階段的成員賦值
2、如果沒有用var聲明的變量,會成為最外部LexicalEnvironment的成員(即window對象的變量)
函數內部對象:arguments

<script> /*提示:*/ // arguments是每一個函數內部的一個對象 // 可以訪問實際傳遞給函數的參數的信息。 // 聲明的時候參數的個數與實際調用時無關 function add(a,b){ console.log(add.length);// 形參的個數 console.log(arguments.length);// 實際傳過來的參數 var total = 0; for(var i = 0;i< arguments.length;i++){ total += arguments[i]; } return total;// 返回實參的總和 } // 調用時傳的實參 var result = add(1,2,3,4,5); var result2 = add(1,2); console.log(result);// 15 console.log(result2);// 3 </script>
二、作用域
提示:js的作用域不是塊級別的;js的作用域是函數級別的。
2.1、塊作用域
2.2、函數作用域
2.3、動態作用域
2.4、詞法作用域
代碼示例:

<script> //js作用域 // 定義:用來查找變量的值的規則集;決定一個變量的范圍 // 提示:js的作用域不是塊級別的;js的作用域是函數級別的。 // javascript使用的是詞法作用域,它的最重要的特征是它的定義過程發生在代碼的書寫階段 /*以下js代碼用立即調用寫法(私有化),避免變量沖突*/ //1、塊作用域:代碼在花括號里面有效(js沒有塊作用域) (function(){ for(var i=0;i<5;i++){ var a = i ; } // 在花括號外面可以訪問到i,a console.log(i);// 5 console.log(a);// 4 })(); //2、函數作用域:代碼在function()函數的花括號里面有效 (function(){ var message = "函數外部的"; function fn(){ var message = "函數內部的"; console.log(message);// 函數內部的 } console.log(message);// 函數外部的 })(); //3、動態作用域:在運行時決定(是this指向的表現;誰調用,this指向誰);動態作用域其實是指this的詞法作用域 // 動態作用域並不關心函數和作用域是如何聲明以及在任何處聲明的,只關心它們從何處調用。 // 換句話說,作用域鏈是基於調用棧的,而不是代碼中的作用域嵌套 (function(){ var a = 2; function foo() { console.log( a ); } function bar() { var a = 3; foo();// 此時this===window } bar();// 2 })(); /* var a = 2; bar = { a:3, foo:function(){ console.log(this.a); } } bar.foo();//3 */ //4、詞法作用域:詞法作用域(也稱為靜態作用域或閉包) // js的作用域解析,用new Function創建函數 (function(){ // 閉包 var a = 2; function bar() { var a = 3; return function(){ console.log(a);// 此時捕獲a=3 }; } var foo = bar(); foo();// 3 })(); // 如果處於詞法作用域,也就是現在的javascript環境。變量a首先在foo()函數中查找,沒有找到。於是順着作用域鏈到全局作用域中查找,找到並賦值為2。所以控制台輸出2 // 如果處於動態作用域,同樣地,變量a首先在foo()中查找,沒有找到。這里會順着調用棧在調用foo()函數的地方,也就是bar()函數中查找,找到並賦值為3。所以控制台輸出3 //小結:兩種作用域的區別,簡而言之,詞法作用域是在定義時確定的,而動態作用域是在運行時確定的 </script>
三、閉包(Closure)
3.1、什么是閉包
由於在Javascript語言中,只有函數內部的子函數才能讀取局部變量,因此可以把閉包簡單理解成“定義在一個函數內部的函數,內部函數並訪問父函數的局部變量”。
理解閉包:
1、閉包可以理解為一個對象,里面包含函數以及被函數捕獲的變量 , 一個圈里包含函數與捕獲的變量
2、也可以只把函數捕獲的變量稱之為閉包。
<script> //如何寫會產生閉包 function P(){ var a = 5; var b = 6; return function C(){ console.log(b);//此時捕獲變量b,值為6 } } var result = P(); result();// 6 </script>
產生閉包的條件:
1、函數內部包含子函數;
2、子函數訪問父函數的變量;
3.2、閉包的好處
用途:一個是前面提到的可以讀取函數內部的變量,另一個就是讓這些變量的值始終保持在內存中。
<script> // 閉包實例 function Person(){ var age = 1; this.getAge = function(){ return age ; } this.setAge = function(val){ age = val; } } var p = new Person(); p.setAge(20); console.log(p.getAge()); </script>
代碼示例:

<script type="text/javascript"> /*閉包--理解*/ // 提示:this由運行時決定! // 題目一:理解r1與r2的輸出 function addFactory(){ var adder = 5; return function(data){ adder += data;// 此時adder變量是閉包捕獲到的值 return adder; } } var adder1 = addFactory(); var r1 = adder1(1);//6 r1 = adder1(1);//7 var adder2 = addFactory(); var r2 = adder2(2);//7 r2 = adder2(2);//9 // 題目二:下面的代碼輸出什么 var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name;// 輸出"The Window";this = window; //return object.name;// 輸出"My object" }; } }; //alert(object.getNameFunc()());// The Window // 理解二: var fun = object.getNameFunc();// 返回一個函數,此時函數this指向window;window.fun() alert(fun());// 所以,輸出是:"The Window" // 題目三: var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this;//this = object; return function(){ return that.name;//閉包捕獲父函數的that,that = object; }; } }; alert(object.getNameFunc()());// My Object // 理解三: // var obj = object.getNameFunc(); // alert(obj());// 此時函數由於內部的name是object調用 </script>
四、函數與對象
4.1、對象
4.2、函數
4.3、原型(prototype)
javascript對象部署:
JavaScript是一種通過原型實現繼承的語言;在JavaScript中所有都是對象,原型(prototype)也是一個對象,通過原型可以實現對象的屬性繼承;
prototype:在js中是函數特有的屬性;指向該函數的原型(this.prototype)
__proto__:在js中是所有對象都有的屬性;指向此對象的構造器(函數)的原型對象(prototype)
<!-- 對象都有這屬性(找對象的父類對象):__proto__; 只有函數有的屬性(找函數的原型,是個對象):prototype; 對象(或函數)的構造器(頂級構造器時Function()):constructor; --> <script> function Aaa(){} //undefined var aaa = new Aaa(); //undefined aaa.__proto__; //{constructor: ƒ} Aaa.prototype; //{constructor: ƒ} aaa.__proto__ === Aaa.prototype; //true aaa.constructor; //ƒ Aaa(){} Aaa.constructor; //ƒ Function() { [native code] } aaa.constructor.constructor //ƒ Function() { [native code] } </script>
原型理解:

1. 函數Foo的__proto的值等於Foo.prototype,對嗎? 錯,函數Foo.__proto__===Function.prototype,函數Foo的實例的__proto__屬性的值等於函數Foo.prototype 2.Object的prototype可以修改嗎?能與不能原因是什么? //可以,函數(對象)的原型可以任意修改或繼承 不可以,因為Object.prototype是只讀,所以不能賦值;但可以修改或添加 3. 頂級constructor是誰? Function() 4.頂級原型對象是誰? Object 5.對象的construtor成員是個屬性還是個方法? 可以是屬性,也可以是方法(一般不建議這么寫,耗資源,在每次new是都要執行很多代碼) 6.Function有沒有__proto__,為什么?值等於Object.prototype嗎? 1.有(是對象都有),與prototype相等,因為Function是頂級構造器,所以,函數的__proto__屬性指向的構造器原型是與Function.prototype相等; 2.不等於,Function.prototype與Function.__proto__指向function.prototype 7.所有的構造器的__proto__都等於其對應的prototype 錯,等於Function.prototype;因為對象的__proto__屬性指向的是對象的構造器的prototype(函數的構造器是Function()) 8.創建類形式的繼承的四部曲是什么? 1.創建父類 2.創建子類 3.確定繼承關系:A.prototype = Object.create(B.prototype); 4.修改構造器(因為繼承后的構造器指向是父類的原型指向的構造器,也就是說,子類的原型指向的構造器===父類的原型指向的構造器) 9.Function的constructor於prototype值可以修改嗎? 不可以,Function是頂級構造器,Function.__proto__指向Function.prototype 10.Object.prototype === Object.__proto__嗎? 不相等,Object.prototype是object.prototype;Object.__proto__是function.prototype 11. Function.prototype === Function.__proto__嗎? 相等,(因為Function是頂級構造器,__proto__指向Function.prototype)Function.prototype===Function.__proto__ 12. function F(){}; var f1 = new F(); f1.__proto__ === Object.prototype嗎? 不對,f1.__proto__ === F.prototype;f1.__proto__.__proto__ === Object.prototype;
4.5、this
原則:
1、this由運行時決定!
2、函數中 this 到底指向誰 , 由調用此函數時的對象決定 , 而不是由定義函數所在的對象決定。
在JavaScript中this表示:誰調用它,this就是誰。
如何改變this指向:
call:
apply:
<script> /* var data = {}; Array.prototype.push.call(data,100,200); Array.prototype.push.apply(data,[1,2,3,8,10]); console.log(data); */ </script>
4.6、new的理解
簡單的可以理解為:new改變了this指向的對象;
五、封裝
六、繼承
七、多態
八、項目實戰minijQuery

<script type="text/javascript"> /* // 提示: // 暴露外部使用的一個接口 var jQuery = window.jQuery = window.$ = function(selector){ return new jQuery.fn.init(selector); } // 處理原型對象 jQuery.fn = jQuery.prototype = {} jQuery.fn.init.prototype = jQuery.fn; // 實現繼承,並且只處理只有一個參數,也就是插件的擴展 jQuery.extend = jQuery.fn.extend = function(){} // 添加靜態方法 jQuery.extend({}); // 添加實例方法 jQuery.fn.extend({}); // 1.獲取節點對象 var jq1 = jQuery(".pp"); 或 var jq1 = jQuery.fn.init(".pp"); */ // 提供全局訪問接口($()、jQuery()) (function () { /// 暫時把window的全局變量存起來,用做處理變量沖突 var _$ = window.$; var _jQuery = window.jQuery; //暴露外部使用的一個接口(獲取節點對象) var jQuery = window.jQuery = window.$ = function(selector){ return new jQuery.fn.init(selector);// init.prototype; }; //處理原型對象 jQuery.fn = jQuery.prototype = { init:function(selector){ var elements = document.querySelectorAll(selector); Array.prototype.push.apply(this,elements); return this; }, version:"1.0.0", length:0, size:function(){ return this.length; } }; // jQuery.fn.init.prototype === init.prototype; // jQuery.prototype; jQuery.fn.init.prototype = jQuery.fn; //實現繼承,並且只處理只有一個參數,也就是插件的擴展 jQuery.extend = jQuery.fn.extend = function(){ var o = arguments[0]; for(var p in o){ this[p] = o[p]; } }; /// 測試:(繼承方法) // var obj = {name:"張三三"} // var jq = $(".pp"); // jq.extend(obj); //添加靜態方法 jQuery.extend({ trim:function(text){ return (text||"").replace(/^\s+|\s+$/g,"");// 替換text字符串的開頭和結尾匹配任何空白字符為空(即,替換開頭和結尾的空格字符為空) }, noConflict:function(){ window.$ = _$; window.jQuery = _jQuery; return jQuery; } }); /// 測試:(命名沖突) // var jq = jQuery.noConflict();//返回一個jQuery函數,解決與全局的jQuery屬性沖突 // var obj = jq(".pp"); //添加實例方法 jQuery.fn.extend({ get:function(num){ return this[num]; }, each:function(fn){ for(var i = 0 ;i< this.length; i++){ fn(i,this[i]); } return this; }, css:function(){ var l = arguments.length; if(l == 1){ return this[0].style[arguments[0]]; } else { var name = arguments[0]; var value = arguments[1]; this.each(function(index,ele) { ele.style[name] = value; }); } return this; } }); })(); </script>