JS-this关键字、宏任务与微任务总结

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指向personthis.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方法当前所在的对象就变成了Bthis.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

数组的mapforeach方法,允许提供一个函数作为参数。这个函数内部不应该使用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,微任务队列为空

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值