1. 程式人生 > >javascript作用域、執行上下文、原型和原型鏈

javascript作用域、執行上下文、原型和原型鏈

img eva reference https 結果 lse prot console 結束

一、作用域
js中作用域是指可訪問變量,對象,函數的集合,也就是調用它們能生效的代碼區塊。在js中沒有塊級作用域,只有全局作用域和函數作用域

1、全局,函數作用域

var a = 10
function f1(){
        var b = c = 20;
        console.log(a); ? ? //10
        console.log(c); ? ? //20
        function f2() {
             console.log(b); //20
        }f2();
}
f1();
console.log(a); ? ? //10
console.log(c); ? ? //20
console.log(b); ? ? //error

var b = c = 20 是指 var b = c; c = 20
在f1函數中c沒使用var聲明,所以c為全局變量,b為局部變量,綁定在f1函數下,外部訪問不到。

2、模仿塊級作用域
沒有塊級作用域,但是有if(),for()等塊語句,在塊語句內部定義的變量會保留在它們已經存在的作用域內,舉個栗子:

if(true) {
        var word = ‘hello‘;
        console.log(word);  //hello
}
console.log(word);      //hello

if()語句存在全局作用域下,所以內部定義的變量存在於全局作用域中,無論在哪都可以訪問。

function add(num) {
        if(num > 10) {
                var num = 10;
                console.log(num);   //10
        }
        console.log(num);       //10
}
add(11);
console.log(num);   //Uncaught ReferenceError: num is not defined

此時if()在add函數中,內部定義的變量存在於add函數的作用域中,只有在add函數和塊語句中才可以訪問到,外部無法訪問。

3、使用自執行的匿名函數包裹塊語句構建塊作用域,也叫私有作用域

function add(num) {
        for(var i = 0; i < num; i++) {
            console.log(i);     //0,1,2,3,4,5,6,7,8,9
        }
        console.log(i);     //10
    }
add(10);

將代碼改為

function add(num) {
        (function () {
            for(var i = 0; i < num; i++) {
                     console.log(i);    //0,1,2,3,4,5,6,7,8,9
                }
        })()
    console.log(i);     //Uncaught ReferenceError: i is not defined
}
add(10);

此時變量i只能在for()循環中訪問到,在add函數和外部都無法訪問,並且在匿名函數中定義的任何變量都會在執行結束時被銷毀,所以變量i只能在for()循環中使用。

二、執行上下文
javascript運行的代碼環境有三種:

  • 全局代碼:代碼默認運行的環境,最先會進入到全局環境中
  • 函數代碼:在函數的局部環境中運行的代碼
  • Eval代碼:在Eval()函數中運行的代碼
  • 全局上下文:是最外圍的一個執行環境,web瀏覽器中被認為是window對象。在初始化代碼時會,先進入全局上下文中
  • 執行上下文:每當一個函數被調用時就會為該函數創建一個執行上下文,每個函數都有自己的執行上下文
function f1() {
        var f1Context = ‘f1 context‘;
        function f2() {
                var f2Context = ‘f2 context‘;
                function f3() {
                        var f3Context = ‘f3 context‘;
                        console.log(f3Context);
                }
                f3();
                console.log(f2Context);
        }
        f2();
        console.log(f1Context);
}
f1();
//結果:
//f3 context
//f2 context
//f1 context

全局上下文:擁有f1()
f1()的執行上下文:有變量f1Context和f2()
f2()的執行上下文:有變量f2Context和f3()
f3()的執行上下文:有變量f3Context

ECS:執行環境棧,可以理解為代碼執行的土壤,即代碼執行的地方
js是單線程,任務都為同步任務的情況下某一時間只能執行一個任務

執行一段代碼首先會進入全局上下文中,並將其壓入ECS中棧頂
首先執行f1(),為其創建執行上下文,進入到棧頂位置,全局上下文被往下壓
f1()中有f2(),再為f2()創建f2()的執行上下文,f2()進入到棧頂位置,f1()被往下壓,
依次,最終全局上下文被壓入到棧底,f3()的執行上下文在棧頂
f3()執行完後,ECS就會彈出其執行上下文(內部變量隨之被銷毀),f3()上下文彈出後,f2()上下文來到棧頂,開始執行f2(),依次,最後ECS中只剩下全局上下文,它等到應用程序退出,例如瀏覽器關閉時銷毀。
技術分享圖片

function foo(i) {
        if(i  == 3) {
                return;
        }
        foo(i+1);
        console.log(i);
}
foo(0);

ECS棧頂為foo(3)的的上下文,直接return彈出後,棧頂變成foo(2)的上下文,執行foo(2),輸出2並彈出,執行foo(1),輸出1並彈出,執行foo(0),輸出0並彈出,關閉瀏覽器後全局EC彈出,所以結果為2,1,0。

三、原型和原型鏈
1、對象(普通對象、函數對象)

  • 所有引用類型(函數,數組,對象)都擁有proto屬性(隱式原型)
  • 所有函數擁有prototype屬性(顯式原型)(僅限函數)
  • 原型對象:擁有prototype屬性的對象,在定義函數時就被創建

2、構造函數
//創建構造函數

function Word(words){
        this.words = words;
}
Word.prototype = {
        alert(){
                alert(this.words);
        }
}
//創建實例
var w = new Word("hello world");
w.print = function(){
        console.log(this.words);
        console.log(this);  //Person對象
}
w.print();  //hello world
w.alert();  //hello world

print()方法是w實例本身具有的方法,所以w.print()打印hello world;alert()不屬於w實例的方法,屬於構造函數的方法,w.alert()也會打印hello world,因為實例繼承,構造函數原型上的方法。
實例w的隱式原型指向它構造函數的顯式原型,指向的意思是恒等於
w.proto === Word.prototype

當調用某種方法或查找某種屬性時,首先會在自身調用和查找,如果自身並沒有該屬性或方法,則會去它的proto屬性中調用查找,也就是它構造函數的prototype中調用查找。所以很好理解實例繼承構造函數的方法和屬性:
w本身沒有alert()方法,所以會去Word()的顯式原型Word.prototype中調用alert(),即實例繼承構造函數的方法。 ?

3、原型和原型鏈

Function.prototype.a = "a";
Object.prototype.b = "b";
function Person(){}
console.log(Person);    //function Person()
let p = new Person();
console.log(p);         //Person {} 對象
console.log(p.a);       //undefined
console.log(p.b);       //b

實例p上面並沒有a屬性,那麽會通過proto向上查找,根據:
p.proto == Person.prototype,然後Person.prototype上也沒有a屬性,Person.prototype仍然是一個對象,上面仍然具有proto屬性,根據:
Person.prototype.proto == Function.prototype //false

Person.prototype.proto == Object.prototype //true

Object.prototype.proto == null //再上一級就是null了

此時,Object.prototype.b = "b",所以p.a是undefined,而p.b是"b",
因為沒有定義Object.prototype.a,只定義了Function.prototype.a

總結:

1.查找屬性,如果本身沒有,則會去proto中查找,也就是構造函數的顯式原型中查找,如果構造函數中也沒有該屬性,因為構造函數也是對象,也有proto,那麽會去它的顯式原型中查找,一直到null,如果沒有則返回undefined
2.p.proto.constructor? == function Person(){}
3.p._proto.proto_== Object.prototype
4.p.
proto.proto.proto== Object.prototype.proto == null ????????
5.通過
proto__形成原型鏈而非protrotype

技術分享圖片

javascript作用域、執行上下文、原型和原型鏈