What's this?

What's this?

由于運行期綁定的特性,JavaScript 中的 this 含義非常多,它可以是全局對象、當前對象或者任意對象,這完全取決于函數的調用方式

隨著函數使用場合的不同,this的值會發生變化。但是有一個總的原則,那就是this指的是,調用函數的那個對象

作為函數調用

在函數被直接調用時this綁定到全局對象。在瀏覽器中,window 就是該全局對象

<script>
        var b =2
        console.log(this)//在全局作用域下,this代表window
        function fn1(){
         var b=1
          console.log(this);//window
          console.log(this.b) //2
         }
fn1();
</script>
<script>
function fn1(){
         var b=1
          console.log(this.b) //undefined
         }

fn1()
<script>
<script>
        var b =2
        function fn1(){
        b=1
          console.log(this);//window
          console.log(this.b) //1
         }
fn1();
</script>

在全局作用域下聲明的變量,

var a =1 //  相當于window.a = 1 也等于this.a = 1

內部函數

函數嵌套產生的內部函數的this不是其父函數,仍然是全局變量

function fn0(){
    function fn(){
        console.log(this); //window
    }
    fn();
}

fn0();

setTimeout、setInterval

這兩個方法執行的函數this也是全局對象

document.addEventListener('click', function(e){
    console.log(this);//document
    setTimeout(function(){
        console.log(this); //window
    }, 200);
}, false);

如果 想要setTimeout里面的this代表document

document.addEventListener('click', function(e){
    console.log(this);//document
    var _this = this
    setTimeout(function(){
        console.log(_this); //document
    }, 200);
}, false);

作為構造函數調用

所謂構造函數,就是通過這個函數生成一個新對象(object)。這時,this就指這個新對象

new 運算符接受一個函數 F 及其參數:new F(arguments...)。這一過程分為三步:

1.創建類的實例。這步是把一個空的對象的__proto__屬性設置為 F.prototype 。
2.初始化實例。函數 F 被傳入參數并調用,關鍵字 this 被設定為該實例。
3.返回實例。

function Person(name){
    this.name = name;
}
Person.prototype.printName = function(){
    console.log(this.name);
};

var p1 = new Person('Byron');
var p2 = new Person('Casper');

p1.printName();//Byron
p2.printName();//Casper

作為對象方法調用

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

var obj1 = {
    name: 'Byron',
    fn : function(){
        console.log(this); //obj1
    }
};

obj1.fn();//誰調用,就指向誰(obj1)
var obj3 = {
    name:'lucas',
    obj1: {
    name: 'Brown',
    fn : function(){
        console.log(this); //obj1
    }
  }
}

obj3.obj1.fn(); //誰調用,就指向誰(obj3.obj1)

小陷阱

var fn2 = obj1.fn;
fn2(); //window
//函數調用只有一種形式func.call(context, p1, p2)
// 由于沒有傳 context
// 所以 this 就是 undefined
// 最后瀏覽器給你一個默認的 this —— window 對象

改變this指向的三個方法: bind,call,apply

不采用這三個方法可能會遇到這樣的問題,采用這種方法調用this.user相當于window.usr,所以返回undefined。

var a = {
    user:"追夢子",
    fn:function(){
        console.log(this.user);
    }
}
var b = a.fn;
b(); //undefined

我們直接執行a.fn()是可以的

var a = {
    user:"追夢子",
    fn:function(){
        console.log(this.user);
    }
}
a.fn(); //追夢子

雖然這種方法可以達到我們的目的,但是有時候我們不得不將這個對象保存到另外的一個變量中,那么就可以通過以下方法。

使用call和apply設置this

call,apply,調用一個函數,傳入函數執行上下文(context級this)及參數

fn.call(context, param1, param2...)
fn.apply(context, paramArray)

通過call或者apply方法,第一個參數都是設置希望this所指向的那個對象,即把fn添加期望運行的context環境中。
不同之處在于call方法接收參數列表,而apply接收參數數組。

var a = {
    user:"追夢子",
    fn:function(){
        console.log(this.user);
    }
}
var b = a.fn
b.call(a); /*也可以是 b.apply(a);*/

注意:如果call和apply的第一個參數寫的是null,那么this指向的是window對象

使用bind設置this

bind方法和call、apply方法有些不同,但它們都可以用來改變this的指向。

Function.prototype.bind(context, param1, param2...)

任何函數都有bind這樣一個方法。bind,返回一個新的函數,函數內部的this為你傳入的第一個參數。
仿照之前call和apply的寫法,我們使用bind

var a = {
    user:"追夢子",
    fn:function(){
        console.log(this.user); //追夢子
    }
}
var b = a.fn;
b.bind(a);

發現代碼沒有被打印,對,這就是bind和call、apply方法的不同,實際上bind方法返回的是一個修改過后的函數。執行該函數看看。

var a = {
    user:"追夢子",
    fn:function(){
        console.log(this.user); //追夢子
    }
}
var b = a.fn;
b.bind(a)(); // "追夢子"

同樣bind也可以有多個參數,并且參數可以執行的時候再次添加

var a = {
    user:"追夢子",
    fn:function(e,d,f){
        console.log(this.user); 
        console.log(e,d,f); 
    }
}
var b = a.fn;
var c = b.bind(a,10,44);
c(33);

更多例子

bind的例子

var obj3={
        name: 'apple'
    }
var obj1={
    name: 'Brown',
    fn : function(){
        console.log(this);
    }
}

var fn3 = obj1.fn.bind(obj3)
fn3() 
//[object Object] {
//  name: "apple"
//}
//此時的this就是你傳入的第一個參數obj3
想要setTimeout里面的this代表document的另一種實現方式
#通過bind綁定的this是setTimeout函數外部的this,即document
document.addEventListener('click', function(e){
    console.log(this);//document
    setTimeout(function(){
        console.log(this); //document
    }.bind(this), 200);
}, false);

call和apply設置this的例子

var value =100
var obj4 = {
    value:200
}
function fn4(a,b){
    console.log(this.value+a+b)
}
fn4(3,4) //107
fn4.call(obj4,3,4) //207
fn4.apply(obj4,[3,4]) //207

應用

var arr = [1,2,7,4]
//Math.max(1,2,7,4)
console.log(Math.max.apply(null,arr)) //7
function joinStr(){
    //console.log(Array.prototype.join.call(arguments,'-'))
    var join = Array.prototype.join.bind(arguments)
    console.log(join('-'))
}
joinStr('a','b','c') //a-b-c

arguments

1.在函數調用時,會在函數內部自動生成一個名為 arguments的隱藏對象
2.為類數組對象,可以使用[]運算符獲取函數調用時傳遞的實參
3.只有函數被調用時,arguments對象才會創建,未調用時其值為null

unction fn5(name, age){
     console.log(arguments);// ["Byron", 20]
     name = 'XXX';
     console.log(arguments); //["XXX", 20]
     arguments[1] = 30;
     console.log(arguments); //["XXX", 30]
 }
 fn5('Byron', 20);

函數的三種變量

  • 實例變量:(this)類的實例才能訪問到的變量
  • 靜態變量:(屬性)直接類型對象能訪問到的變量
  • 私有變量:(局部變量)當前作用域內有效的變量
function ClassA(){
    var a = 1; //私有變量,只有函數內部可以訪問
    this.b = 2; //實例變量,只有實例可以訪問
}

ClassA.c = 3; // 靜態變量,也就是屬性,類型訪問

console.log(a); // error
console.log(ClassA.b) // undefined
console.log(ClassA.c) //3

var classa = new ClassA();
console.log(classa.a);//undefined
console.log(classa.b);// 2
console.log(classa.c);//undefined

原型與原型鏈

回顧一下類、實例、prototype、__proto__的關系


1.我們通過函數定義了類Person,類(函數)自動獲得屬性prototype
2.每個類的實例都會有一個內部屬性__proto__,指向類的prototype屬性

因為prototype本質上是個類Object的實例,所以prototype也和其它實例一樣也有個__proto__內部屬性,指向其類型Object的prototype

類型

instanceof操作符,判斷一個對象是不是某個類型的實例

function Person(nick, age){
    this.nick = nick;
    this.age = age;
}
Person.prototype.sayName = function(){
    console.log(this.nick);
}

var p1 = new Person();

判斷p1是不是Person類型的實例,

p1 instanceof Person //true
//其中經歷了一下過程
//先查找p1._proto_ === Person.prototype
//如果上面查找未成功,再查找p1._proto_._proto_ === Preson.prototype
原型鏈

繼承

繼承是指一個對象直接使用另一對象的屬性和方法。
JavaScript并不提供原生的繼承機制,但可以自己實現

只要實現了兩點的話就可以說我們實現了繼承
1.得到一個類的屬性
2.得到一個類的方法

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.sayName = function(){
    console.log('My name is'+this.name);
};
Person.prototype.walk = function(){
    console.log(this.name+'is walking')
}
function Student(name, age,sex){
}
Student.prototype.doing = function(){
    console.log("I am studying");
};

var s = new Student('hunger',2,'boy')
#問題:怎么讓Studen繼承Person的屬性name, age和方法sayName呢?

屬性獲取

對象屬性的獲取是通過構造函數的執行,我們在一個類中執行另外一個類的構造函數,就可以把屬性賦值到自己內部,但是我們需要把環境改到自己的作用域內,可以借助函數call或bind

function Student(name, age,sex){
    Person.call(this,name,age)
    //Person.bind(this,name,age)
    this.sex = sex
}

方法獲取

Object.create(proto)
創建一個新的對象,新對象的原型就是傳入的參數

類的方法都定義在了prototype里面,所以只要我們把子類的prototype改為父類的prototype的備份就好了

Student.prototype = Object.create(Person.prototype)

這里我們通過Object.createclone了一個新的prototype而不是直接把Person.prtotype直接賦值,因為引用關系,這樣會導致后續修改子類的prototype也修改了父類的prototype,因為引用關系修改的是同一個值,如果是直接賦值就等于是Student.prototype.__proto__ = Person.prototype

Object.create是ES5方法,之前版本通過遍歷屬性也可以實現淺拷貝

注意:對子類添加新的方法,必須在修改其prototype之后,否則會被覆蓋掉

最后需要重新指定一下constructor屬性到自己的類型

所以,最后寫為

function Person(name, age){
   this.name = name;
   this.age = age;
}

Person.prototype.sayName = function(){
   console.log('My name is'+this.name);
};
Person.prototype.walk = function(){
   console.log(this.name+'is walking')
}
var p = new Person('ruoyu',100)

function Student(name, age,sex){
   //把屬性拿過來
   Person.call(this,name,age)
   //Person.bind(this,name,age)
   this.sex = sex
}
//方法拿過來
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student //重新指定一下constructor屬性到自己的類型 ,不然Student.prototype.constructor = Person啦
Student.prototype.doing = function(){
   console.log("I am studying");
};

var s = new Student('hunger',2,'boy')
#繼承過后的a既是Student的對象 也是Person的對象
s instanceof Student//true
//s._proto_ ===Student.prototype
s instanceof Person //true
//s._proto_._proto_ === Person.prototype

另一種方法,把方法的繼承封裝成函數inherit

function inherit(superType, subType){
    var _prototype  = Object.create(superType.prototype);
    _prototype.constructor = subType;
    subType.prototype = _prototype;
}
#使用方法
function Person(name, sex){
    this.name = name;
    this.sex = sex;
}

Person.prototype.printName = function(){
    console.log(this.name);
};    

function Male(name, sex, age){
    Person.call(this, name, sex);
    this.age = age;
}
inherit(Person, Male);

// 在繼承函數之后寫自己的方法,否則會被覆蓋
Male.prototype.printAge = function(){
    console.log(this.age);
};

var m = new Male('Byron', 'm', 26);
m.printName();
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容