首頁 web前端 js教程 JavaScript高階程式設計(第3版)學習筆記8 js函數(中)_基礎知識

JavaScript高階程式設計(第3版)學習筆記8 js函數(中)_基礎知識

May 16, 2016 pm 05:49 PM

6、執行環境與作用域

(1)執行環境(execution context):所有的JavaScript程式碼都運作在一個執行環境中,當控制權轉移到JavaScript的可執行程式碼時,就進入了一個執行環境。活動的執行環境從邏輯上形成了一個棧,全域執行環境永遠是這個棧的棧底元素,棧頂元素就是目前正在運作的執行環境。每一個函數都有自己的執行環境,當執行流進入函數時,會將這個函數的執行環境壓入棧頂,函數執行完之後再將這個執行環境彈出,控制權回傳給先前的執行環境。

(2)變數物件(variable object):每一個執行環境都有一個與之對應的變數對象,而執行環境中定義的所有變數和函數就是保存在這個變數物件中。這個變數對像是後台實作中的一個對象,我們無法在程式碼中訪問,但這有助於我們理解執行環境和作用域相關概念。

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

(4)活動物件(activation object):如果一個執行環境是函數執行環境,也將變數物件稱為活動物件。活動物件在最開始只包含一個變量,即arguments物件(這個物件在全域環境的變數物件中不存在)。

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

// 進入全域作用域,建立全域變數物件
var color = " blue";

function changeColor(){
// 進入changeColor作用域,建立changeColor對應變數物件
var anotherColor = "red";

function swapors(111 , color2){
// 進入到swapColors作用域,創建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)進入全域環境,建立全域變數對象,將全域環境壓入堆疊頂(這裡也是堆疊底)。根據前面的關於宣告提升的結論,這裡建立全域變數物件可能的一個過程是,先建立全域變數對象,然後處理函數宣告設定屬性changeColor為對應函數,再處理變數宣告設定屬性color為undefined。

(2)執行全域環境中的程式碼。先執行color變數初始化,賦值為'blue',再呼叫changeColor()函數。

(3)呼叫changeColor()函數,進入到changeColor函數執行環境,建立這個環境對應的變數物件(也就是活動物件),將這個環境壓入堆疊頂部。建立活動對象可能的一個過程是,先建立活動對象,處理內部函數宣告設定屬性swapColors為對應函數,處理函數參數建立活動對象的屬性arguments對象,處理內部變數宣告設定屬性anotherColor為undefined。

(4)執行changeColor()函數程式碼。先執行anotherColor初始化為'red',再呼叫swapColors()函數。

(5)呼叫swapColors()函數,進入到swapColors函數執行環境,建立對應的變數物件(活動物件),將swapColors執行環境壓入堆疊頂。這裡創建活動對象可能的一個過程是,先創建活動對象,處理函數參數,將形式參數作為活動對象的屬性並賦值為undefined,創建活動對象的屬性arguments對象,並根據實際參數初始化形式參數和arguments對應的值和屬性(將屬性color1和arguments[0]初始化為'white',由於沒有第二個實際參數,所以color2的值為undefined,而arguments的長度只為1了),處理完函數參數之後,再處理函數內部變數聲明,將tempColor作為活動物件的屬性並賦值為undefined。

(6)執行swapColors()函數程式碼。先將tempColor初始化賦值,然後實作值交換功能(這裡color和anotherColor的值都是沿著作用域鏈才讀取到的)。

(7)swapColors()函數程式碼執行完之後,返回undefined,將對應的執行環境彈出堆疊並銷毀(注意,這裡會銷毀執行環境,但是執行環境對應的活動物件不一定會被銷毀),目前執行環境恢復成changeColor()函數的執行環境。隨著swapColor()函數執行完並返​​回,changeColor()也就執行完了,同樣返回undefined,並將changeColor()函數的執行環境彈出堆疊並銷毀,當前執行環境恢復成全域環境。整個處理過程結束,全域環境直到頁面退出再銷毀。

  作用域鏈也解釋了為什麼函數可以在內部遞歸呼叫自身:函數名稱是函數定義所在執行環境對應變數物件的屬性,然後在函數內部執行環境中,就可以沿著作用域鏈向外上溯一層訪問函數名指向的函數物件了。如果在函數內部將函數名稱指向了一個新函數,遞歸呼叫時就會不正確了:
複製程式碼 程式碼如下:

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

關於作用域和宣告提升,再看一個例子:
複製程式碼 程式碼如下:


程式碼如下:



程式碼如下:


程式碼如下:

console.info(name);//linjisong


這裡最不直觀的可能是第3行輸出undefined,因為在全域中已經定義過name了,不過按照上面解析的步驟去解析一次,就可以得到正確的結果了。另外強調一下,在ECMAScript中只有全域執行環境和函數執行環境,對應的也只有全域作用域和函數作用域,沒有區塊作用域-雖然有區塊語句。




複製程式碼


程式碼如下:


function fn(){
{
var blockScope = 'b'; blockScope = fnScope; } console.info(blockScope);//沒有區塊作用域,所以可以在整個函數作用域內存取blockScope console.info(fnScope); } fn();//ba,a console.info(blockScope);//ReferenceError,console.info(blockScope);//ReferenceError,console.info(blockScope);//ReferenceError,console.info(blockScope);//ReferenceError,函數作用域外,不能存取內部定義的變數console.info(fnScope);//ReferenceError
對於作用域鏈,也可以使用with、try-catch語句的catch區塊來延長:

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

7、函數內部物件與this

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

(1)全域環境中的this

  在全域環境中,this指向全域物件本身,在瀏覽器中也就是window,這裡也可以把全域環境中的this理解為全域執行環境對應的變數對象,在全域環境定義的變數和函數都是這個變數物件的屬性:
複製程式碼 程式碼如下:

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

  在函數環境中,this是一個內部屬性對象,可以理解成函數對應的活動對象的一個屬性,而這個內部屬性的值是動態的。那this值是怎麼動態決定的呢?

•使用new呼叫時,函數也稱為建構函數,這個時候函數內部的this被指定為新建立的物件。
複製程式碼 程式碼如下:

function fn(){


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


需要注意區分函數執行環境中定義的屬性(也即活動物件的屬性)和this物件的屬性,在使用陣列元素方式呼叫函數時,函數內部this指向數組本身,因此上例最後輸出undefined。

•作為一般函數呼叫時,this指向全域物件。
•當物件的方法呼叫時,this指向呼叫這個方法的物件。   看下面的範例: 複製程式碼

程式碼如下:


var🎜> 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的指定是動態的,是在調用時指定的,而不管函數是單獨定義的還是作為物件方法定義的。也正是因為函數作為對象的方法呼叫時this指向這個呼叫對象,所以在函數內部返回this時才能夠延續呼叫對象的下一個方法-也就是鍊式操作(jQuery的一大特色)。

•使用apply()、call()或bind()呼叫函數時,this指向第一個參數物件。如果沒有傳入參數或傳入的是null和undefined,this指向全域物件(在ES5的嚴格模式下會設為null)。如果傳入的第一個參數是一個簡單類型,會將this設定為對應的簡單類型包裝物件。
複製程式碼 程式碼如下:

var name = 'function; (){
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呼叫函數,執行傳入的第一個參數對象,因此返回person2.name
console.info(fn.call(person2));//hujinxing
var newFn = fn.bind(person3);//ES5中新增方法,會建立一個新函數實例返回,內部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.Name ());//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、函数属性和方法

  函数是一个对象,因此也可以有自己的属性和方法。不过函数属性和方法与函数内部属性很容易混淆,既然容易混淆,就把它们放一起对照着看,就好比一对双胞胎,不对照着看,不熟悉的人是区分不了的。

  先从概念上来区分一下:

(1)函数内部属性:可以理解为函数相应的活动对象的属性,是只能从函数体内部访问的属性,函数每一次被调用,都会被重新指定,具有动态性。

(2)函数属性和方法:这是函数作为对象所具有的特性,只要函数一定义,函数对象就被创建,相应的属性和方法就可以访问,并且除非你在代码中明确赋为另一个值,否则它们的值不会改变,因而具有静态性。有一个例外属性caller,表示调用当前函数的函数,也是在函数被调用时动态指定,在《JavaScript高级程序设计(第3版)》中也因此将caller属性和函数内部属性arguments、this一起讲解,事实上,在ES5的严格模式下,不能对具有动态特性的函数属性caller赋值。

  光从概念上区分是非常抽象的,也不是那么容易理解,再把这些属性列在一起比较一下(没有列入一些非标准的属性,如name):

类别 名称 继承性 说明 备注
函数内部属性 this - 函数据以执行的环境对象 和一般面向对象语言有很大区别
arguments -

表示函数实际参数的类数组对象

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

1、length属性表示实际接收到的参数个数

2、callee属性指向函数对象本身,即有:

  fn.arguments.callee === fn

3、caller属性主要和函数的caller相区分,值永远都是undefined

函数属性 caller 调用当前函数的函数 虽然函数一定义就可访问,但是不在函数体内访问时永远为null,在函数体内访问时返回调用当前函数的函数,在全局作用域中调用函数也会返回null
length 函数形式参数的长度 就是定义函数时命名的参数个数
prototype 函数原型对象 原型对象是ECMAScript实现继承的基础
constructor 继承自Object,表示创建函数实例的函数,也就是Function() 值永远是Function,也就是内置的函数Function()
函数方法 apply 调用函数自身,以(类)数组方式接受参数

这三个方法主要作用是动态绑定函数内部属性this

1、apply和call在绑定之后会马上执行

2、bind在绑定之后可以在需要的时候再调用执行

call 调用函数自身,以列举方式接受参数
bind 绑定函数作用域,ES5中新增
toLocalString 覆盖

覆盖了Object类型中的方法,返回函数体

不同浏览器实现返回可能不同,可能返回原始代码,也可能返回去掉注释后的代码

toString 覆盖
valueOf 覆盖
hasOwnProperty 直接继承自Object类型的方法,用法同Object
propertyIsEnumerable
isPropertyOf

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

複製程式碼 程式碼如下:

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

  for(l) 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()函數,這個函數也用於擴展作用域,但是可以不用馬上執行函數,它傳回一個函數實例,將傳入給它的第一個參數作為原函數的作用域。它的一個可能的實作如下:

複製程式碼 程式碼如下:
function bindscope ){
var that = this;
return function(){
that.apply(scope, arguments);
}
}
Function.prototype.bind = bind;

這裡涉及了一個閉包的概念,明天再繼續。
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

前端熱敏紙小票打印遇到亂碼問題怎麼辦? 前端熱敏紙小票打印遇到亂碼問題怎麼辦? Apr 04, 2025 pm 02:42 PM

前端熱敏紙小票打印的常見問題與解決方案在前端開發中,小票打印是一個常見的需求。然而,很多開發者在實...

神秘的JavaScript:它的作用以及為什麼重要 神秘的JavaScript:它的作用以及為什麼重要 Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

誰得到更多的Python或JavaScript? 誰得到更多的Python或JavaScript? Apr 04, 2025 am 12:09 AM

Python和JavaScript開發者的薪資沒有絕對的高低,具體取決於技能和行業需求。 1.Python在數據科學和機器學習領域可能薪資更高。 2.JavaScript在前端和全棧開發中需求大,薪資也可觀。 3.影響因素包括經驗、地理位置、公司規模和特定技能。

JavaScript難以學習嗎? JavaScript難以學習嗎? Apr 03, 2025 am 12:20 AM

學習JavaScript不難,但有挑戰。 1)理解基礎概念如變量、數據類型、函數等。 2)掌握異步編程,通過事件循環實現。 3)使用DOM操作和Promise處理異步請求。 4)避免常見錯誤,使用調試技巧。 5)優化性能,遵循最佳實踐。

如何實現視差滾動和元素動畫效果,像資生堂官網那樣?
或者:
怎樣才能像資生堂官網一樣,實現頁面滾動伴隨的動畫效果? 如何實現視差滾動和元素動畫效果,像資生堂官網那樣? 或者: 怎樣才能像資生堂官網一樣,實現頁面滾動伴隨的動畫效果? Apr 04, 2025 pm 05:36 PM

實現視差滾動和元素動畫效果的探討本文將探討如何實現類似資生堂官網(https://www.shiseido.co.jp/sb/wonderland/)中�...

如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? 如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? Apr 04, 2025 pm 05:09 PM

如何在JavaScript中將具有相同ID的數組元素合併到一個對像中?在處理數據時,我們常常會遇到需要將具有相同ID�...

JavaScript的演變:當前的趨勢和未來前景 JavaScript的演變:當前的趨勢和未來前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

console.log輸出結果差異:兩次調用為何不同? console.log輸出結果差異:兩次調用為何不同? Apr 04, 2025 pm 05:12 PM

深入探討console.log輸出差異的根源本文將分析一段代碼中console.log函數輸出結果的差異,並解釋其背後的原因。 �...

See all articles