重點。
一、函數
1、函數介紹
函數是一塊JavaScript代碼,被定義一次,但可執行和調用多次。JS中的函數也是對象,所以JS函數可以像其他對象那樣操作和傳遞,所以我們也常叫JS中的函數為函數對象。
注意:
返回Return
return語句可以使函數提前返回。
一個函數總會返回一個值,函數的返回值,依賴於return語句。
一般的函數調用:如果沒有return語句的話,默認會在所有代碼執行完以后返回undefined。
如果是作為構造器,外部使用new去調用的話,如果沒有return語句,或者return的是基本類型的話,默認會將this作為返回。
反之,如果return的是對象,將這個對象作為new構造器的返回值。
2、函數的屬性和方法【20170501】
函數屬性
- length屬性
- prototype屬性
- 自定義屬性
函數方法
- call()
- apply()
3、函數的相關內容
函數內容較多,重點有:
- this
- arguments
- 作用域
- 不同調用方式
- 直接調用foo()
- 對象方法o.method()
- 構造器new Foo()
- call/apply/bind調用 func.call(o)
- 不同創建方法
二、函數聲明和表達式
1、函數聲明
function add(a,b){ a=+a; b=+b; if(isNaN(a)||isNaN(b)){ return; } return a+b; }
一個完整的語句以function開頭,也不加括號,也不加嘆號,也不會把它括起來,也不會把它作為賦值語句的右值等待。這樣定義的函數就叫函數聲明。
2、函數表達式
1、 函數變量
函數表達式賦值給變量。
//函數變量 function variable var add=function(a,b){ //do sth }
2、立即執行函數表達式(IEF)
把一個匿名函數用括號括起來,再去直接調用。
//立即執行函數表達式 IEF(Immediately Executed Function) (function(){ })();
3、作為返回值的函數表達式
將函數對象作為返回值,函數也是對象。
//first-class function return function(){ //do sth }
4、命名式函數表達式(不常用)
同樣是賦值給一個變量,但這個函數不是匿名函數,而是有一個名字的函數,
//NFE (Named Function Expression) var add=function(a,b){ //do sth }
3、函數聲明和函數表達式的區別
最主要的區別是函數聲明會被前置。
函數聲明,在聲明前調用也可以,因為會前置。
函數表達式在聲明前調用會報錯:undefined is not a function。
函數表達式中
var add=function(a,b){//do sth};
變量的聲明會被提前,var add會提前,add被提前后它的值是undefined。
當把一個undefined的變量嘗試像函數那樣去調用的時候,就會報異常:undefined is not a function。
注意:在《jquery按鈕綁定特殊事件》
中第一次點擊按鈕觸發一個事件,第二次點擊觸發另一個事件。遇到類似的情況,
//不要這樣做 if(condition){ function sayHi(){ alert("Hi!"); } }else{ function sayHi(){ alert("Yo!"); } } //可以這樣做 var sayHi; if(condition){ sayHi=function(){ alert("Hi!"); } }else{ sayHi=function(){ alert("Yo!"); } }
4、命名函數表達式(NFE)
函數名字可以被調試器和開發工具識別。
經典bug
命名函數表達式里的名字nfe在 函數對象創建所在的作用域中 正常情況下是訪問不到的。所以會報錯:nfe is not defined
老的IE瀏覽器(IE6~8)仍然可以訪問得到,但是nfe和func又不是同一個對象。
命名函數表達式相關規范:
規范規定:命名函數表達式名字(標識符)在函數體的作用域內有效,且不能被覆蓋。
b = function c() { a = 1, b = 2, c = 3; console.log(typeof c); // function };
命名函數表達式應用:
調試
遞歸調用自己
//遞歸調用 var func=function nfe(){/** do sth. **/ nfe();}
5、函數構造器
除了函數聲明和函數表達式,還有一種不常見的構造函數的方式—使用函數構造器。
Function()中參數可以有多個,前面的參數表示函數對象里面的形參,最后一個參數表示函數體里面的代碼。
函數體里的代碼也是字符串,這也是為什么函數構造器不常用的原因。
var func=new Function('a','b','console.log(a+b);'); //創建一個對象,有a,b2個形參,函數體里面是輸出a+b func(1,2);//3 //不管用new還是不用new最后得到的結果都是一樣的。 var func=Function('a','b','console.log(a+b);'); func(1,2);//3
Function構造器作用域和其他處理與一般函數聲明和函數表達式的差異
1、在Function構造器里面創建的變量仍然是局部變量,
//CASE1 Function('var localVal="local";console.log(localVal);')();//立即執行 console.log(typeof localVal); //undefined
2、Function函數聲明能訪問到全局變量,卻訪問不到它的外函數中定義的變量。
local不可訪問,全局global可以訪問。
三、this
1、全局的this(瀏覽器)
全局作用域下的this一般指向全局對象,瀏覽器匯總的全局對象就是window。
2、一般函數的this(瀏覽器)【重點】
全局作用域下直接調用f1(),this就仍然指向全局對象,瀏覽器中就是window,在node.js里面就是global對象。
嚴格模式下直接調用f2(),this執行是undefined。
注意:語言設計上的不足【update 20170306】
調用函數時,this被綁定到全局對象。如下內部函數調用時this也是綁定到全局對象window。

function outer(){ console.log(this===window); function inner(){ console.log("內部函數:"+(this===window)); } inner(); }
倘若語言設計正確,當內部函數被調用時,this應該仍然綁定到外部函數的this變量。這個設計錯誤的后果就是方法不能利用內部函數來幫助它工作,因為內部函數的this被綁定了錯誤的值,所以不能共享該方法對對象的訪問權。
一個簡單的解決方案如下:方法定義一個一個變量that並給它賦值為this,那么內部函數就可以通過that訪問到this。
<script> //函數字面量創建add var add=function(a,b){ return a+b; } var myObject = { value: 0, increment: function(inc) { this.value += typeof inc === 'number' ? inc : 1; } } myObject.increment(); document.writeln(myObject.value); //1 myObject.increment(2); document.writeln(myObject.value); //3 //給myObject增加一個double方法 myObject.double=function(){ var that=this;//解決方法 var helper=function(){ that.value=add(that.value,that.value); } helper(); //以函數的形式調用helper } //以方法的形式調用double myObject.double(); document.writeln(myObject.value); //6 </script>
3、作為對象方法的 函數的this【重點】
只要將函數作為對象的方法o.f,this就會指向這個對象o。
var o={ prop:37, f:function(){ return this.prop; } } console.log(o.f()); //37
或者
var o={prop:37}; function independent(){ return this.prop; } o.f=independent; console.log(o.f()); //37
在方法調用模式中this到對象的綁定發生在調用時,這個“超級”延遲綁定(very late binding)使得函數可以對this高度重用。【update20170306】
4、對象原型鏈上的this
對象o有個屬性f。
p是個空對象,並且p的原型指向o。給p添加2個屬性a和b,再調用p.f()。
調用p.f()的時候調用的是p的原型o上面的這樣一個屬性f。所以對象原型鏈上的this調用時指向的還是對象。
var o={f:function(){return this.a+this.b}}; var p=Object.create(o); p.a=1; p.b=4; console.log(p.f()); //5
5、get/set方法與this
get set方法中的this一般也是指向get,set方法所在的對象。
function modulus(){ return Math.sqrt(this.re*this.re+this.im*this.im); } var o={ re:1, im:-1, get phase(){ return Math.atan2(this.im,this.re); } } Object.defineProperty(o,'modulus',{ get:modulus, enumerable:true, configurable:true }) console.log(o.phase,o.modulus); //-0.7853981633974483 1.4142135623730951
6、構造器中的this【重點】
如果在一個函數前面帶上new來調用,那么背地里將會創建一個連接到該函數的prototype成員的新對象,同時this會綁定到那個新對象上。
將MyClass作為了構造器來用。
function MyClass(){ this.a=37; } var o=new MyClass(); /*this指向空對象,並且這個空對象的原型指向MyClass.prototype, this作為返回值,因為沒有return 所以對象o就會有屬性a為37*/ console.log(o.a);//37
注意:
return語句返回的是對象的話,將該對象作為返回值,所以下面a就是38。
function C2(){ this.a=37; return {a:38}; } o=new C2(); console.log(o.a);//38
7、call/apply方法與this【重點】
function add(c,d){ console.log(this.a+this.b+c+d); } var o={a:1,b:3}; //call調用 add.call(o,5,7);//16 //1+3+5+7=16 //apply調用 add.apply(o,[10,20]);//34 //1+3+10+20=34
應用
function bar(){ console.log(Object.prototype.toString.call(this)); } bar.call(7); //[object Number]
call和apply如果this傳入null或者undefined的話,this會指向全局對象,在瀏覽器里就是window。
如果是嚴格模式的話:
傳入this為null和undefined,那this就是null和undefined。
8、bind與this[ES5提供,IE9+才有]
想把某一個對象作為this的時候,就傳進去。
function f(){ return this.a; } var g=f.bind({a:"test"}); console.log(g());//test /* 綁定一次,多次調用,仍然實現這樣一個綁定,比apply和call更高效 */ var o={a:37,f:f,g:g}; /*f屬性賦值為直接的f方法,g賦值為剛才綁定之后的方法*/ console.log(o.f(),o.g()); //37 "test" /*o.f()通過對象的屬性的方式調用的,返回37*/ /*比較特殊的一點,使用bind方法綁定了之后,即使把新綁定之后的方法作為對象的屬性去調用,仍然會按照之前的綁定去走,所以仍然返回test*/
應用【bind的經典例子】
this.x=9; //相當於window.x=9 var module={ x:81, getX:function(){return this.x;} }; module.getX();//81 作為對象方法調用 var getX=module.getX();//把對象的方法賦值給一個變量 getX();//9 this指向window,調用的是window的x var boundGetx=getX.bind(module); boundGetx();//81 通過bind修改運行時的this
四、函數屬性arguments
foo.length拿到形參的個數。在函數內和函數外都有效。foo.length===arguments.callee.length【20170501】
arguments.length拿到實際傳參的個數。
arguments.callee當前正在執行的函數
foo.name拿到函數名。
坑:嘗試通過arguments[2]=100修改未傳入的z的值,z還是undefined。
就是說:參數如果沒傳進來的話,arguments和參數沒有改下修改這樣的綁定關系。
function foo(x,y,z){ console.log(arguments.length); //2 console.log(arguments[0]); //1 arguments[0]=10; console.log(x); //有綁定關系,形參x被修改為10 arguments[2]=100;//z未傳入 console.log(z);//沒有綁定關系,z仍然是undefined console.log(arguments.callee===foo);//true,嚴格模式禁止使用 } foo(1,2); console.log(foo.length);//3 console.log(foo.name);//"foo"
好處:使得編寫一個無須指定參數個數的函數成為可能。
注意設計錯誤
因為語言設計錯誤,arguments並不是一個真正的數組。它只是一個"類似數組(array-like)"的對象。arguments擁有一個length屬性,但arguments沒有任何數組的方法。
五、bind和函數柯里化
函數柯里化就是把一個函數拆成多個單元。
1、柯里化
function add(a,b,c) { console.log(a+'|'+b+'|'+c); console.log(a+b+c); }
//不需要改變this,所以傳入一個undefined就可以了 var func=add.bind(undefined,100); func(1,2); //100|1|2 //103 var func2=func.bind(undefined,200); func2(10); //100|200|10 //310
100固定賦值給a參數。
再柯里化一次,200固定賦值給b參數。
2、實際例子
/*getConfig獲取一套配置 在不同的頁面中配置可能是不一樣的, 但是在同一個模塊下,可能前2個參數,或者某些參數是一樣的 */ function getConfig(colors,size,otherOptions){ console.log(colors,size,otherOptions); } /*this無所謂,寫個null或者undefined都可以,可能在某個模塊下color都是#CC0000,size都是1024*768*/ var defaultConfig=getConfig.bind(null,"#CC0000","1024*768"); /*拿到defaultConfig這樣一個模塊級別的通用配置以后,只要傳入最后一個參數,可能是每個頁面下的單獨配置*/ defaultConfig("123"); //#CC0000 1024*768 123 defaultConfig("456"); //#CC0000 1024*768 456
六、bind和new
用new去調用,在this這個層面上.bind()的作用會被忽略。
用new的時候,即使綁定了bind,也會被忽略。
func()直接調用,this會指向bind參數{a:1},return this.a就會返回1.
執行了this.b=100其實是給{a:1}加了個b屬性,最后是{a: 1, b: 100}只是不會作為返回值,因為指定了返回值。
new調用的話,return除非是對象,不是對象的話會把this作為返回值,並且this會被初始化為默認的一個空對象,這個對象的原型是foo.prototye。
所以這里new func()調用的時候,即使我們指定了bind方法,this仍然會指向沒有bind時所指向的空對象,空對象的原型指向foo.prototype,這個空對象的b屬性被設置為100,整個對象會作為一個返回值返回,會忽略return this.a。所以用new func()調用后會返回對象字面量{b:100}。
七、bind方法模擬
在老的瀏覽器里怎樣實現bind方法?模擬實現。
bind方法實現2個功能,綁定this和柯里化。
MDN的模擬實現。
本文作者starof,因知識本身在變化,作者也在不斷學習成長,文章內容也不定時更新,為避免誤導讀者,方便追根溯源,請諸位轉載注明出處:http://www.cnblogs.com/starof/p/6396450.html有問題歡迎與我討論,共同進步。