天天看點

JavaScript core筆記

from:http://weizhifeng.net/javascript-the-core.htm

1.對象

①一個對象就是一個屬性集合,并擁有獨立的prototyoe(原型)對象,這個prototype可以是一個對象或者null。

②一個對象的prototype是通過内部的proto屬性來引用的。

2.原型鍊

①原型對象(如person prototype),也是簡單的對象,并且可以擁有它們自己的原型。(對象的prototype也有自己的prototype原型,通過前者内部的proto)。

②原型鍊是一個用來實作繼承和共享屬性的有限對象鍊。

③想象這麼一種情況,我們擁有兩個對象,兩者絕大部分都一緻,隻有一些很小的差别。為了省事,或者說的好聽點,為了節約系統記憶體或提高運作效率,希望将其相同的部分合并,個性區域分為兩個小塊,但是,ES(ESCAScript)沒有類這個概念。這個時候原型鍊就派上用場了,也就引出了原型繼承。但是呢,原型繼承共享性又太強了,沒有了隐私的存在。是以又誕生了一個新的東西—-組合繼承(原型鍊+借用結構函數)和寄生組合式繼承(前面兩個再加個寄生繼承)。

var a={
    x:,
    calculate:function(z){
        return this.x+this.y+z;
    }
};
var b={
    y:,
    __proto__:a
};
var c={
    y:,
    __proto__:a
};
b.calculate();//35=10+20+5
c.calculate();//49=10+30+9
           

3.構造函數

/*
A constructor function which may create objects by specified
pattern:they have after creation own 'y' property
*/
function Foo(y){
    this.y=y;
}
/*
also 'Foo.prototype' stores reference to the prototype of 
newly created objects,so we may use it to define shared/
inherited properties or methods,so the same as in previous
example we have:
*/
//inherited property
Foo.prototype.x=;
//inherited method
Foo.prototype.calculate=function(z){
    return this.x+this.y+z;
};
//now creata our 'b' and 'c' objects using pattern Foo
var b=new Foo();
var c=new Foo();
//call the inherited method
b.calculate();//60=10+20+30
c.calculate();//80=10+30+40

b.__proto__===Foo.prototype;//true
c.__proto__===Foo.prototype;//true
b.constructor===Foo;//true
c.constructor===Foo;//true
b.x;//10
c.x;//10
Foo.prototype.constructor===Foo;//true
b.calculate===b.__proto__.calculate;//true
b.__proto__.calculate===Foo.prototype.calculate;//true
Foo.__proto__===Function.prototype;//true
Foo.prototype===Foo.prototype;//true
Foo.prototype.__proto__===Object.prototype;//true
Function.prototype.__proto__===Object.prototype;//true
Object.prototype.__proto__===null;//true
           

①以指定模式建立對象/自動為建立的對象設定一個原型對象。這個原型對象存儲在ConstructorFunction.prototype屬性中。

②每一個對象都有一個原型。

a)構造函數Foo也有自己的proto,值為Function.prototype

b)Function.prototype也有自己的proto,值為Object.prototype

c)Object.prototype也有自己的proto,值為null

③ES中憑借構造函數和原型對象的組合,媲美“類”。

4.執行上下文棧

①三種類型的ES代碼:全局代碼,函數代碼,eval代碼。

②棧中第一個元素(最下面的)永遠全局上下文(Global execution context)

5.執行上下文

①一個execution context可以抽象看成一個對象。

②該“對象”包含三個必需屬性:variable object(變量對象),this,scope chain

var foo=;
function fd(){}//function declaration,FD
(function fe(){});//function expressions,FE
this.foo===foo;//true
window.fd===fd;//true
window.fe===fe;//fe is not defined
           

6.變量對象(activation object)

①變量對象是執行上下文相關的資料作用域,它是一個與上下文相關的特殊對象,其中儲存了在上下文中定義的變量和函數聲明(函數表達式不在該對象中)。

Object.prototype.x=;
var w=;
var y=;
x;//10
//(函數)();-----函數表達式
(function foo(){
    var w=;
    var x=;
    with({z:}){
        console.log(w,x,y,z);//40 10 30 50
    }
    console.log(x,w);//100 40
    console.log(window.w);//20
    console.log(this.w);//20
})();
           

②隻有函數可以建立一個新的作用域。在函數作用域中所定義的變量和内部函數在函數外部是無法直接通路到的,而且并不會污染全局變量對象。

③在函數的上下文中,變量對象是以活動對象(activation object)來表示的。

7.活動對象

①當一個函數被調用時,會建立一個特殊的對象,叫做活動對象。

②這個對象中包含形參和一個特殊的arguments對象(是形參的一個映射,但值是通過索引擷取的)。

③活動對象之後會成為函數上下文中的變量對象來使用。

④ES5中的變量對象和活動對象被并入詞法環境模型(lexical environment model)。

8.作用域鍊

①作用域鍊是一個對象清單,上下文代碼中出現的辨別符在這個清單中尋找。

②查找規則:如果一個變量在該函數自身作用域(變量/活動對象)中沒有找到,那麼會繼續在其父函數(外層函數)查找,以此類推。

③如果在一個函數中引用了一個不是局部變量(或局部函數或形參)的辨別符,該辨別符叫做自由變量。搜尋這些自由變量時需要用到作用域鍊。

④當原型鍊與作用域鍊相結合時,先proto,後parent。

⑤全局變量内部的proto屬性指向Object.prototype.【不是在所有實作中全局對象都繼承自Object.prototype】

9.閉包

①當函數作為參數傳遞給其他函數時,這個參數叫做函數類型參數,funargs—functional arguments,接收函數類型參數的函數叫做高階函數。

②同樣,函數也可以從其他函數中傳回,傳回其他函數的函數叫做以函數為值的函數(functions with functional value)。【函數類型值】

③scope chain(作用域鍊)=Activation object+[[scope]]

④确切得來說是:在建立時刻,函數b會儲存父函數a的作用域鍊。

function foo(){
    var x=;
    return function bar(){
        console.log(x);
    };
}
var returnedFunction=foo();
var x=;
returnedFunction();//10


//自上而下
var x=;
function foo(){
    console.log(x);
}
(function(funArg){
    var x=;
    funArg();
})(foo);//10
           

⑥閉包的精确定義:閉包是一個代碼塊,在ES中就是一個函數,以靜态方式存儲着包括父作用域鍊中的活動對象(變量對象)。通過這些存儲的作用域,便于查找自由變量。

⑦多個函數可能共享父作用域,比如在函數a中,嵌套了多個函數。導緻一個閉包對變量的修改會展現在另一個閉包對變量的讀取上。

unction baz(){
    var x=;
    return{
        foo:function foo(){return ++x;},
        bar:function bar(){return --x;}
    };
}
var closures=baz();
closures.foo();//2
closures.foo();//3
closures.foo();//4
closures.bar();//3
closures.bar();//2
           

var data=[];
for(var k=;k<;k++){
    data[k]=function(){
        console.log(k);
    };
}
data[0]();//3
data[1]();//3
data[2]();//3
           

個人的一種解釋:基于閉包共享+變量提升—-因為JS中沒有塊級作用域這個概念,隻有全局作用域和局部作用域,再加個eval,而局部作用域是由定義函數時建立的,該代碼中for循環的變量k為全局變量,其内部的多個函數(數組函數)形成閉包,是以全局變量(Global Varibale object)中的k=3,而每個數組函數中指向的k都是全局變量中的那個k=3,是以所有輸出均為3.

一種解決辦法:通過函數表達式為每個循環建立一個新作用域,并在新作用域中用一個新變量儲存每次循環裡k的值,這些新作用域被獨立儲存在每個數組元素的函數的閉包裡,互不影響。

var data=[];
for(var k=;k<;k++){
    data[k]=(function help(x){
        return function(){
            console.log(x);
        }
    })(k);
}
data[0]();//0
data[1]();//1
data[2]();//2


var data=[];
for(var j=;j<;j++){
    data[j]=(function help(j){
        return function(){
            console.log(j);
        };
    })(j);
}
data[0]();//0
data[1]();//1
data[2]();//2
           

⑨為什麼var 換成let就變成0 1 2 了—目前let聲明的變量僅僅在本輪循環中有效。

使用let來聲明變量,類似于var,但是所聲明的變量,僅僅在let指令所執行的代碼塊中有效。

var data=[];
for(let k=;k<;k++){
    data[k]=function(){
        console.log(k);
    };
}
data[0]();//0
data[1]();//1
data[2]();//2
           
for(let i=;i<;i++){
    let i='yyc';
    console.log(i);
}
//yyc
//yyc
//yyc
           

10.

/*
foo() returns also a function and this returned
function uses free variable 'x'
*/
function foo(){
    var x=;
    return function bar(){
        console.log(x);
    };
}
foo();//傳回的是foo函數中return的那個bar函數。
bar();
/*
Uncaught ReferenceError: bar is not defined。同時這個時候你在外部
是無法通路到foo函數内部的bar函數的。
*/
var result=foo();
/*
foo()表示它的foo()函數傳回值(函數類型值),放在result這個變量中儲存。
*/
result();//10
/*
result()表示,執行了bar函數,在該函數内部的變量x是自由變量。
*/
           

11.了解一種新的寫法,省事—(funtion(){})();匿名的,可以自動執行的一種函數的表現方式。分号前的圓括号裡代表匿名函數的參數。

繼續閱讀