欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript高級程序設計(第3版)學習筆記8 js函數(中)

 更新時間:2012年10月11日 14:48:44   作者:  
接著看函數——這個具有魔幻色彩的對象。在上篇文章中說函數內部屬性時,還遺留了一個this內部屬性沒有解釋,不過在說this之前,我想先說一說執(zhí)行環(huán)境和作用域的概念
6、執(zhí)行環(huán)境和作用域

(1)執(zhí)行環(huán)境(execution context):所有的JavaScript代碼都運行在一個執(zhí)行環(huán)境中,當控制權轉移至JavaScript的可執(zhí)行代碼時,就進入了一個執(zhí)行環(huán)境。活動的執(zhí)行環(huán)境從邏輯上形成了一個棧,全局執(zhí)行環(huán)境永遠是這個棧的棧底元素,棧頂元素就是當前正在運行的執(zhí)行環(huán)境。每一個函數都有自己的執(zhí)行環(huán)境,當執(zhí)行流進入一個函數時,會將這個函數的執(zhí)行環(huán)境壓入棧頂,函數執(zhí)行完之后再將這個執(zhí)行環(huán)境彈出,控制權返回給之前的執(zhí)行環(huán)境。

(2)變量對象(variable object):每一個執(zhí)行環(huán)境都有一個與之對應的變量對象,執(zhí)行環(huán)境中定義的所有變量和函數就是保存在這個變量對象中。這個變量對象是后臺實現中的一個對象,我們無法在代碼中訪問,但是這有助于我們理解執(zhí)行環(huán)境和作用域相關概念。

(3)作用域鏈(scope chain):當代碼在一個執(zhí)行環(huán)境中運行時,會創(chuàng)建由變量對象組成的一個作用域鏈。這個鏈的前端,就是當前代碼所在環(huán)境的變量對象,鏈的最末端,就是全局環(huán)境的變量對象。在一個執(zhí)行環(huán)境中解析標識符時,會在當前執(zhí)行環(huán)境相應的變量對象中搜索,找到就返回,沒有找到就沿著作用域鏈一級一級往上搜索直至全局環(huán)境的變量對象,如果一直未找到,就拋出引用異常。

(4)活動對象(activation object):如果一個執(zhí)行環(huán)境是函數執(zhí)行環(huán)境,也將變量對象稱為活動對象。活動對象在最開始只包含一個變量,即arguments對象(這個對象在全局環(huán)境的變量對象中不存在)。

  這四個概念雖然有些抽象,但還是比較自然的,可以結合《JavaScript高級程序設計(第3版)》中的一個例子來細細體會一下:
復制代碼 代碼如下:

// 進入到全局作用域,創(chuàng)建全局變量對象
var color = "blue";

function changeColor(){
// 進入到changeColor作用域,創(chuàng)建changeColor相應變量對象
var anotherColor = "red";

function swapColors(color1, color2){
// 進入到swapColors作用域,創(chuàng)建swapColors相應變量對象
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
/*
* swapColors作用域內可以訪問的對象有:
* 全局變量對象的color,changeColor
* changeColor函數相應變量對象的anotherColor、swapColors
* swapColors函數相應變量對象的tempColor
*/
}
swapColors('white');
/*
* changeColor作用域內可以訪問的對象有:
* 全局變量對象的color,changeColor
* changeColor函數相應變量對象的anotherColor、swapColors
*/
}

changeColor();
/*
* 全局作用域內可以訪問的對象有:
* 全局變量對象的color,changeColor
*/

這里的整個過程是:

(1)進入全局環(huán)境,創(chuàng)建全局變量對象,將全局環(huán)境壓入棧頂(這里也是棧底)。根據前面的關于聲明提升的結論,這里創(chuàng)建全局變量對象可能的一個過程是,先創(chuàng)建全局變量對象,然后處理函數聲明設置屬性changeColor為相應函數,再處理變量聲明設置屬性color為undefined。

(2)執(zhí)行全局環(huán)境中的代碼。先執(zhí)行color變量初始化,賦值為'blue',再調用changeColor()函數。

(3)調用changeColor()函數,進入到changeColor函數執(zhí)行環(huán)境,創(chuàng)建這個環(huán)境相應的變量對象(也就是活動對象),將這個環(huán)境壓入棧頂。創(chuàng)建活動對象可能的一個過程是,先創(chuàng)建活動對象,處理內部函數聲明設置屬性swapColors為相應函數,處理函數參數創(chuàng)建活動對象的屬性arguments對象,處理內部變量聲明設置屬性anotherColor為undefined。

(4)執(zhí)行changeColor()函數代碼。先執(zhí)行anotherColor初始化為'red',再調用swapColors()函數。

(5)調用swapColors()函數,進入到swapColors函數執(zhí)行環(huán)境,創(chuàng)建相應的變量對象(活動對象),將swapColors執(zhí)行環(huán)境壓入棧頂。這里創(chuàng)建活動對象可能的一個過程是,先創(chuàng)建活動對象,處理函數參數,將形式參數作為活動對象的屬性并賦值為undefined,創(chuàng)建活動對象的屬性arguments對象,并根據實際參數初始化形式參數和arguments對應的值和屬性(將屬性color1和arguments[0]初始化為'white',由于沒有第二個實際參數,所以color2的值為undefined,而arguments的長度只為1了),處理完函數參數之后,再處理函數內部變量聲明,將tempColor作為活動對象的屬性并賦值為undefined。

(6)執(zhí)行swapColors()函數代碼。先給tempColor初始化賦值,然后實現值交換功能(這里color和anotherColor的值都是沿著作用域鏈才讀取到的)。

(7)swapColors()函數代碼執(zhí)行完之后,返回undefined,將相應的執(zhí)行環(huán)境彈出棧并銷毀(注意,這里會銷毀執(zhí)行環(huán)境,但是執(zhí)行環(huán)境相應的活動對象并不一定會被銷毀),當前執(zhí)行環(huán)境恢復成changeColor()函數的執(zhí)行環(huán)境。隨著swapColor()函數執(zhí)行完并返回,changeColor()也就執(zhí)行完了,同樣返回undefined,并將changeColor()函數的執(zhí)行環(huán)境彈出棧并銷毀,當前執(zhí)行環(huán)境恢復成全局環(huán)境。整個處理過程結束,全局環(huán)境直至頁面退出再銷毀。

  作用域鏈也解釋了為什么函數可以在內部遞歸調用自身:函數名是函數定義所在執(zhí)行環(huán)境相應變量對象的一個屬性,然后在函數內部執(zhí)行環(huán)境中,就可以沿著作用域鏈向外上溯一層訪問函數名指向的函數對象了。如果在函數內部將函數名指向了一個新函數,遞歸調用時就會不正確了:
復制代碼 代碼如下:

function fn(num){
if(1 == num){
return 1;
}else{
fn = function(){
return 0;
};
return num * fn(num - 1);
}
}
console.info(fn(5));//0

關于作用域和聲明提升,再看一個例子:
復制代碼 代碼如下:

var name = 'linjisong';
function fn(){
console.info(name);//undefined
var name = 'oulinhai';
console.info(name);//oulinhai
}
fn();
console.info(name);//linjisong

這里最不直觀的可能是第3行輸出undefined,因為在全局中已經定義過name了,不過按照上面解析的步驟去解析一次,就可以得出正確的結果了。另外強調一下,在ECMAScript中只有全局執(zhí)行環(huán)境和函數執(zhí)行環(huán)境,相應的也只有全局作用域和函數作用域,沒有塊作用域——雖然有塊語句。
復制代碼 代碼如下:

function fn(){
var fnScope = 'a';

{
var blockScope = 'b';
blockScope += fnScope;
}
console.info(blockScope);//沒有塊作用域,所以可以在整個函數作用域內訪問blockScope
console.info(fnScope);
}
fn();//ba,a

console.info(blockScope);//ReferenceError,函數作用域外,不能訪問內部定義的變量
console.info(fnScope);//ReferenceError

對于作用域鏈,還可以使用with、try-catch語句的catch塊來延長:

•使用with(obj){}語句時,將obj對象添加到當前作用域鏈的最前端。
•使用try{}catch(error){}語句時,將error對象添加到當前作用域鏈的最前端。
  插了一段較為抽象的概念,希望不至于影響整個閱讀的流暢,事實上,我在這里還悄悄的繞過了一個稱為“閉包”的概念,關于函數與閉包,在下篇文章中再詳細敘述。

7、函數內部對象與this

  對于面向對象語言的使用者來說,this實在是再熟悉不過了,不就是指向構造函數新創(chuàng)建的對象嗎!不過,在ECMAScript中,且別掉以輕心,事情沒有那么簡單,雖然在使用new操作符調用函數的情況下,this也的確是指向新創(chuàng)建的對象,但這只是指定this對象值的一種方式而已,還有更多的方式可以指定this對象的值,換句話說,this是動態(tài)的,是可以由我們自己自由指定的。

(1)全局環(huán)境中的this

  在全局環(huán)境中,this指向全局對象本身,在瀏覽器中也就是window,這里也可以把全局環(huán)境中的this理解為全局執(zhí)行環(huán)境相應的變量對象,在全局環(huán)境中定義的變量和函數都是這個變量對象的屬性:
復制代碼 代碼如下:

var vo = 'a';
vo2 = 'b';
function fn(){
return 'fn';
}
console.info(this === window);//true
console.info(this.vo);//a
console.info(this.vo2);//b
console.info(this.fn());//fn

如果在自定義函數中要引用全局對象,雖然可以直接使用window,但更好的方式則是將全局對象作為參數傳入函數,這是在JS庫中非常通用的一種方式:
復制代碼 代碼如下:

(function(global){
console.info(global === window);//在內部可以使用global代替window了
})(this);  

這種方式兼容性更好(ECMAScript的實現中全局對象未必都是window),在壓縮時,也可以將global簡化為g,而不用使用window了。

(2)函數內部屬性this

  在函數環(huán)境中,this是一個內部屬性對象,可以理解成函數對應的活動對象的一個屬性,而這個內部屬性的值是動態(tài)的。那this值是怎么動態(tài)確定的呢?

•使用new調用時,函數也稱為構造函數,這個時候函數內部的this被指定為新創(chuàng)建的對象。
復制代碼 代碼如下:

function fn(){
var name = 'oulinhai';//函數對應的活動對象的屬性
this.name = 'linjisong';//當使用new調用函數時,將this指定為新創(chuàng)建對象,也就是給新創(chuàng)建對象添加屬性
}
var person = new fn();
console.info(person.name);//linjisong

var arr = [fn];
console.info(arr[0]());//undefined

需要注意區(qū)分一下函數執(zhí)行環(huán)境中定義的屬性(也即活動對象的屬性)和this對象的屬性,在使用數組元素方式調用函數時,函數內部this指向數組本身,因此上例最后輸出undefined。

•作為一般函數調用時,this指向全局對象。
•作為對象的方法調用時,this指向調用這個方法的對象。
  看下面的例子:
復制代碼 代碼如下:

var name = 'oulinhai';
var person = {
name:'linjisong',
getName:function(){
return this.name;
}
};
console.info(person.getName());//linjisong
var getName = person.getName;
console.info(getName());//oulinhai

這里函數對象本身是匿名的,是作為person對象的一個屬性,當作為對象屬性調用時,this指向了對象,當把這個函數賦給另一個函數然后調用時,是作為一般函數調用的,this指向了全局對象。這個例子充分說明了“函數作為對象的方法調用時內部屬性this指向這個調用對象,函數作為一般函數調用時內部屬性this指向全局對象”,也說明了this的指定是動態(tài)的,是在調用時指定的,而不管函數是單獨定義的還是作為對象方法定義的。也正是因為函數作為對象的方法調用時this指向這個調用對象,所以在函數內部返回this時才能夠延續(xù)調用對象的下一個方法——也就是鏈式操作(jQuery的一大特色)。

•使用apply()、call()或bind()調用函數時,this指向第一個參數對象。如果沒有傳入參數或傳入的是null和undefined,this指向全局對象(在ES5的嚴格模式下會設為null)。如果傳入的第一個參數是一個簡單類型,會將this設置為相應的簡單類型包裝對象。
復制代碼 代碼如下:

var name = 'linjisong';
function fn(){
return this.name;
}
var person = {
name:'oulinhai',
getName:fn
};
var person2 = {name:'hujinxing'};
var person3 = {name:'huanglanxue'};
console.info(fn());//linjisong,一般函數調用,內部屬性this指向全局對象,因此this.name返回linjisong
console.info(person.getName());//oulinhai,作為對象方法調用,this指向這個對象,因此這里返回person.name
console.info(fn.apply(person2));//hujinxing,使用apply、call或bind調用函數,執(zhí)行傳入的第一個參數對象,因此返回person2.name
console.info(fn.call(person2));//hujinxing
var newFn = fn.bind(person3);//ES5中新增方法,會創(chuàng)建一個新函數實例返回,內部this值被指定為傳入的參數對象
console.info(newFn());//huanglanxue

上面示例中列出的都是一些常見情況,沒有列出第一個參數為null或undefined的情況,有興趣的朋友可以自行測試。關于this值的確定,在原書中還有一個例子:
復制代碼 代碼如下:

var name = 'The Window';
var object = {
name : 'My Object',
getName:function(){
return this.name;
},
getNameFunc:function(){
return function(){
return this.name;
}
}
};

console.info(object.getName());//My Object
console.info((object.getName)());//My Object
console.info((object.getName = object.getName)());//The Window
console.info(object.getNameFunc()());//The Window

第1個是正常輸出,第2個(object.getName)與object.getName的效果是相同的,而第3個(object.getName=object.getName)最終返回的是函數對象本身,也就是說第3個會作為一般函數來調用,第4個則先是調用getNameFunc這個方法,返回一個函數,然后再調用這個函數,也是作為一般函數來調用。

8、函數屬性和方法

  函數是一個對象,因此也可以有自己的屬性和方法。不過函數屬性和方法與函數內部屬性很容易混淆,既然容易混淆,就把它們放一起對照著看,就好比一對雙胞胎,不對照著看,不熟悉的人是區(qū)分不了的。

  先從概念上來區(qū)分一下:

(1)函數內部屬性:可以理解為函數相應的活動對象的屬性,是只能從函數體內部訪問的屬性,函數每一次被調用,都會被重新指定,具有動態(tài)性。

(2)函數屬性和方法:這是函數作為對象所具有的特性,只要函數一定義,函數對象就被創(chuàng)建,相應的屬性和方法就可以訪問,并且除非你在代碼中明確賦為另一個值,否則它們的值不會改變,因而具有靜態(tài)性。有一個例外屬性caller,表示調用當前函數的函數,也是在函數被調用時動態(tài)指定,在《JavaScript高級程序設計(第3版)》中也因此將caller屬性和函數內部屬性arguments、this一起講解,事實上,在ES5的嚴格模式下,不能對具有動態(tài)特性的函數屬性caller賦值。

  光從概念上區(qū)分是非常抽象的,也不是那么容易理解,再把這些屬性列在一起比較一下(沒有列入一些非標準的屬性,如name):

類別 名稱 繼承性 說明 備注
函數內部屬性 this - 函數據以執(zhí)行的環(huán)境對象 和一般面向對象語言有很大區(qū)別
arguments -

表示函數實際參數的類數組對象

arguments本身也有自己的屬性:length、callee和caller

1、length屬性表示實際接收到的參數個數

2、callee屬性指向函數對象本身,即有:

  fn.arguments.callee === fn

3、caller屬性主要和函數的caller相區(qū)分,值永遠都是undefined

函數屬性 caller 調用當前函數的函數 雖然函數一定義就可訪問,但是不在函數體內訪問時永遠為null,在函數體內訪問時返回調用當前函數的函數,在全局作用域中調用函數也會返回null
length 函數形式參數的長度 就是定義函數時命名的參數個數
prototype 函數原型對象 原型對象是ECMAScript實現繼承的基礎
constructor 繼承自Object,表示創(chuàng)建函數實例的函數,也就是Function() 值永遠是Function,也就是內置的函數Function()
函數方法 apply 調用函數自身,以(類)數組方式接受參數

這三個方法主要作用是動態(tài)綁定函數內部屬性this

1、apply和call在綁定之后會馬上執(zhí)行

2、bind在綁定之后可以在需要的時候再調用執(zhí)行

call 調用函數自身,以列舉方式接受參數
bind 綁定函數作用域,ES5中新增
toLocalString 覆蓋

覆蓋了Object類型中的方法,返回函數體

不同瀏覽器實現返回可能不同,可能返回原始代碼,也可能返回去掉注釋后的代碼

toString 覆蓋
valueOf 覆蓋
hasOwnProperty 直接繼承自Object類型的方法,用法同Object
propertyIsEnumerable
isPropertyOf

  函數屬性和方法,除了從Object繼承而來的屬性和方法,也包括函數本身特有的屬性和方法,用的最多的方法自然就是上一小節(jié)說的apply()、call(),這兩個方法都是用來設置函數內部屬性this從而擴展函數作用域的,只不過apply()擴展函數作用域時是以(類)數組方式接受函數的參數,而call()擴展函數作用域時需要將函數參數一一列舉出來傳遞,看下面的例子:

復制代碼 代碼如下:

function sum(){
  var total = 0,
  l = arguments.length ;

  for(; l; l--){
  total += arguments[l-1];
  }
  return total;
}

console.info(sum.apply(null,[1,2,3,4]));//10
console.info(sum.call(null,1,2,3,4));//10

不過需要強調的是:apply和call的主要作用還是在于擴展函數作用域。apply和call在擴展作用域時會馬上調用函數,這使得應用中有了很大限制,因此在ES5中新增加了一個bind()函數,這個函數也用于擴展作用域,但是可以不用馬上執(zhí)行函數,它返回一個函數實例,將傳入給它的第一個參數作為原函數的作用域。它的一個可能的實現如下:
復制代碼 代碼如下:

function bind(scope){
var that = this;
return function(){
that.apply(scope, arguments);
}
}
Function.prototype.bind = bind;

這里涉及了一個閉包的概念,明天再繼續(xù)。

相關文章

  • JavaScript中的eval()函數詳解

    JavaScript中的eval()函數詳解

    和其他很多解釋性語言一樣,JavaScript同樣可以解釋運行由JavaScript源代碼組成的字符串,并產生一個值。JavaScript通過全局函數eval()來完成這個工作
    2013-08-08
  • JS 全屏和退出全屏詳解及實例代碼

    JS 全屏和退出全屏詳解及實例代碼

    退出全屏效果與全屏效果我們可能在看視頻網站時看到多,這里來為各位介紹利用js全屏和退出全屏代碼范例吧,,需要的朋友可以參考下
    2016-11-11
  • 使用JS CSS去除IE鏈接虛線框的三種方法

    使用JS CSS去除IE鏈接虛線框的三種方法

    本文使用JS、CSS、標簽屬性等方式去除IE鏈接上的虛線框,方法很簡單,大家可以選擇使用
    2013-11-11
  • JavaScript編程中布爾對象的基本使用

    JavaScript編程中布爾對象的基本使用

    這篇文章主要介紹了JavaScript編程中布爾對象的基本使用,是JavaScript入門學習中的基礎知識,需要的朋友可以參考下
    2015-10-10
  • JavaScript多線程運行庫Nexus.js詳解

    JavaScript多線程運行庫Nexus.js詳解

    這篇文章主要介紹了JavaScript多線程運行庫Nexus.js的學習心得以及代碼分享,有需要的朋友一起參考學習下吧。
    2017-12-12
  • 老生常談onBlur事件與onfocus事件(js)

    老生常談onBlur事件與onfocus事件(js)

    下面小編就為大家?guī)硪黄仙U刼nBlur事件與onfocus事件(js)。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-07-07
  • php與js的區(qū)別是什么

    php與js的區(qū)別是什么

    php與js的區(qū)別包括:類型轉換的不同、實形參的不同、數據類型的不同等等,更多參考下文,希望對大家有所幫助
    2013-08-08
  • JavaScript初學者應注意的七個細節(jié)詳細介紹

    JavaScript初學者應注意的七個細節(jié)詳細介紹

    種種語言都有它特別的地方,對于JavaScript來說,使用var就可以聲明任意類型的變量,這門腳本語言看起來很簡單,然而想要寫出優(yōu)雅的代碼卻是需要不斷積累經驗的,接下來介紹初學者應注意
    2012-12-12
  • WEB前端設計師常用工具集錦

    WEB前端設計師常用工具集錦

    這篇文章主要介紹了WEB前端設計師常用工具集錦,非常的全面,想成為一個優(yōu)秀的WEB前端設計師嗎?那本文你要好好研究研究了。
    2014-12-12
  • 分析Node.js connect ECONNREFUSED錯誤

    分析Node.js connect ECONNREFUSED錯誤

    最近在準備Angularjs +node.js demo的時候在我的mac開發(fā)中 遇見此錯誤
    2013-04-04

最新評論