this的重要性
this是JavaScript中最为关键的语法点,一般在简单项目里,this可有可无,但大部分的开发任务中,this起着关键作用。
this的由来
以下面JS代码为例,调用该自身对象的其它属性的值。
var obj = {
name: 'cheche',
say: function(){
console.log('你好,'+obj.name)
}
}
obj.say() // 'cheche'
假如我们要在控制台打印出你好,cheche
,通过上面的JS代码虽然用变量获取可以实现,但通过写obj.name
,这样严重依赖了变量名,如果变量名obj
变了,那么里面用到变量名的地方都得改变,上面代码的写法显然不可取。
为了不依赖变量名,可以给obj.say()传入一个参数,
var obj = {
name: 'cheche',
say: function(person,问候语){
console.log(问候语+','+person.name)
}
}
obj.say(obj,'早') // '早,cheche'
这样的写法算是比较友好的,没有依赖变量名。但是得传入两个参数,那能不能只传一个参数?
如果只传一个参数便是这种形式obj.say('早')
,但obj.say(obj,'早')
和obj.say('早')
这二者很难区分,所以JS之父布兰登·艾克把第一个参数隐藏起来,但隐藏起来之后,怎么知道person.name
是什么,于是就引入了this作为它的第一个参数。那么上面的代码就变成了这样,
var obj = {
name: 'cheche',
say: function(问候语){
console.log(问候语+','+this.name)
}
}
obj.say('早') // '早,cheche'
其中obj,say('早')
形式上等价于obj.say.call(obj,'早')
,前者是一种语法糖的形式,后面这种才是计算机真正认为的,浏览器认为obj === this
,所以我们可以认为this就是函数call
的第一个参数。
this的概念
this的数据类型
var obj = {
foo: function(){
console.log(this)
}
}
var bar = obj.foo
obj.foo() //obj
bar() //undefined => Window
上面代码里,调用obj.foo()打印出来的是obj
这个对象自身,因为obj.foo()
形式上等于obj.foo.call(obj)
,而call的第一个参数就是this,所以我们知道了this就是一个对象,即使你指定的this是数字1,它也会变为一种对象的形式。
在调用bar()时,并没有指定this,形式上相当于
bar.call(undefined)
,这时浏览器会默认this等于window对象。但在nodejs里,this === global
,global是个全局对象。不要以为
bar = obj.foo
,就认为bar() === obj.foo()
,函数是独立存在的,不会进行补全。
this这个参数
当函数未调用时,this只是一个参数,无法确认this是什么。
var fn = function(p1){
console.log(this)
console.log(p1)
}
这是你无法确认this和p1是什么,因为他们只是参数。当你调用之后,便可以指定参数了。
this的确定
既然我们知道,函数传的第一参数就是this,那我们该如何确定它?
- 通过console.log(this)
这是最简单的方法,但是缺乏说服力。 - 读DOM或jQuery的源代码
水平不够,读不懂 - 查对应API的文档
html:<button id="xxx">点我</button> js:xxx.addEventListener('click',function(){console.log(this)}) //点击button
通过查EventTarger.addEventListener
文档,可以知道this值是触发事件元素的引用。所以,this === xxx
,即等于button
元素。
改变this的值
var obj = {
fn : function(){
console.log(this)}
}
setTimeout(obj.fn,1000) //window对象
当向setTimeout()传递一个函数时,该函数的this会指向一个错误的值,在非严格模式下,this是window对象,而在严格模式下,this是undefined。那我们该如何让this为obj这个对象呢?有三种方法。
用包装函数
setTimeout(function(){obj.fn()},1000)
,这样强制指定this为obj-
bind
setTimeout(obj.fn.bind(obj),1000)
obj.fn.bind(obj)会返回一个新的函数,在形式上等于
function(){obj.fn()}
可以理解为在
obj.fn
外套一层,重新写个call覆盖原来的this。
-
this的高级用法
var a = [1,2,3]
var b = a.join()
var c = [4,5,6]
var d = a.join.call(c)
console.log(b) // '1,2,3'
console.log(d) // '4,5,6'
array.join()是个数组的API,是将数组所有元素连接到字符串中,默认以逗号隔开。那么
a.join()
,里面的join
是怎么获取到数组a的,它的原理是什么?我们可以做出以下猜想。
var a = [1,2,3]
var a.join = function(){
var result = ''
for(var i=0;i<this.length;i++){
if(i !== this.lenght -1){ //最后一个不用加逗号
result += this[i] + ','
}else{
result +=this[i] }
}
我们做出猜想,join函数里有用了this,而这个this等于数组a。如果把this的传入改为数组c,那么join函数会把数组c变成字符串d。也就是说数组c调用了数组a的方法。
如果c不是一个数组,而是一个伪数组,同样可以用a的方法。a的slice方法操作了this,把c当作this给a的slice操作就行了。slice()是一个截取数组的方法
var a = [1,2,3]
var c = {0:4,1:5,2:6,length:3}
var e = a.slice.call(c,0,2)
console.log(e) // [4,5]
- 打出想要的this
html:
<button id=xxx name="curry"
javascript:
var obj = {
name: 'allen',
say: function(){
xxx.onclick = function(){console.log(this.name)}
}
}
obj.say() //xxx.name
此时this是xxx
这个按钮,我们想打出’cheche’,怎么办?
-
古老方法
将函数外的this赋值给一个变量,然后把函数里的this改成那个变量就行了。var obj = { name: 'cheche', var _this = this say: function(){ xxx.onclick = function(){console.log(_this.name)} } } obj.say() // 'cheche'
-
bind
var obj = { name: 'cheche', say: function(){ xxx.onclick = function(){console.log(_this.name)} }.bind(this) } obj.say() // 'cheche'
此时,bind传入的this是函数外的this,可以理解为bind()是最里面的this传入,当然以它传入为准。
- 箭头函数
var obj = {name: 'cheche',say: function(){xxx.onclick = ( ) => {console.log(this.name)}}obj.say() // 'cheche'
箭头函数没有隐藏的this,它自身并不会传入this,所以箭头函数里的this不是被点击的元素,而是它外面的this。
箭头函数无法用call()指定this
this的特殊用法
在new关键字调用构造函数时,构造函数里的this表示的是一个实例对象。
下边this因为
function Arr(id){
this.id = id
}
var s = new Arr(1)
此时这个this就是对象`s’。
下边代码this指向的是被点击button元素
var button = document.querySelector('button');
var name = 'jack';
var object = {
name: 'lucy',
sayHi: function() {
var that = this;
button.onclick = function() {
console.log(that.name);
}
},
}
object.sayHi(); //btn
setTimeout和setInterval this处理
setTimeout中函数内的this是指向了window对象,这是由于setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致这些代码中包含的 this 关键字会指向 window (或全局)对象.
方法一:保存this变量
var num = 0;
function Obj (){
var that = this; //将this存为一个变量,此时的this指向obj
this.num = 1,
this.getNum = function(){
console.log(this.num);
},
this.getNumLater = function(){
setTimeout(function(){
console.log(that.num); //利用闭包访问that,that是一个指向obj的指针
}, 1000)
}
}
var obj = new Obj;
obj.getNum();//1 打印的是obj.num,值为1
obj.getNumLater()//1 打印的是obj.num,值为1
方法二bind
var num = 0;
function Obj (){
this.num = 1,
this.getNum = function(){
console.log(this.num);
},
this.getNumLater = function(){
setTimeout(function(){
console.log(this.num);
}.bind(this), 1000) //利用bind()将this绑定到这个函数上
}
}
var obj = new Obj;
obj.getNum();//1 打印的为obj.num,值为1
obj.getNumLater()//1 打印的为obj.num,值为1
方法三箭头函数
function Obj (){
this.num = 1,
this.getNum = function(){
console.log(this.num);
},
this.getNumLater = function(){
setTimeout(() => {
console.log(this.num);
}, 1000) //箭头函数中的this总是指向外层调用者,也就是Obj
}
}
var obj = new Obj;
obj.getNum();//1 打印的是obj.num,值为1
obj.getNumLater()//1 打印的是obj.num,值为1