JS-this关键字
含义
在JavaScript中this
关键字非常重要,能影响到绝大多数的开发任务,它的作用是引用当前执行的上下文的对象,在实际的应用中,this
关键字的值即this返回的对象并不是固定的,其取决于函数的调用方式。
例1
console.log(this);//输出:Window
在上面代码中,this
是全局上下文中的,将在全局范围内调用,this
指向全局对象,在浏览器中返回window
例2
var person = {
name: '张三',
describe: function () {
return 'Hello'+ this.name;
}
};
person.describe()// "Hello 张三"
在上面代码中this.name
调用的是name
属性所对应的那个对象。由于this.name
是在describe
方法中调用,而describe
方法所在的当前对象是person
,因此this
指向person
,this.name
就是person.name
。
例3
var A = {
name: '张三',
describe: function () {
return 'Hello'+ this.name;
}
};
var B = {
name: '李四'
};
B.describe = A.describe;//将A.describe赋值给B.describe
B.describe()// "Hello 李四"
同例2所说,这里this.name
所在的方法任然是describe
,describe
方法所在的当前对象为A
,但是后面将A.desribe
赋值给了B.describe
,所以describe
方法当前所在的对象就变成了B
,this.name
调用的对象自然也就变成了B.name
例4
var A = {
name: '张三',
describe: function () {
return '姓名:'+ this.name;
}
};
var name = '李四';
var f = A.describe;
f() // "姓名:李四"
上面代码中,A.describe
被赋值给变量f
,内部的this
就会指向f
运行时所在的对象(本例是顶层对象)。
this关键字存在的原因
var obj = (foo : 5)
在JavaScript的引擎中对象(foo : 5)
的保存是现在内存里面生成一个对象(foo : 5)
,然后把这个对象的内存地址赋值给OBJ
,也就是说OBJ
是一个地址。当需要读取这个对象是,引擎就会先拿到地址OBJ
,然后在地址中·读出原始的对象并返回他的属性。
但是这里存在一个问题,原始的对象是以字典的形式保存的,属性值可能会是一个函数,类似于foo : function()
这种时候JavaScript引擎就会先将函数function()
单独保存在内存中,然后再将函数的地址赋值给foo
属性的value
属性,这就导致了function()
函数不仅能在obj
中被foo
对象调用,还能单独调用。鉴于如此,如何确定函数调用的具体位置就成了一个难题,于是this
就应运而生了,它的出现就是为了在函数体内部,指代函数当前的调用环境。
var f = function () {
console.log(this.x);
}
var x = 1;
var obj = {
f: f,
x: 2,
};
// 单独执行
f() // 1
// obj 环境执行
obj.f() // 2
上面代码中,函数f
在全局环境执行,this.x
指向全局环境的x
;在obj
环境执行,this.x
指向obj.x
。
this的应用举例
1.全局上下文中的this
this === window // true
function f() {
console.log(this === window);
}
f() // true
在上述代码中this
是在全局环境下运行的,此时不论this
是否在函数中,都将指向顶层对象window
2.构造函数中的this
var Obj = function (p) {
this.p = p;
};
var o = new Obj('Hello World!');
o.p // "Hello World!"
构造函数中的this
,指的是实例对象。上面代码定义了一个构造函数Obj
。由于this
指向实例对象,所以在构造函数内部定义this.p
,就相当于定义实例对象有一个p
属性。
注意事项
避免多层this
var o = {
f1: function () {
console.log(this);
var f2 = function () {
console.log(this);
}();
}
}
o.f1()
// Object
// Window
上述代码中出现了两层this
,第一层this
指向对象O
,输出object
,而第二层this
指向对象却是全局对象,也就是说会输出window
。为了避免这种情况的出现,可以将第二层的this
通过改写成一个对外的this
var o = {
f1: function() {
console.log(this);
var that = this;
var f2 = function() {
console.log(that);
}();
}
}
o.f1()
// Object
// Object
如上述代码就是定义了一个变量that
,用来固定对外的this
,然后在内部使用that
替换this
,就能够解决多层this
的问题
避免数组处理方法中的 this
数组的map
和foreach
方法,允许提供一个函数作为参数。这个函数内部不应该使用this
。
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + ' ' + item);
});
}
}
o.f()
// undefined a1
// undefined a2
上面代码中,foreach
方法的回调函数中的this
,其实是指向window
对象,因此取不到o.v
的值。原因跟上一段的多层this
是一样的,就是内层的this
不指向外部,而指向顶层对象。
解决这个问题的一种方法,就是前面提到的,使用中间变量固定this
。
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
var that = this;
this.p.forEach(function (item) {
console.log(that.v+' '+item);
});
}
}
o.f()
// hello a1
// hello a2
另一种方法是将this
当作foreach
方法的第二个参数,固定它的运行环境。
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + ' ' + item);
}, this);
}
}
o.f()
// hello a1
// hello a2
避免回调函数中的 this
回调函数中的this
往往会改变指向,最好避免使用。
var o = new Object();
o.f = function () {
console.log(this === o);
}
// jQuery 的写法
$('#button').on('click', o.f);
上面代码中,点击按钮以后,控制台会显示false
。原因是此时this
不再指向o
对象,而是指向按钮的 DOM 对象,因为f
方法是在按钮对象的环境中被调用的。这种细微的差别,很容易在编程中忽视,导致难以察觉的错误。
为了解决这个问题,可以采用下面的一些方法对this
进行绑定,也就是使得this
固定指向某个对象,减少不确定性。
JS-宏任务和微任务
JS是一门单线程语言,只有一条通道,但是当任务多的情况下,JS会模仿多线程语言形成一种伪“多线程”,所以就产生了同步任务与异步任务,宏任务与微任务都属一异步任务
关于宏任务与微任务的注意事项
(1)js是单线程的,但是分同步异步
(2)微任务和宏任务皆为异步任务,它们都属于一个队列
(3)宏任务一般是:script、setTimeout、setInterval、postMessage
(4)微任务:Promise.then ES6
(5)先执行同步再执行异步,异步遇到微任务,先执行微任务,执行完后如果没有微任务,就执行下一个宏任务,如果有微任务,就按顺序一个一个执行微任务
案例
setTimeout(()=>{
new Promise(resolve =>{
resolve();
}).then(()=>{
console.log('test');
});
console.log(4);
});
new Promise(resolve => {
resolve();
console.log(1)
}).then( () => {
console.log(3);
Promise.resolve().then(() => {
console.log('before timeout');
}).then(() => {
Promise.resolve().then(() => {
console.log('also before timeout')
})
})
})
console.log(2);
//1,2,3,before timeout,also before timeout,4,test;
执行过程分析
遇到setTimeout,异步宏任务,将() => {console.log(4)}放入宏任务队列中;
遇到new Promise,new Promise在实例化的过程中所执行的代码都是同步进行的,所以输出1;
而Promise.then,异步微任务,将其放入微任务队列中
遇到同步任务console.log(2),输出2;主线程中同步任务执行完
从微任务队列中取出任务到主线程中,输出3,此微任务中又有微任务,Promise.resolve().then(微任务a).then(微任务b),将其依次放入微任务队列中;
从微任务队列中取出任务a到主线程中,输出 before timeout;
从微任务队列中取出任务b到主线程中,任务b又注册了一个微任务c,放入微任务队列中;
从微任务队列中取出任务c到主线程中,输出 also before timeout;微任务队列为空
从宏任务队列中取出任务到主线程,此任务中注册了一个微任务d,将其放入微任务队列中,接下来遇到输出4,宏任务队列为空
从微任务队列中取出任务d到主线程 ,输出test,微任务队列为空