What's this?
由于運行期綁定的特性俊抵,JavaScript 中的 this 含義非常多,它可以是全局對象谜诫、當前對象或者任意對象牵祟,這完全取決于函數(shù)的調(diào)用方式
隨著函數(shù)使用場合的不同,this的值會發(fā)生變化弱卡。但是有一個總的原則乃正,那就是this指的是,調(diào)用函數(shù)的那個對象
作為函數(shù)調(diào)用
在函數(shù)被直接調(diào)用時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
內(nèi)部函數(shù)
函數(shù)嵌套產(chǎn)生的內(nèi)部函數(shù)的this不是其父函數(shù)名党,仍然是全局變量
function fn0(){
function fn(){
console.log(this); //window
}
fn();
}
fn0();
setTimeout、setInterval
這兩個方法執(zhí)行的函數(shù)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);
作為構造函數(shù)調(diào)用
所謂構造函數(shù)挠轴,就是通過這個函數(shù)生成一個新對象(object)传睹。這時,this就指這個新對象
new 運算符接受一個函數(shù) F 及其參數(shù):new F(arguments...)岸晦。這一過程分為三步:
1.創(chuàng)建類的實例欧啤。這步是把一個空的對象的__proto__
屬性設置為 F.prototype 。
2.初始化實例启上。函數(shù) F 被傳入?yún)?shù)并調(diào)用邢隧,關鍵字 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
作為對象方法調(diào)用
在 JavaScript 中倒慧,函數(shù)也是對象,因此函數(shù)可以作為一個對象的屬性,此時該函數(shù)被稱為該對象的方法纫谅,在使用這種調(diào)用方式時炫贤,this 被自然綁定到該對象
var obj1 = {
name: 'Byron',
fn : function(){
console.log(this); //obj1
}
};
obj1.fn();//誰調(diào)用,就指向誰(obj1)
var obj3 = {
name:'lucas',
obj1: {
name: 'Brown',
fn : function(){
console.log(this); //obj1
}
}
}
obj3.obj1.fn(); //誰調(diào)用系宜,就指向誰(obj3.obj1)
小陷阱
var fn2 = obj1.fn;
fn2(); //window
//函數(shù)調(diào)用只有一種形式func.call(context, p1, p2)
// 由于沒有傳 context
// 所以 this 就是 undefined
// 最后瀏覽器給你一個默認的 this —— window 對象
改變this指向的三個方法: bind照激,call,apply
不采用這三個方法可能會遇到這樣的問題盹牧,采用這種方法調(diào)用this.user相當于window.usr俩垃,所以返回undefined。
var a = {
user:"追夢子",
fn:function(){
console.log(this.user);
}
}
var b = a.fn;
b(); //undefined
我們直接執(zhí)行a.fn()是可以的
var a = {
user:"追夢子",
fn:function(){
console.log(this.user);
}
}
a.fn(); //追夢子
雖然這種方法可以達到我們的目的汰寓,但是有時候我們不得不將這個對象保存到另外的一個變量中口柳,那么就可以通過以下方法。
使用call和apply設置this
call有滑,apply跃闹,調(diào)用一個函數(shù),傳入函數(shù)執(zhí)行上下文(context級this)及參數(shù)
fn.call(context, param1, param2...)
fn.apply(context, paramArray)
通過call或者apply方法毛好,第一個參數(shù)都是設置希望this所指向的那個對象望艺,即把fn添加期望運行的context環(huán)境中。
不同之處在于call方法接收參數(shù)列表肌访,而apply接收參數(shù)數(shù)組找默。
var a = {
user:"追夢子",
fn:function(){
console.log(this.user);
}
}
var b = a.fn
b.call(a); /*也可以是 b.apply(a);*/
注意:如果call和apply的第一個參數(shù)寫的是null,那么this指向的是window對象
使用bind設置this
bind方法和call吼驶、apply方法有些不同惩激,但它們都可以用來改變this的指向。
Function.prototype.bind(context, param1, param2...)
任何函數(shù)都有bind這樣一個方法蟹演。bind风钻,返回一個新的函數(shù),函數(shù)內(nèi)部的this為你傳入的第一個參數(shù)酒请。
仿照之前call和apply的寫法骡技,我們使用bind
var a = {
user:"追夢子",
fn:function(){
console.log(this.user); //追夢子
}
}
var b = a.fn;
b.bind(a);
發(fā)現(xiàn)代碼沒有被打印,對羞反,這就是bind和call哮兰、apply方法的不同,實際上bind方法返回的是一個修改過后的函數(shù)苟弛。執(zhí)行該函數(shù)看看。
var a = {
user:"追夢子",
fn:function(){
console.log(this.user); //追夢子
}
}
var b = a.fn;
b.bind(a)(); // "追夢子"
同樣bind也可以有多個參數(shù)阁将,并且參數(shù)可以執(zhí)行的時候再次添加
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就是你傳入的第一個參數(shù)obj3
想要setTimeout里面的this代表document的另一種實現(xiàn)方式
#通過bind綁定的this是setTimeout函數(shù)外部的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.在函數(shù)調(diào)用時,會在函數(shù)內(nèi)部自動生成一個名為 arguments的隱藏對象
2.為類數(shù)組對象窘哈,可以使用[]運算符獲取函數(shù)調(diào)用時傳遞的實參
3.只有函數(shù)被調(diào)用時亭敢,arguments對象才會創(chuàng)建,未調(diào)用時其值為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);
函數(shù)的三種變量
- 實例變量:(this)類的實例才能訪問到的變量
- 靜態(tài)變量:(屬性)直接類型對象能訪問到的變量
- 私有變量:(局部變量)當前作用域內(nèi)有效的變量
function ClassA(){
var a = 1; //私有變量帅刀,只有函數(shù)內(nèi)部可以訪問
this.b = 2; //實例變量,只有實例可以訪問
}
ClassA.c = 3; // 靜態(tài)變量骇窍,也就是屬性锥余,類型訪問
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.我們通過函數(shù)定義了類Person,類(函數(shù))自動獲得屬性prototype
2.每個類的實例都會有一個內(nèi)部屬性
__proto__
吁脱,指向類的prototype屬性
因為prototype本質上是個類Object的實例彬向,所以prototype也和其它實例一樣也有個__proto__
內(nèi)部屬性,指向其類型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
//其中經(jīng)歷了一下過程
//先查找p1._proto_ === Person.prototype
//如果上面查找未成功凿蒜,再查找p1._proto_._proto_ === Preson.prototype
繼承
繼承是指一個對象直接使用另一對象的屬性和方法胁黑。
JavaScript并不提供原生的繼承機制废封,但可以自己實現(xiàn)
只要實現(xiàn)了兩點的話就可以說我們實現(xiàn)了繼承
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呢丧蘸?
屬性獲取
對象屬性的獲取是通過構造函數(shù)的執(zhí)行,我們在一個類中執(zhí)行另外一個類的構造函數(shù)刽漂,就可以把屬性賦值到自己內(nèi)部演训,但是我們需要把環(huán)境改到自己的作用域內(nèi)贝咙,可以借助函數(shù)call或bind
function Student(name, age,sex){
Person.call(this,name,age)
//Person.bind(this,name,age)
this.sex = sex
}
方法獲取
Object.create(proto)
創(chuàng)建一個新的對象,新對象的原型就是傳入的參數(shù)
類的方法都定義在了prototype里面庭猩,所以只要我們把子類的prototype改為父類的prototype的備份就好了
Student.prototype = Object.create(Person.prototype)
這里我們通過Object.create
clone了一個新的prototype而不是直接把Person.prtotype直接賦值,因為引用關系眯娱,這樣會導致后續(xù)修改子類的prototype也修改了父類的prototype,因為引用關系修改的是同一個值试伙,如果是直接賦值就等于是Student.prototype.__proto__ = Person.prototype
Object.create是ES5方法于样,之前版本通過遍歷屬性也可以實現(xiàn)淺拷貝
注意:對子類添加新的方法,必須在修改其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
另一種方法贬芥,把方法的繼承封裝成函數(shù)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);
// 在繼承函數(shù)之后寫自己的方法,否則會被覆蓋
Male.prototype.printAge = function(){
console.log(this.age);
};
var m = new Male('Byron', 'm', 26);
m.printName();