搜尋
首頁web前端js教程淺談關於JavaScript的語言特性分析_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
es6数组怎么去掉重复并且重新排序es6数组怎么去掉重复并且重新排序May 05, 2022 pm 07:08 PM

去掉重复并排序的方法:1、使用“Array.from(new Set(arr))”或者“[…new Set(arr)]”语句,去掉数组中的重复元素,返回去重后的新数组;2、利用sort()对去重数组进行排序,语法“去重数组.sort()”。

JavaScript的Symbol类型、隐藏属性及全局注册表详解JavaScript的Symbol类型、隐藏属性及全局注册表详解Jun 02, 2022 am 11:50 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于Symbol类型、隐藏属性及全局注册表的相关问题,包括了Symbol类型的描述、Symbol不会隐式转字符串等问题,下面一起来看一下,希望对大家有帮助。

原来利用纯CSS也能实现文字轮播与图片轮播!原来利用纯CSS也能实现文字轮播与图片轮播!Jun 10, 2022 pm 01:00 PM

怎么制作文字轮播与图片轮播?大家第一想到的是不是利用js,其实利用纯CSS也能实现文字轮播与图片轮播,下面来看看实现方法,希望对大家有所帮助!

JavaScript对象的构造函数和new操作符(实例详解)JavaScript对象的构造函数和new操作符(实例详解)May 10, 2022 pm 06:16 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于对象的构造函数和new操作符,构造函数是所有对象的成员方法中,最早被调用的那个,下面一起来看一下吧,希望对大家有帮助。

JavaScript面向对象详细解析之属性描述符JavaScript面向对象详细解析之属性描述符May 27, 2022 pm 05:29 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于面向对象的相关问题,包括了属性描述符、数据描述符、存取描述符等等内容,下面一起来看一下,希望对大家有帮助。

javascript怎么移除元素点击事件javascript怎么移除元素点击事件Apr 11, 2022 pm 04:51 PM

方法:1、利用“点击元素对象.unbind("click");”方法,该方法可以移除被选元素的事件处理程序;2、利用“点击元素对象.off("click");”方法,该方法可以移除通过on()方法添加的事件处理程序。

foreach是es6里的吗foreach是es6里的吗May 05, 2022 pm 05:59 PM

foreach不是es6的方法。foreach是es3中一个遍历数组的方法,可以调用数组的每个元素,并将元素传给回调函数进行处理,语法“array.forEach(function(当前元素,索引,数组){...})”;该方法不处理空数组。

整理总结JavaScript常见的BOM操作整理总结JavaScript常见的BOM操作Jun 01, 2022 am 11:43 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于BOM操作的相关问题,包括了window对象的常见事件、JavaScript执行机制等等相关内容,下面一起来看一下,希望对大家有帮助。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。