JS的this、__proto__ 和. prototype 、constructor

一、this關鍵字

作爲函數被調用

函數也可以直接被調用,此時 this 綁定到全局對象。在瀏覽器中,window 就是該全局對象。比如下面的例子:函數被調用時,this 被綁定到全局對象,接下來執行賦值語句,相當於隱式的聲明瞭一個全局變量,這顯然不是調用者希望的。

 function makeNoSense(x) { 
 this.x = x; 
 } 

 makeNoSense(5); 
 x;// x 已經成爲一個值爲 5 的全局變量

作爲對象方法調用

在 JavaScript 中,函數也是對象,因此函數可以作爲一個對象的屬性,此時該函數被稱爲該對象的方法,在使用這種調用方式時,this 被自然綁定到該對象。

 var point = { 
 x : 0, 
 y : 0, 
 moveTo : function(x, y) { 
     this.x = this.x + x; 
     this.y = this.y + y; 
     } 
 }; 

 point.moveTo(1, 1)//this 綁定到當前對象,即 point 對象

內部函數再定義內部函數

上述的moveTo()函數已經是point的內部函數了,如果,我還想在moveTo中再加入一個函數:

var point = { 
 x : 0, 
 y : 0, 
 moveTo : function(x, y) {
    console.log(this); //point對象
     // 內部函數
     var moveX = function(x) {
        console.log(this); //window對象
        this.x = x;//this 綁定到了哪裏?
    }; 
    // 內部函數
    var moveY = function(y) { 
        this.y = y;//this 綁定到了哪裏?
    }; 

    moveX(x); 
    moveY(y); 
    } 
 }; 
 point.moveTo(1, 1); 
 point.x; //==>0
 point.y; //==>0
 x; //==>1
 y; //==>1

這裏出現了一個錯誤,第一個this顯示出是point對象,但是第二個this卻顯示出是window對象。

這屬於 JavaScript 的設計缺陷,正確的設計方式是內部函數的 this 應該綁定到其外層函數對應的對象上,爲了規避這一設計缺陷,聰明的 JavaScript 程序員想出了變量替代的方法,約定俗成,該變量一般被命名爲 that。

 var point = { 
 x : 0, 
 y : 0, 
 moveTo : function(x, y) { 
      var that = this; 
     // 內部函數
     var moveX = function(x) { 
     that.x = x; 
     }; 
     // 內部函數
     var moveY = function(y) { 
     that.y = y; 
     } 
     moveX(x); 
     moveY(y); 
     } 
 }; 
 point.moveTo(1, 1); 
 point.x; //==>1 
 point.y; //==>1

當內部函數嵌套只有一層,那麼還是符合內部函數this綁定在外層函數對應的對象上,但是,如果超過了1層嵌套,那麼在高於1層的內部函數中,this對象如果不借助that,那麼this將會指向window對象。

apply或call調用

function Point(x, y){ 
    this.x = x; 
    this.y = y; 
    this.moveTo = function(x, y){ 
        this.x = x; 
        this.y = y; 
    } 
}

 var p1 = new Point(0, 0); 
 var p2 = {x: 0, y: 0}; 
 p1.moveTo(1, 1); 
 p1.moveTo.apply(p2, [10, 10]);

這裏p1是一個Point {x: 1, y: 1}對象,而p2則是一個Object {x: 10, y: 10}對象。

eval調用

JavaScript 中的 eval 方法可以將字符串轉換爲 JavaScript 代碼,使用 eval 方法時,this 指向哪裏呢?答案很簡單,看誰在調用 eval 方法,調用者的執行環境(ExecutionContext)中的 this 就被 eval 方法繼承下來了。

二、proto 和. prototype

p.proto === p.constructor.prototype === Person.prototype;

一、 所有構造器/函數的_proto_都指向Function.prototype,它是一個空函數(Empty function)

function A(){}
A.__proto__ === Function.prototype; //true
//內置對象
Number.__proto__ === Function.prototype  // true

特殊的,如Math對象的proto是Object.prototype

Math.__proto__ === Object.prototype  // true

知道了所有構造器(含內置及自定義)的proto都是Function.prototype,那Function.prototype的proto是誰呢?

Function.prototype.__proto__ === Object.prototype;//true

這說明所有的構造器也都是一個普通JS對象,可以給構造器添加/刪除屬性等。同時它也繼承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等。

最後Object.prototype的proto是誰?

Object.prototype.__proto__ === null  // true

二、所有對象的_proto_都指向其構造器的prototype

__proto__指向了,我不知道怎麼說,用java的話來說就是class類。

var obj = {name: 'jack'}
var arr = [1,2,3]
var reg = /hello/g
var date = new Date
var err = new Error('exception')

console.log(obj.__proto__ === Object.prototype) // true
console.log(arr.__proto__ === Array.prototype)  // true
console.log(reg.__proto__ === RegExp.prototype) // true
console.log(date.__proto__ === Date.prototype)  // true
console.log(err.__proto__ === Error.prototype)  // true

再看看自定義的構造器,這裏定義了一個Person

function Person(name) {
    this.name = name
}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true

p是Person的實例對象,p的內部原型總是指向其構造器Person的prototype。

每個對象都有一個constructor屬性,可以獲取它的構造器,因此以下打印結果也是恆等的

function Person(name) {
    this.name = name
}
var p = new Person('jack')
p.__proto__ === p.constructor.prototype

修改原型

function Person(name) {
    this.name = name
}
var p = new Person('jack')
Person.prototype = {
    getName: function() {}
}

又由於

p.__proto__ === p.constructor.prototype === Person.prototype;

所以,我覺得有三種寫法。

Person.prototype.getName = function(){};
var per = new Person('jack');
per.__proto__.getName = function(){};
per.constructor.prototype.getName = function(){};

三、constructor屬性

返回一個指向創建了該對象原型的函數引用。

var a,b;
(function(){
  function A (arg1,arg2) {
    this.a = 1;
    this.b=2; 
  }

  A.prototype.log = function () {
    console.log(this.a);
  }
  a = new A();
  b = new A();
})()
a.log();
// 1
b.log();
// 1

通過以上代碼我們可以得到兩個對象,a,b,他們同爲類A的實例。因爲A在閉包裏,所以現在我們是不能直接訪問A的,那如果我想給類A增加新方法怎麼辦?

// a.constructor.prototype 在chrome,firefox中可以通過 a.__proto__ 直接訪問
a.constructor.prototype.log2 = function () {
  console.log(this.b)
}

a.log2();
// 2
b.log2();
// 2

由於閉包的特性,我們只能藉助a.constructor.prototype來更改原型。

參考

https://www.ibm.com/developerworks/cn/web/1207_wangqf_jsthis/
http://www.cnblogs.com/snandy/archive/2012/09/01/2664134.html
http://stackoverflow.com/questions/9959727/proto-vs-prototype-in-javascript

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章