標籤:
1、Javascript範圍原理
var name = ‘laruence‘;
function echo() {
alert(name); //laruence
var name = ‘eve‘;
alert(name); //eve
alert(age); //指令碼錯誤
}
echo();
運行結果是什麼呢?
上面的問題, 我相信會有很多人會認為應該像注釋那樣的。
執行了一次之後發現第一次alert出來確是undefined。為什麼呢?
JS權威指南中有一句很精闢的描述: ”JavaScript中的函數運行在它們被定義的範圍裡,而不是它們被執行的範圍裡.”
其實在JS中:”一切皆是對象, 函數也是”。
在一個函數被定義的時候, 會將它定義時刻的scope chain連結到這個函數對象的[[scope]]屬性.
在一個函數對象被調用的時候,會建立一個使用中的物件(也就是一個對象), 然後對於每一個函數的形參,都命名為該使用中的物件的命名屬性, 然後將這個使用中的物件做為此時的範圍鏈(scope chain)最前端, 並將這個函數對象的[[scope]]加入到scope chain中.
看個例子:
- var func = function(lps, rps){
- var name = ‘laruence‘;
- ........
- }
- func();
在調用func的時候, 會建立一個使用中的物件(假設為aObj, 由JS引擎先行編譯時刻建立, 後面會介紹),並建立arguments屬性, 然後會給這個對象添加倆個命名屬性aObj.lps, aObj.rps;
對於每一個在這個函數中申明的局部變數和函數定義, 都作為該使用中的物件的同名命名屬性.
有了上面的範圍鏈, 在發生標識符解析的時候, 就會逆向查詢當前scope chain列表的每一個使用中的物件的屬性,如果找到同名的就返回。找不到,那就是這個標識符沒有被定義。
注意到, 因為函數對象的[[scope]]屬性是在定義一個函數的時候決定的, 而非調用的時候, 所以如下面的例子:
- function factory() {
- var name = ‘laruence‘;
- var intro = function(){
- alert(‘I am ‘ + name);
- }
- return intro;
- }
-
- function app(para){
- var name = para;
- var func = factory();
- func();
- }
-
- app(‘eve‘);
當調用app的時候, scope chain是由: {window使用中的物件(全域)}->{app的使用中的物件} 組成.
在剛進入app函數體時, app的使用中的物件有一個arguments屬性, 倆個值為undefined的屬性: name和func. 和一個值為’eve’的屬性para;
此時的scope chain如下:
- [[scope chain]] = [
- {
- para : ‘eve‘,
- name : undefined,
- func : undefined,
- arguments : []
- }, {
- window call object
- }
注意到, 此時的範圍鏈中, 並不包含app的使用中的物件.
在定義intro函數的時候, intro函數的[[scope]]為:
- [[scope chain]] = [
- {
- name : ‘laruence‘,
- intor : undefined
- }, {
- window call object
- }
- ]
從factory函數返回以後,在app體內調用intor的時候, 發生了標識符解析, 而此時的sope chain是:
- [[scope chain]] = [
- {
- intro call object
- }, {
- name : ‘laruence‘,
- intor : undefined
- }, {
- window call object
- }
- ]
因為scope chain中,並不包含factory使用中的物件. 所以, name標識符解析的結果應該是factory使用中的物件中的name屬性, 也就是’laruence’.
所以運行結果是:
I am laruence
現在, 大家對”JavaScript中的函數運行在它們被定義的範圍裡,而不是它們被執行的範圍裡.”這句話, 應該有了個全面的認識了吧?
2、原型和原型鏈
原型使用方式1
var Calculator = function (decimalDigits, tax) {
this.decimalDigits = decimalDigits;
this.tax = tax;
};
然後,通過給Calculator對象的prototype屬性賦值對象字面量來設定Calculator對象的原型。
Calculator.prototype = {
add: function (x, y) {
return x + y;
},
subtract: function (x, y) {
return x - y;
}
};
//alert((new Calculator()).add(1, 3));
這樣,我們就可以new Calculator對象以後,就可以調用add方法來計算結果了。
原型使用方式2
第二種方式是,在賦值原型prototype的時候使用function立即執行的運算式來賦值,即如下格式:
Calculator.prototype = function () { } ();
它的好處在前面的文章裡已經知道了,就是可以封裝私人的function,通過return的形式暴露出簡單的使用名稱,以達到public/private的效果,修改後的代碼如下
Calculator.prototype = function () {
add = function (x, y) {
return x + y;
},
subtract = function (x, y) {
return x - y;
}
return {
add: add,
subtract: subtract
}
} ();
//alert((new Calculator()).add(11, 3));
我們再來說一下如何分布來設定原型的每個屬性吧。
var BaseCalculator = function () {
//為每個執行個體都聲明一個小數位元
this.decimalDigits = 2;
};
//使用原型給BaseCalculator擴充2個對象方法
BaseCalculator.prototype.add = function (x, y) {
return x + y;
};
BaseCalculator.prototype.subtract = function (x, y) {
return x - y;
};
建立完上述代碼以後,我們來開始:
var Calculator = function () {
//為每個執行個體都聲明一個數字
this.tax = 5;
};
Calculator.prototype = new BaseCalculator();
var calc = new Calculator();
alert(calc.add(1, 1));
//BaseCalculator 裡聲明的decimalDigits屬性,在 Calculator裡是可以訪問到的
alert(calc.decimalDigits);
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
// 設定Bar的prototype屬性為Foo的執行個體對象
Bar.prototype = new Foo();
Bar.prototype.foo = ‘Hello World‘;
// 修正Bar.prototype.constructor為Bar本身
Bar.prototype.constructor = Bar;
var test = new Bar() // 建立Bar的一個新執行個體
// 原型鏈
test [Bar的執行個體]
Bar.prototype [Foo的執行個體]
{ foo: ‘Hello World‘ }
Foo.prototype
{method: ...};
Object.prototype
{toString: ... /* etc. */};
3、閉包的範例
例子1:閉包中局部變數是引用而非拷貝
function say667() {
// Local variable that ends up within closure
var num = 666;
var sayAlert = function() { alert(num); }
num++;
return sayAlert;
}
var sayAlert = say667();
sayAlert()
因此執行結果應該彈出的667而非666。
例子2:多個函數綁定同一個閉包,因為他們定義在同一個函數內。
function setupSomeGlobals() {
// Local variable that ends up within closure
var num = 666;
// Store some references to functions as global variables
gAlertNumber = function() { alert(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGolbals(); // 為三個全域變數賦值
gAlertNumber(); //666
gIncreaseNumber();
gAlertNumber(); // 667
gSetNumber(12);//
gAlertNumber();//12
例子3:當在一個迴圈中賦值函數時,這些函數將綁定同樣的閉包
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = ‘item‘ + list[i];
result.push( function() {alert(item + ‘ ‘ + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// using j only to help prevent confusion - could use i
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList的執行結果是彈出item3 undefined視窗三次,因為這三個函數綁定了同一個閉包,而且item的值為最後計算的結果,但是當i跳出迴圈時i值為4,所以list[4]的結果為undefined.
例子4:外部函數所有局部變數都在閉包內,即使這個變數聲明在內建函式定義之後。
function sayAlice() {
var sayAlert = function() { alert(alice); }
// Local variable that ends up within closure
var alice = ‘Hello Alice‘;
return sayAlert;
}
var helloAlice=sayAlice();
helloAlice();
執行結果是彈出”Hello Alice”的視窗。即使局部變數聲明在函數sayAlert之後,局部變數仍然可以被訪問到。
例子5:每次函數調用的時候建立一個新的閉包
function newClosure(someNum, someRef) {
// Local variables that end up within closure
var num = someNum;
var anArray = [1,2,3];
var ref = someRef;
return function(x) {
num += x;
anArray.push(num);
alert(‘num: ‘ + num +
‘\nanArray ‘ + anArray.toString() +
‘\nref.someVar ‘ + ref.someVar);
}
}
closure1=newClosure(40,{someVar:‘closure 1‘});
closure2=newClosure(1000,{someVar:‘closure 2‘});
closure1(5); // num:45 anArray[1,2,3,45] ref:‘someVar closure1‘
closure2(-10);// num:990 anArray[1,2,3,990] ref:‘someVar closure2‘
JavaScript深度學習