首頁 web前端 js教程 淺談關於JavaScript的語言特性分析_javascript技巧

淺談關於JavaScript的語言特性分析_javascript技巧

May 16, 2016 pm 05:37 PM
javascript

前言
在JavaScript中,作用域、上下文、閉包、函數等算是精華中的精華了。對於初級JSer來說,是進階必備。對前端攻城師來說,只有靜下心來,了解這些精華,才能寫出優雅的程式碼​​。

本文旨在總結容易忘記的重要知識,不會講基本的概念。如果對基本知識不太熟悉,就去翻下《 JavaScript權威指南》吧~


語言特性函數表達式

先看程式碼段:

複製程式碼 程式碼如下:

[javascript] view plaincopyprint    return typeof foo; // foo是在內部作用域內有效  
}; 
// foo在外部用於是不可見的   type foo;
f(); // "function" 
var f = function foo(){
    return typeof foo; // foo在內部作用域內有效
};
// foo在外部用於是不可見的
typeof foo; // "undefined"
f(); // "function"


這裡想說一點的就是,在函數表達式中的foo,只能在函數內部引用,外面是不能引用的。

json
很多JavaScript開發人員都錯誤地把JavaScript物件字面量(Object Literals)稱為JSON物件(JSON Objects)。 JSON是設計成描述資料交換格式的,它也有自己的語法,這個語法是JavaScript的子集。

{ “prop”: “val” } 這樣的聲明有可能是JavaScript物件字面量,也有可能是JSON字串,取決於什麼上下文使用它。如果是用在string上下文(用單引號或雙引 號引住,或者從text檔讀取)的話,那它就是JSON字串,如果是用在物件字面量上下文中,那它就是物件字面量。

複製程式碼 程式碼如下:
[javascript] view plaincopyprint?是JSON字串  
var foo = '{ "prop": "val" }'; 
// 這是物件字面量  
var bar = { "prop": "val" }; 
// 這是JSON字串
var foo = '{ "prop": "val" }';
// 這是物件字面量
var bar = { "prop": "val" };



還有一點要知道的是,JSON.parse用來將JSON字串反序列化成對象,JSON.stringify用來將物件序列化成JSON字串。舊版的瀏覽器不支援這個對象,但你可以透過json2.js來實現同樣的功能。

原型


複製程式碼 程式碼如下:function Animal (){  .. .
}
function cat (){ 
    // ...

cat.prototype = new Animal();//這種方式會繼承建構子裡面的。
cat.prototype = Animal.prototype;//這種方式不會繼承建構子裡面的。
//還有一個重要的細節要注意的就是一定要維護自己的原型鏈,新手總是會忘記這個!
cat.prototype.constructor = cat;



如果我們徹底改變函數的prototype屬性(透過分配一個新的物件),那麼原始建構函式的參考就是遺失,這是因為我們建立的物件不包括constructor屬性:

複製程式碼

程式碼如下:function A() {}
function A() {}
A.prototype = {
  x: 10
};
var a = new A();
alert(a.x); // 10
alert(a.constructor === A); / / false!

讓我們一起看下MDN上關於constructor的解釋吧:prototype:Returns a reference to the Object function that created the instance's prototype.因此,對函數的原型引用需要手工恢復:

複製程式碼 程式碼如下:
function A() {}


function A() {}
A.prototype = {
  constructor: A,
  x: 10
};
var a = new A();
alert(a.x); // 10
alert(a.constructor === A); // true

然而,提交prototype屬性不會影響已經創建物件的原型(只有在建構函式的prototype屬性改變的時候才會影響到),就是說新建立的物件才有新的原型,而已建立物件還是引用到原來的舊原型(這個原型已經不能再被修改了)。

複製程式碼 程式碼如下:

function A() {}


function A() {}
A.prototype .x = 10;
var a = new A();
alert(a.x); // 10
A.prototype = {
  constructor: A,
  x: 20
  constructor: A,
  x: 20
  constructor: A,
  x: 20
  y: 30
};
// 物件a是透過隱式的[[Prototype]]引用從原油的prototype上取得的值
alert(a.x); // 10alert (a.y) // undefined

var b = new A();

// 但新物件是從新原型上取得的值

alert(b.x); // 20

alert(b.y) // 30


因此,「動態修改原型將影響所有的物件都會擁有新的原型」是錯誤的,新原型僅在原型修改以後的新建立物件上生效。這裡的主要規則是:對象的原型是對象的創建的時候創建的,並且在此之後不能修改為新的對象,如果仍然引用到同一個對象,可以通過構造函數的顯式prototype引用,對象創建以後,只能對原型的屬性進行新增或修改。 變數物件在函數執行上下文中,VO(variable object)是不能直接存取的,此時由活動物件(activation object)扮演VO的角色。 活動物件是在進入函數上下文時刻被創建的,它透過函數的arguments屬性初始化。 arguments屬性的值是Arguments物件:


複製程式碼


程式碼如下:


function foo(x, z) {
  // 宣告的函數參數數arguments (x, y, z)
  alert(foo.length); // 3  // 真正傳入的參數數量(only x, y)

  alert(arguments.length); // 2

  // 參數的callee是函數本身

  alert(arguments.callee === foo); // true
}

當進入執行上下文(程式碼執行之前)時,VO裡已經包含了下列屬性:1. 函數的所有形參(如果我們是在函數執行上下文中);

•所有函數宣告(FunctionDeclaration, FD);•所有變數宣告(var, VariableDeclaration);另一個經典範例:


複製程式碼


程式碼如下:

alert(x); // function

alert(x); // function

var x = 10;

alert(x); // 10x = 20;function x() {};alert(x); // 20
根據規範函數宣告是在當進入上下文時填入的; 在進入上下文的時候還有一個變數宣告“x”,那麼正如我們在上面所說,變數宣告在順序上跟在函數宣告和形式參數宣告之後,而且在這個進入上下文階段,變數宣告不會幹擾VO中已經存在的同名函數宣告或形式參數宣告。變數相對於簡單屬性來說,變數有一個特性(attribute):{DontDelete},這個特性的意義就是不能用delete運算子直接刪除變數屬性。





複製程式碼


程式碼如下:


a = 10;
alert(.a. ); // 10
alert(delete a); // true
alert(window.a); // undefined
var b = 20;
alert(window.b); // 20alert(delete b); // false

alert(window.b); // still 20。 b is variable,not property!

var a = 10; // 全域上下文中的變數

(function () {

  var b = 20; // function上下文中的局部變數} )();alert(a); // 10alert(b); // 全域變數"b" 沒有宣告.
this在一個函數上下文中,this由呼叫者提供,由呼叫函數的方式決定。如果呼叫括號()的左邊是引用類型的值,this將設為引用類型值 的base物件(base object),在其他情況下(與引用型別不同的任何其它屬性),這個值為null。不過,實際上不存在this的值為null的情況,因為當this的值 為null的時候,其值會被隱式轉換為全域物件。 複製程式碼 程式碼如下:

(function () {
  alert(this); // null => global
})(); 

在這個例子中,我們有一個函數物件但不是引用型別的物件(它不是標示符,也不是屬性存取器),對應地,this值最終設為全域物件。

複製程式碼 程式碼如下:

var foo = {
 {
      alert(this);
    }
};
foo.bar(); // Reference, OK => foo
(foo.bar)(); // Reference, OK => foo
(foo.bar)(); // Reference, OK => foo
(foo.bar = foo.bar)(); // global
(false || foo.bar)(); // global
(foo.bar, foo.bar )(); // global

問題在於後面的三個調用,在應用一定的運算操作之後,在調用括號的左邊的值不在是引用類型。


•第一個例子很明顯—-明顯的引用類型,結果是,this為base對象,即foo。

•在第二個例子中,群組運算子並不適用,想想上面提到的,從引用類型中獲得一個物件真正的值的方法,如GetValue。對應的,在群組運算的返回中———我們得到仍是一個引用類型。這就是this值為什麼要再次設為base對象,也就是foo。

•第三個例子中,與群組運算子不同,賦值運算子呼叫了GetValue方法。傳回的結果是函數物件(但不是引用型別),這表示this設為null,結果是global物件。

•第四個和第五個也是一樣-逗號運算子和邏輯運算子(OR)呼叫了GetValue 方法,相應地,我們失去了引用而得到了函數。並再次設為global。

正如我們所知道的,局部變數、內部函數、形式參數儲存在給定函數的活化物件中。

程式碼如下:


function foo() {


function foo() {
  function {
      alert(this); // global
   }   bar(); // the same as AO.bar()

}


活動物件總是作為this返回,值為null——(即偽代碼的AO.bar()相當於null.bar())。這裡我們再次回到上面描述的例子,this設定為全域物件。

作用域鏈

透過函式建構函式所建立的函數的scope屬性總是唯一的全域物件。

一個重要的例外,它涉及到透過函式建構函式所建立的函式。
複製程式碼


程式碼如下:


var x = 10;


var x = 10;
function f🎜> {
   var y = 20;
   function barFD() { // 函數宣告
      alert(x);
   alert(x); alert(y);');
   barFD(); // 10, 20   barFn(); // 10, "y" is not defined

}foo ();

還有:
複製程式碼


程式碼如下:


var x = 10, y = 10; with ({x: 20}) {
  var x = 30, y = 30;
//這裡的x = 30 覆蓋了x = 20;
  alert(x); // 30
  alert(y); // 30
}alert(x); // 10alert(y); // 30

在進入上下文時發生了什麼事?標識符“x”和“y”已被加入到變數物件中。此外,在程式碼運行階段作如下修改:

•x = 10, y = 10;
•物件{x:20}加入作用域的前端;
•在with內部,遇到了var聲明,當然什麼也沒創建,因為在進入上下文時,所有變數已被解析添加;
•在第二步驟中,僅修改變數“x”,實際上物件中的“x”現在被解析,並添加到作用域鏈的最前端, 「x」為20,變成30;
•同樣也有變數物件「y」的修改,被解析後其值也對應的由10變成30;
•此外,在with聲明完成後,它的特定物件從作用域鏈中移除(已改變的變數「x」-30也從那個物件移除),即作用域鏈的結構恢復到with會加強先前的狀態。
•在最後兩個alert中,目前變數物件的「x」保持相同,「y」的值現在等於30,在with聲明運行中已改變。
函數

關於圓括號的問題

讓我們看下這個問題:『 為何在函數創建後的立即呼叫中必須用圓括號來包圍它? ',答案就是:表達式句子的限制就是這樣的。

依照標準,表達式語句不能以一個大括號 { 開始是因為他很難與程式碼區塊區分,同樣,他也不能以函數關鍵字開始,因為很難與函數宣告區分。即,所以,如果我們定義一個立即執行的函數,在其建立後立即以以下方式呼叫:

複製程式碼 程式碼如下:

function () {
>}();
// 即使有名稱
function foo() {
  ...
}();

我們使用了函數聲明,上述2個定義,解釋器在解釋的時候都會報錯,但是可能有多種原因。如果在全域程式碼裡定義(也就是程式層級),解釋器會將它看做是函數聲明,因為他是以function關鍵字開頭,第一個例子,我們會得到SyntaxError錯誤,因為函數聲明沒有名字(我們前面提到了函數宣告必須有名字)。第二個例子,我們有一個名稱為foo的一個函數聲明正常創建,但是我們仍然得到了一個語法錯誤——沒有任何表達式的分組操作符錯誤。在函數宣告後面他確實是一個分組運算符,而不是一個函數呼叫所使用的圓括號。所以如果我們宣告如下程式碼:

複製程式碼 程式碼如下:
// "foo" 是一個函數聲明,在進入在上下文的時候建立
alert(foo); // 函數
function foo(x) {
   alert(x);
}(1); // 這只是一個分組運算符,不是函數呼叫!
foo(10); // 這是一個真正的函數調用,結果是10

建立表達式最簡單的方式就是用分組運算子括號,裡邊放入的永遠是表達式,所以解釋器在解釋的時候就不會出現歧義。在程式碼執行階段這個的function就會被創建,並且立即執行,然後自動銷毀(如果沒有引用的話)

複製程式碼 程式碼如下:
(function fox) {
 x);
})(1); // 這就是調用,不是分組運算子

上述程式碼就是我們所說的在用括號括住一個表達式,然後透過(1)去呼叫。請注意,下面一個立即執行的函數,周圍的括號不是必須的,因為函數已經處在表達式的位置,解析器知道它處理的是在函數執行階段應該被創建的FE,這樣在函數創建後立即調用了函數。

複製程式碼 程式碼如下:
var foo = { ) {
        return x % 2 != 0 ? 'yes' : 'no';
    }(1)
};
alert(foo.bar); // 'yes'
alert(foo.bar); // 'yes'

就像我們看到的,foo.bar是一個字串而不是一個函數,這裡的函數僅僅用來根據條件參數初始化這個屬性——它創建後並立即調用。


1.因此,」關於圓括號」問題完整的答案如下:
2.當函數不在表達式的位置的時候,分組運算子圓括號是必須的-也就是手工將函數轉化成FE。
3.如果解析器知道它處理的是FE,就沒必要用圓括號。
自由變數:

複製程式碼 程式碼如下:

function testFn() {


function testFn() {
;//對innerFn函數來說,localVar就屬於自由變數。
   function innerFn(innerParam) {
      alert(innerParam localVar);
   }
   return innerFn;   }

   return innerFn;
   }

   return innerFn;閉包的靜態作用域: 
複製程式碼


程式碼如下:


 var = 10;
function foo() {
  alert(z);
}
foo(); // 10 – 使用靜態和動態作用域的時候
(function () {
  var z = 20;
  foo(); // 10 – 使用靜態作用域, 20 – 使用動態作用域
})();
// 將foo當作參數的時候是相同的
(function (funArg) {
    var z = 30;    funArg(); // 10 – 靜態作用域, 30 – 動態作用域

})(foo);

 

理論:因為作用域鏈,使得所有的函數都是閉包(與函數型別無關: 匿名函數,FE,NFE,FD都是閉包)。從實踐角度:以下函數才算是閉包:* 即使創建它的上下文已經銷毀,它仍然存在(例如,內部函數從父函數中返回)


* 在程式碼中引用了自由變數

最後:ECMAScript是一種物件導向語言,支援基於原型的委託式繼承。
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡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

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

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

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

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

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

熱門話題

Java教學
1664
14
CakePHP 教程
1423
52
Laravel 教程
1321
25
PHP教程
1269
29
C# 教程
1249
24
如何使用WebSocket和JavaScript實現線上語音辨識系統 如何使用WebSocket和JavaScript實現線上語音辨識系統 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術 WebSocket與JavaScript:實現即時監控系統的關鍵技術 Dec 17, 2023 pm 05:30 PM

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統 如何利用JavaScript和WebSocket實現即時線上點餐系統 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統 如何使用WebSocket和JavaScript實現線上預約系統 Dec 17, 2023 am 09:39 AM

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

JavaScript與WebSocket:打造高效率的即時天氣預報系統 JavaScript與WebSocket:打造高效率的即時天氣預報系統 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

簡易JavaScript教學:取得HTTP狀態碼的方法 簡易JavaScript教學:取得HTTP狀態碼的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

javascript如何使用insertBefore javascript如何使用insertBefore Nov 24, 2023 am 11:56 AM

用法:在JavaScript中,insertBefore()方法用於在DOM樹中插入一個新的節點。這個方法需要兩個參數:要插入的新節點和參考節點(即新節點將要插入的位置的節點)。

JavaScript與WebSocket:打造高效率的即時影像處理系統 JavaScript與WebSocket:打造高效率的即時影像處理系統 Dec 17, 2023 am 08:41 AM

JavaScript是一種廣泛應用於Web開發的程式語言,而WebSocket則是一種用於即時通訊的網路協定。結合二者的強大功能,我們可以打造一個高效率的即時影像處理系統。本文將介紹如何利用JavaScript和WebSocket來實作這個系統,並提供具體的程式碼範例。首先,我們需要明確指出即時影像處理系統的需求和目標。假設我們有一個攝影機設備,可以擷取即時的影像數

See all articles