1. js介紹
js是為了實現網絡交互而是設計的腳本語言,有以下三部分組成
- ECMAScript,由ECMA-262定義,提供核心功能,其實web瀏覽器是其宿主環境之一,組成部分:語法,類型,語句,關鍵字,保留字,操作符,對象
- 文檔對象模型(DOM),提供訪問和操作頁面的接口和方法,DOM把整個頁面映射為一個多層節點結構,根據其提供的API,開發人員可自如增刪改換節點。
DOM1=DOM Core(映射文檔結構) + DOM html(對html的對象和方法)
DOM2新模塊:
- DOM視圖(DOM views),跟蹤不同文檔的視圖接口
2 .DOM事件(DOM Events),定義事件和時間處理的接口 - DOM樣式(DOM style),基于CSS為元素應用樣式接口
- DOM遍歷和范圍(DOM Traversal and Range),遍歷和操作文檔樹的接口
DOM3新模塊:
.DOM驗證(DOM Validation)文檔驗證方法并支持XML1.0規范
- 瀏覽器對象模型(BOM)
根本來說,處理瀏覽器的窗口和框架,但人們習慣將一下擴展也算成BOM的一部分
1.彈出新瀏覽器窗口
2.移動 關閉 縮放瀏覽器
- 提供瀏覽器詳細信息的navigator對象
4.瀏覽器所加載頁面的詳細信息location對象
5.用戶顯示器分辨率信息screen對象
6.對cookie的支持
7.自定義對象
2.在html中使用js
通過<script>引入,常用屬性
1.async,表示應該立即下載腳本,但不妨礙頁面的其他操作,會在頁面load之前進行
2.defer,表示文檔可以延遲到文檔全部被解析和顯示后再執行
3.src,執行代碼的外部文件
4.type,表示編寫代碼使用的腳本語言的內容類型(也成為MIME類型),一般是text/javascript(這個是默認的)
使用方式
1.直接放入內部元素,內部/script應替換成</script>,以免造成誤解
2.嵌入式
一般放在body后面,這樣在解析JS語言之前,頁面內容已經完全展示在瀏覽器上,不要出現瀏覽器明顯延遲的問題
<noscript>在不支持js的瀏覽器顯示替代的內容
3.基本概念
- 標識符
駝峰大小寫格式,如firstStudent - 注釋
//單行
/*
*多行
*/ - 嚴格模式
"use strict" - 變量
松散類型
var message; //沒初始化,因此是undefined
不使用var標識符的話就是全局變量
- 類型typeof
undefined,boolean, string,object,number,function - number:
Infinity(正無窮)
-Infinity(負無窮)
isFinite()判斷是否有窮
NaN非數值isNaN()判斷是否為數值
將非數值轉化為數值
Number parseInt parseFloat - object對象
初始化 var o = new object();
屬性方法
Constructor創建當前對象的函數
hasOwnProperty(propertyName)給定的屬性在當前實例中是否出現
** isPrototypeOf(object) **別傳入的對象是否為另一個對象的原型
PropertyIsEnumerable判斷給定的屬性是否可以用 for...in 語句進行枚舉。
toLocaleString 方法返回執行環境對應的字符串。
toString()對象字符串表示
valueOf()返回對象的字符串數值或布爾表示 - 語句
if do-while while for switch
for-in枚舉
lable-代碼中添加標簽,以便后來使用???
break 跳出整個循環 continue跳出當前循環
with 作用域設定到一個對象中
4.變量,作用域,內存問題
執行環境類型——全局,局部
其中內部環境可通過作用域鏈訪問所有的外部環境,但外部環境不能內部環境的任何變量和函數
4.1延長作用域鏈
執行下列語句時,作用域延長
try-catch
with
- 栗子1
function buildUrl(){
var qs="?debug=true";
with(location){
var url=href+qs;
}
return url;
}
var result=buildUrl();
alert(result);
輸出: 靜態頁地址+qs的值。
原因:
1.由于with語句塊中作用域的‘變量對象’是只讀的,所以在他本層定義的標識符,不能存儲到本層,而是存儲到它的上一層作用域
2.with代碼塊中,javascript引擎對變量的處理方式是:先查找是不是該對象的屬性,如果是,則停止。如果不是繼續查找是不是局部變量。
var o={href:"sssss"};
var href="1111";
function buildUrl(){
var qs="?debug=true";
with(o){
href="2222";
var url=href+qs;
}
return url;
}
var result=buildUrl();
alert(result);
alert(href);
輸出: 結果:2222?debug=true + 1111
4.2 JavaScript作用域之沒有塊級作用域
<script>
function test(o){
var i=0;
if(typeof o == "object"){
var j=0;
for(var k=0; k<10; k++){
document.writeln("k="+k);
}
document.writeln("k2="+k);
}
document.writeln("j="+j);
}
test(new String("pppp"));
</script>
輸出結果為:k=0 k=1 k=2 k=3 k=4 k=5 k=6 k=7 k=8 k=9 +++k=10 j=0
這是因為,由于JavaScript中不存在塊級作用域,因此函數中聲明的所有變量,無論是在哪里聲明的,在整個函數中它們都有定義。
<script>
var scope="global";
function f(){
alert(scope);
var scope="local";
alert(scope);
}
f();
</script>
結果:第一個alert輸出:underfined而不是global,第二個alert輸出local
與下列函數等價
function f(){
var scope;
alert(scope);
var scope="local";
alert(scope);
}
5.引用類型
5.1Object類型
創建有兩種方式:
1.new操作符后跟Object構造函數
var person = new Object();
person.name = "Nicholas";
person.age = 29;
2.字面量表示法
var person = {
name: "Nicholas",
age: 20,
5:true
};
這里所有的數值屬性名都會被轉換為字符串
訪問方法
alert(person.name);
alert(person[name])
5.2Arrary類型
構造
var colors = ["red","green"];
var arrays = new Array(3);//new可省略
var names = new Array["Mia"];
數組尾部添加新項
colors[colors.length] = "pink"
js判斷數組類型的方法
- instanceof
var a=[];
console.log(a instanceof Array) //返回true
- constructor
constructor 屬性返回對創建此對象的數組函數的引用
console.log([].constructor == Array);
console.log({}.constructor == Object);
console.log("string".constructor == String);
console.log((123).constructor == Number);
console.log(true.constructor == Boolean);
較為嚴謹并且通用的方法:
function isArray(object){
return object && typeof object==='object' &&
Array == object.constructor;
}
!!注意:
使用instaceof和construcor,被判斷的array必須是在當前頁面聲明的!比如,一個頁面(父頁面)有一個框架,框架中引用了一個頁面(子頁面),在子頁面中聲明了一個array,并將其賦值給父頁面的一個變量,這時判斷該變量,Array == object.constructor;會返回false;
原因:
1、array屬于引用型數據,在傳遞過程中,僅僅是引用地址的傳遞。
2、每個頁面的Array原生對象所引用的地址是不一樣的,在子頁面聲明的array,所對應的構造函數,是子頁面的Array對象;父頁面來進行判斷,使用的Array并不等于子頁面的Array;切記,不然很難跟蹤問題!
- 特性判斷法
function isArray(object){
return object && typeof object==='object' &&
typeof object.length==='number' &&
typeof object.splice==='function' &&
//判斷length屬性是否是可枚舉的 對于數組 將得到false
!(object.propertyIsEnumerable('length'));
}
有length和splice并不一定是數組,因為可以為對象添加屬性,而不能枚舉length屬性,才是最重要的判斷因子。備注:如果 proName 存在于 object 中且可以使用一個 For…In 循環窮舉出來,那么 propertyIsEnumerable 屬性返回 true
- 最簡單的方法
function isArray(o) {
return Object.prototype.toString.call(o) === ‘[object Array]‘;
}
轉化方法
- arrayObject.toLocaleString()
返回值:
arrayObject 的本地字符串表示。 - toString()
把數組轉換為字符串,并返回結果。返回值與沒有參數的 join() 方法返回的字符串相同。
alert(colors.join(",")) - valueOf
valueOf()方法返回 Array 對象的原始值。實際上,為了創建這個字符串會調用數組的每一項的toString()方法。
棧方法
棧是一種LIFO(Last-In-First-Out,后進先出)的數據結構ECMAScript為數組提供了push()和pop()方法,可以實現類似棧的行為。分別添加到數組末尾和從數組末尾移除最后一項。
隊列方法
shift:從數組中把第一個元素刪除,并返回這個元素的值。
unshift: 在數組的開頭添加一個或更多元素,并返回新的長度
push:在數組的中末尾添加元素,并返回新的長度
pop:從數組中把最后一個元素刪除,并返回這個元素的值。
unshift比push要慢差不多100倍!因此,平時還是要慎用unshift,特別是對大數組。那如果一定要達到unshift的效果,可以借助于Array的reverse方法,Array的reverse的方法能夠把一個數組反轉。先把要放進數組的元素用push添加,再執行一次reverse
重排序方法
reverse()——反轉
sort()——升序排列數組
操作方法
-
concat()方法
基于當前數組中所有項創建新數組。
var colors = ["red","green","blue"];
var colors2 = colors.concat("yellow",["black","brown"]);
alert(colors);
alert(colors2);
結果為colors為數組[“red”,”green”,”blue”];
colors2為數組[“red”,”green”,”blue”,”yellow”,”black”,”brown”];
concat()方法只是用當前數組重新創建一個新的數組,因此當前數組保持不變(colors數組不變
-
slice()方法
slice()方法:基于當前數組中的一個或多個項創建一個新數組。
slice()方法中可以有一個或者兩個參數(代表數組的索引值,0,1,2……)。
接收一個參數時:返回當前數組中從此參數位置開始到當前數組末尾間所有項。
接收兩個參數時:返回當前數組中兩個參數位置間的所有項,但不返回第二個參數位置的項。
參數也可以為負數,表示從末尾算起,-1代表最后一個,使用方法和正數一樣。
var colors = ["red","green","blue","yellow","black","brown"];
var colors2 = colors.slice(2);
var colors3 = colors.slice(1,4);
var colors4 = colors.slice(2,-2);
var colors5 = colors.slice(-3,-1);
console.log(colors2);
console.log(colors3);
console.log(colors4);
console.log(colors5);
結果為:
[“blue”, “yellow”, “black”, “brown”]
[“green”, “blue”, “yellow”]
[“blue”, “yellow”]
[“yellow”, “black”]
-
splice()
splice()主要用途是向當前數組的中間插入項,可以進行刪除、插入、替換操作。會返回一個數組,包含從原始項中刪除的項(若果沒有刪除,返回一個空數組)
刪除:兩個參數,刪除起始項的位置和刪除的項數。
var colors = ["red","green","blue"];
var removed = colors.splice(1,2);
alert(colors); //red
alert(removed); //green,blue
插入:在指定位置插入任意數量項,包括兩個基本參數(即刪除操作中的兩個參數類型)和要插入項的參數,兩個基本參數為起始位置和0(要刪除的項數應為0項),要插入的項參數可以是任意個(”red”,”green”,”blue”)。
var colors = ["red","green","blue"];
var removed = colors.splice(1,0,"yellow","orange");
alert(colors); //"red","yellow","orange","green","blue"
alert(removed); //空數組
替換:向指定位置插入任意數量的項同時刪除任意數量的項,插入項數和刪除項數可以不同。參數包括兩個基本參數(即刪除操作中的兩個參數類型)和要插入項的參數。
var colors = ["red","green","blue"];
var removed = colors.splice(1,1,"purple","black");
alert(colors); //"red","purple","black","blue"
alert(removed); //"green"
位置方法
indexOf() 方法
返回某個指定的字符串值在字符串中首次出現的位置。lastIndexOf() 方法
返回一個指定的字符串值最后出現的位置,在一個字符串中的指定位置從后向前搜索。substr
substr(start,length)表示從start位置開始,截取length長度的字符串。
var src="images/off_1.png";
alert(src.substr(7,3));
彈出值為:offsubstring
substring(start,end)表示從start到end之間的字符串,包括start位置的字符但是不包括end位置的字符。
var src="images/off_1.png";
alert(src.substring(7,10));
彈出值為:off
迭代方法
var arr = [3,4,5,6,7,"a"];
var isNum = function(elem,index,AAA){
return !isNaN(elem);
}
var toUpperCase = function(elem){
return String.prototype.toUpperCase.apply(elem);
}
var print = function(elem,index){
console.log(index+"."+elem);
}
-
every
對數組中的每一項執行測試函數,直到獲得對指定的函數返回 false 的項。使用此方法 可確定數組中的所有項是否滿足某一條件,類似于&&的含義
var res = arr.every(isNum);
console.log(res);//false;
-
some
對數組中的每一項執行測試函數,直到獲得返回 true 的項。 使用此方法確定數組中的所有項是否滿足條件.類似于||的含義
res = arr.some(isNum);
console.log(res);//true
-
filter
對數組中的每一項執行測試函數,并構造一個新數組,返回 true的項被添加進新數組。 如果某項返回 false,則新數組中將不包含此項
res = arr.filter(isNum);
console.log(res);//[3, 4, 5, 6, 7]
-
map
對數組中的每一項執行函數并構造一個新數組,并將原始數組中的每一項的函數結添加進新數組。
res = arr.map(toUpperCase);
console.log(res);//["3", "4", "5", "6", "7", "A"]
-
forEach
對數組中的每一項執行函數,不返回值
res = arr.forEach(print);
console.log(res);
縮小方法
- reduce
array.reduce(callbackfn,[initialValue])
function callbackfn(preValue,curValue,index,array){}
**preValue
**: 上一次調用回調返回的值,或者是提供的初始值(initialValue)
**curValue
**: 數組中當前被處理的數組項
**index
**: 當前數組項在數組中的索引值
**array
**: 調用 reduce()
Date類型
Date.parse接受一個字符串參數,如果可以轉化,將轉換為對應的毫秒數,否則返回 NaN;
Date.UTC最少接受兩個參數,分別表示年份和月份(0·11),其他的日期,小時(0-24)、分鐘、秒,可以指定也可以不指定,不指定時默認為 0;
dateObject.getTime() 0~... 從GTM1970年1月1日0:00:00開始計算的毫秒數。
dateObject.getYear() 70~... 指定的年份減去1900,2000年后為4位數表示的年份。
dateObject.getFullYear() 1970~... 4位數年份,適用于版本4以上的瀏覽器。
dateObject.getMonth() 0~11 一年中的月份(1月為0)。
dateObject.getDate() 1~31 一月中的日期。
dateObject.getDay() 0~6 星期(星期日為0)。
dateObject.getHours() 0~23 一天中指定的小時數,采用24小時制。
dateObject.getMinutes() 0~59 指定小時內的分鐘數。
dateObject.getSeconds() 0~59 指定分鐘內的秒數。
dateObject.setTime(val) 0~... 從GTM1970年1月1日0:00:00開始計算的毫秒數。
dateObject.setYear(val) 70~... 指定的年份減去1900,2000年后為4位數表示的年份。
dateObject.setMonth(val) 0~11 一年中的月份(1月為0)。
dateObject.setDate(val) 1~31 一月中的日期。
dateObject.setDay(val) 0~6 星期(星期日為0)。
dateObject.setHours(val) 0~23 一天中的小時數,采用24小時值。
dateObject.setMinutes(val) 0~59 指定小時內的分鐘數。
dateObject.setSecond(val) 0~59 指定小時內的秒數。
當前時間
var start = Date.now()
格式化
var d = new Date();
console.log(d); // 輸出:Mon Nov 04 2013 21:50:33 GMT+0800 (中國標準時間)
console.log(d.toDateString()); // 日期字符串,輸出:Mon Nov 04 2013
console.log(d.toGMTString()); // 格林威治時間,輸出:Mon, 04 Nov 2013 14:03:05 GMT
console.log(d.toISOString()); // 國際標準組織(ISO)格式,輸出:2013-11-04T14:03:05.420Z
console.log(d.toJSON()); // 輸出:2013-11-04T14:03:05.420Z
console.log(d.toLocaleDateString()); // 轉換為本地日期格式,視環境而定,輸出:2013年11月4日
console.log(d.toLocaleString()); // 轉換為本地日期和時間格式,視環境而定,輸出:2013年11月4日 下午10:03:05
console.log(d.toLocaleTimeString()); // 轉換為本地時間格式,視環境而定,輸出:下午10:03:05
console.log(d.toString()); // 轉換為字符串,輸出:Mon Nov 04 2013 22:03:05 GMT+0800 (中國標準時間)
console.log(d.toTimeString()); // 轉換為時間字符串,輸出:22:03:05 GMT+0800 (中國標準時間)
console.log(d.toUTCString()); // 轉換為世界時間,輸出:Mon, 04 Nov 2013 14:03:05 GMT
RegExp
function
函數內部屬性只要包括兩個特殊的對象:arguments和this。
函數屬性包括:length和prototype
函數方法(非繼承)包括:apply()和call()
繼承而來的函數方法:bind()、toString()、toLocaleString()、valueOf()
- 沒有重載
js不存在重載的概念,后面的方法會覆蓋先前的同名的方法。 - 函數聲明和函數表達式
1)函數聲明(Function Declaration);
// 函數聲明
function funDeclaration(type){
return type==="Declaration";
}
2)函數表達式(Function Expression)。
// 函數表達式
var funExpression = function(type){
return type==="Expression";
}
上面的代碼看起來很類似,感覺也沒什么太大差別。但實際上,Javascript函數上的一個“陷阱”就體現在Javascript兩種類型的函數定義上。下面看兩段代碼:
funDeclaration("Declaration");//=> true
function funDeclaration(type){
return type==="Declaration";
}
funExpression("Expression");//=>error
var funExpression = function(type){
return type==="Expression";
}
用函數聲明創建的函數funDeclaration可以在funDeclaration定義之前就進行調用;而用函數表達式創建的funExpression函數不能在funExpression被賦值之前進行調用。
代碼1段JS函數等同于:
function funDeclaration(type){
return type==="Declaration";
}
funDeclaration("Declaration");//=> true
代碼2段JS函數等同于:
var funExpression;
funExpression("Expression");//==>error
funExpression = function(type){
return type==="Expression";
}
再來個例子
var sayHello;
console.log(typeof (sayHey));//=>function
console.log(typeof (sayHo));//=>undefined
if (true) {
function sayHey() {
console.log("sayHey");
}
sayHello = function sayHo() {
console.log("sayHello");
}
} else {
function sayHey() {
console.log("sayHey2");
}
sayHello = function sayHo() {
console.log("sayHello2");
}
}
sayHey();// => sayHey2
sayHello();// => sayHello
等同于
var sayHello;
function sayHey() {
console.log("sayHey");
}
function sayHey() {
console.log("sayHey2");
}
console.log(typeof (sayHey));//=>function
console.log(typeof (sayHo));//=>undefined
if (true) {
//hoisting...
sayHello = function sayHo() {
console.log("sayHello");
}
} else {
//hoisting...
sayHello = function sayHo() {
console.log("sayHello2");
}
}
sayHey();// => sayHey2
sayHello();// => sayHello
Javascript 中函數聲明和函數表達式是存在區別的,函數聲明在JS解析時進行函數提升,因此在同一個作用域內,不管函數聲明在哪里定義,該函數都可以進行調用。而函數表達式的值是在JS運行時確定,并且在表達式賦值完成后,該函數才能調用。這個微小的區別,可能會導致JS代碼出現意想不到的bug,讓你陷入莫名的陷阱中。
- 函數的內部屬性
-
arguments
有兩個特殊的對象:arguments和this。其中,arguments它是一個類數組對象,包含著傳入函數中的所有參數。雖然arguments的主要用途是保存函數參數,但這個對象含所有一個名叫callee的屬性,該屬性是一個指針,指向擁有這個arguments對象的函數。
下面這個非常經典的階乘函數。
function factorial (num){
if(num <= 1){
return 1;
} else{
return num * factorial(num-1);
}
}
定義階乘函數一般都會用到遞歸算法,如上面代碼所示,在有函數名字,并且函數名字以后也不會改變的情況下,這種定義沒問題。但是這個函數的執行與函數名factorial緊緊耦合在了一起,為了消除這種緊密耦合現象(函數名字改變等情況),可以使用arguments.callee。
function factorial(num){
if(num<=1){
return 1;
} else{
return num * arguments.callee(num-1);
}
}
在這個重寫后的factorial()函數的函數體內,沒有再引用函數名factorial。這樣,無論引用函數時使用的是什么名字,都可以保證正常完成遞歸調用。例如:
var trueFactorial=factorial;
factorial=function()
{
return 0;
};
alert(trueFactorial(5));//120
alert(factorial(5));//0
在此,變量trueFactorial獲得了factorial的值,實際上是另一個位置上保存了一個函數的指針。然后,我們又將一個簡單的返回了0的函數賦值給了factorial變量。如果像原來factorial()那樣不用使用arguments.callee,調用trueFactorial()就會返回0。可是,在解除了函數體內的代碼與函數名的耦合狀態之后,trueFactorial()仍然能夠正常的計算階乘;至于factorial(),它現在只是一個返回0的函數。
-
this
函數內部的另一個特殊對象是this,其行為與java和c#中的this大致類似。換句話說,this引用的是函數據以執行的環境對象,或則也可以說是this值(當在網頁的全局作用中調用函數時,this對象引用的就是window)。如下例子:
window.color="red";
var o={color:"blue"};
function sayColor()
{
alert(this.color);
}
sayColor();//red
o.sayColor=sayColor;
o.sayColor();//blue
- apply()方法
接收兩個參數:一個是在其中運行函數的作用域,另一個是參數數組。其中,第二個參數可以使Array的實例,也可以是arguments對象
unction sum(num1,num2)
{
return num1+num2;
}
function callSum1(num1,num2)
{
return sum.apply(this,arguments);//傳入arguments對象
}
function callSum2(num1,num2)
{
return sum.apply(this,[num1,num2]);//傳入數組
}
alert(callSum1(10,10));//20
alert(callSum2(10,10));//20
- call()方法
對于call()方法而言,第一個參數是this值沒有變化,變化的是其余參數都直接傳遞給函數。換句話講,在使用call()方法時,傳遞給函數的參數必須逐個列舉出來,如下例子:
function sum(num1,num2)
{
return num1+num2;
}
function callSum(num1,num2)
{
return sum.call(this,num1,num2);
}
alert(callSum(10,10));//20
事實上,傳遞參數并非apply()和call()正真的用武之地;它們正真強大的地方是能夠擴充函數賴以運行的作用域。如下例子:
window.color="red";
var o={color:"blue"};
function sayColor()
{
alert(this.color);
}
sayColor();//red
sayColor.call(this);//red
sayColor.call(window);//red
sayColor.call(o);//blue
這一次,sayColor()也是作為全局函數定義的,而且當在全局作用域中調用它時,它確實會顯示“red”,因為對this.color的求值會轉換成對window.color的求值。而sayColor.call(this)和sayColor.call(window),則是兩種顯示的在全局作用域中調用函數的方式,結果當然都會顯示“red”。但是,當運行sayColor.call(o)時,函數的執行環境就不一樣了,因為此時函數體內的this對象指向了o,于是結果顯示的是“blue”。
使用call()或apply()來擴充作用域的最大好處,就是對象不需要與方法有任何耦合關系。在前面例子的第一個版本中,我們是先將sayColor()函數放到了對象o中,然后再通過o來調用它;而在這里重寫的例子中,就不需要先前那個多余的步驟了。
- bind()
window.color="red";
var o={color:"blue"};
function sayColor()
{
alert(this.color);
}
var objectSayColor=sayColor.bind(o);
objectSayColor();//blue
在這里,sayColor()調用bind()并傳入對象o,創建了objectSayColor()函數。objectSayColor()函數的this值等于o,因此即使是在全局作用域中調用這個函數,也會看到“blue”。
函數表達式
理解作用域鏈是理解閉包的關鍵;
(1)什么是執行環境
執行環境有二種: 全局執行環境;局部執行環境---function里面;
執行流進入函數執行時,創建執行環境;函數執行結束,回收!
(2)變量對象
理解變量對象非常重要,變量對象在函數中也稱為 活動對象,我還是喜歡說成變量對象。
每一個執行環境都有一個變量對象,變量對象會保存這個執行環境的變量和方法;我們不難想到全局的變量對象是誰? window對象
我們在全局中添加變量和方法都是添加到window里。函數執行時 執行環境中就會創建變量對象;一般來說:函數調用完畢之后無論是執行環境(出棧),還是變量對象都是回收的。閉包的出現使得變量對象不回收;
(3)作用域鏈?
什么是作用域鏈:作用域鏈的本質是 指向變量對象的一組有序指針;字面上很難理解,我第一次看不明白!
有什么作用:在一個執行環境中對變量和方法進行有序(正確)訪問;
什么時候創建: 一個函數聲明的時候就創建了,作用域鏈會保存在一個函數的內部屬性[[Scope]]中;
注意:執行流進入函數是: 創建執行環境->將作用域鏈(利用[[Scope]]屬性) 復制到執行環境中->創建變量對象(活動對象)->將變量對象的引用(指針)導入作用域鏈的最前端->執行代碼
具體請看下面的代碼
function compare(value1,value2){
if(value1<value2){return 1;}
else if(value1>value2){return -1;}
else{return 0;}
}
var result=compare(5,10)//1
我們可以看到,作用域鏈是什么:有序指針,前面說的作用域最前端在就是0位置。 查找變量或者函數是最前端開始的,0指向的活動對象沒有你要找的變量,再去1找。0-1其實
從代碼的角度上看其實就是從函數內部一直向函數外部找標識符(變量或者函數),找到立即停止,不會再向上查找。這就是作用域鏈!
- 閉包
定義: 閉包是有權訪問另外一個函數作用域變量的函數;注意閉包是一個函數!
創建閉包的主要的方法:在一個函數里面創建一個函數(閉包); 比如
<script type="text/javascript">
function A(value){
var num=value;
return function(){ //閉包
alert(num);
}
}
var B = A(0);
alert(B()); //0
</script>
在這里匿名函數是一個閉包,一般情況下: 一個函數執行完畢之后,無論是作用域鏈還是變量對象都會回收,但是這個匿名函數的作用域鏈里有A()變量對象的引用,所以沒有消除。
還可以訪問變量對象的num;這就是閉包的好處!但是閉包的出現加大內存負擔,就這里而已,我們即使后面不再使用B函數了,A()變量對象也不會消失,javascript不知道你什么時候還會再用,當然我們可以這樣 B=null; 這樣的話匿名函數沒有引用,被回收,A()變量對象也一起回收!
《javascript高級程序設計》中尼古拉斯大神建議我們:可以不使用閉包盡量不使用,萬不得已才使用!
-
塊級作用域
我們都知道javascript是沒有塊級作用域的,比如{}; if(i=0){} while(){} for(){}這些都不會形成塊級作用域; 那么怎么創建 java c#類似功能的塊級作用域?
語法:
(function(){
//塊級作用域
})();
注意: 我們創建一個匿名函數,立即調用,里面的代碼都要運行一遍,而在window中看不見,這不就是塊級作用域嗎? 還有一個好處,這個匿名函數沒有指針,
調用后回收,不會產生垃圾(里面的方法,變量都不需要再訪問的)。簡直就是完美的塊級作用域! 注意格式 (匿名函數)其實就是一個指針,再加上()就是調用了。
-
構造函數中的閉包
(1) 我們知道怎么為對象添加'私有變量' 這樣
function Person(name,age){
this.name=name;//共有屬性
this.age=age;
var school="一中"; //私有屬性
this.GetSchool=function (){return school;}
}
我們這個school是私有的變量,因為閉包的原因, 對象實例自然可以訪問這個變量; 比如 var p=new Person('nos',20); p.GetSchool(); // 一中;
(2)現在來一個奇葩: 靜態私有屬性 ;
(function(){
var school=""; //靜態私有;
Person=function(name,age,value){ //構造函數
this.name=name;this.age=age;
school=value;
};
Person.prototype.GetSchool=function(){alert(school);}
})();
var p=new Person('andy',21,'一中');
p.GetSchool();//一中
var pp=new Person('nos',29,'二中');
pp.GetSchool();//二中
p.GetSchool();//二中
從結果上看 school是對象共有的,私有的屬性, 即靜態私有屬性;
我們看看構造函數是怎么定義的: 沒有使用var ,前面說過即使在函數里面定義,沒有使用var申明,就是window的,為全局的。自然可以在全局使用!
這個匿名函數中調用了一次,只產生一個對象變量,school都是同一個,實現共享。