js中的this
2018-12-05 本文已影响0人
little_short
前言
近期对this很感兴趣,于是乎简单整理了一些关于this的技术点,加深一下对this的理解。
非箭头函数
在非箭头函数下,this指向调用其所在函数的对象,而且是离谁近就是指向谁(此对于常规对象,原型链,getter & setter等都适用;构造函数下,this与被创建的新对象绑定;DOM事件,this指向触发事件的元素;内联事件分两种情况,bind绑定,call & apply 方法等,容以下一步一步讨论。
解析
- 全局环境下this 始终指向全局对象(window), 无论是否严格模式。
- 函数直接调用的情况下。
- 普通函数内部的this分两种情况,严格模式和非严格模式。
- 非严格模式下,this默认指向全局对象window。
function f1(){
return this;
}
f1() === window; // true
- 其它函数的情况
- 情况1:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。
- 情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。
- 情况3:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象。
- 而严格模式下, this为undefined
function f2(){
"use strict"; // 这里是严格模式
return this;
}
f2() === undefined; // true
- 对象中的this
- 函数的定义位置不影响其this指向,this指向只和调用函数的对象有关。
- 多层嵌套的对象,内部方法的this指向离被调用函数最近的对象(window也是对象,其内部对象调用方法的this指向内部对象,而非window)。
//1
const o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); //37
const a = o.f;
console.log(a()): //undefined
const o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // logs 37
//2
o.b = {
g: independent,
prop: 42
};
console.log(o.b.g()); // logs 42
- 原型链中this
- 原型链中的方法的this仍然指向调用它的对象
var o = {
f : function(){
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
- 可以看出,在p中没有属性f,当执行p.f()时,会查找p的原型链,找到f函数并执行,但这与函数内部this指向对象p没有任何关系,只需记住谁调用指向谁。以上对于函数作为getter & setter调用时同样适用。
const o = {
f : function(){
return this.a + this.b;
}
};
const p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
- 构造函数中this
- 构造函数中的this与被创建的新对象绑定。
- 注意:当构造器返回的默认值是一个this引用的对象时,可以手动设置返回其他的对象,如果返回值不是一个对象,返回this。
function C(){
this.a = 37;
}
const o = new C();
console.log(o.a); // logs 37
function C2(){
this.a = 37;
return {a:38};
}
const b = new C2();
console.log(b.a); // logs 38
- 以上两个例子内部的this都指向对象o, 看到这里的你不妨在控制台执行下以上代码,看看对象o和b ,这些是属于构造函数的内容了,此处不多做介绍。(C2函数中的this.a = 37对整个过程完全没有影响的,可以被忽略的)。
- call & apply
- 当函数通过Function对象的原型中继承的方法 call() 和 apply() 方法调用时, 其函数内部的this值可绑定到 call() & apply() 方法指定的第一个对象上, 如果第一个参数不是对象,JavaScript内部会尝试将其转换成对象然后指向它。
function add(c, d){
return this.a + this.b + c + d;
}
const o = {a:1, b:3};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
function tt() {
console.log(this);
}
tt.call(5); // Number {[[PrimitiveValue]]: 5}
tt.call('asd'); // String {0: "a", 1: "s", 2: "d", length: 3, [[PrimitiveValue]]: "asd"}
- bind 方法
- bind方法在ES5引入, 在Function的原型链上, Function.prototype.bind。通过bind方法绑定后, 函数将被永远绑定在其第一个参数对象上, 而无论其在什么情况下被调用。
function f(){
return this.a;
}
const g = f.bind({a:"azerty"});
console.log(g()); // azerty
const o = {a:37, f:f, g:g};
console.log(o.f(), o.g()); // 37, azerty
- DOM 事件处理函数中的 this & 内联事件中的 this
- 1、当函数被当做监听事件处理函数时,其this指向触发该事件的元素(针对于addEventListener事件)。
// 被调用时,将关联的元素变成蓝色
function bluify(e){
//在控制台打印出所点击元素
console.log(this);
//阻止时间冒泡
e.stopPropagation();
//阻止元素的默认事件
e.preventDefault();
this.style.backgroundColor = '#A5D9F3';
}
// 获取文档中的所有元素的列表
const elements = document.getElementsByTagName('*');
// 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色
for(let i=0 ; i<elements.length ; i++){
elements[i].addEventListener('click', bluify, false);
}
- 内联事件
- 内联事件中的this指向分两种情况:
- 1、当代码被内联处理函数调用时,它的this指向监听器所在的DOM元素。
- 2、当代码被包括在函数内部执行时,其this指向等同于函数直接调用的情况,即在非严格模式指向全局对象window, 在严格模式指向undefined
<button onclick="console.log(this);">show me</button> //<button onclick="console.log(this);">show me</button>
<button onclick="(function(){console.log(this)})()"></button> //window
<button onclick="(function(){'use strict';console.log(this)}())">use strict</button> //undefined
- setTimeout & setInterval
- 对于延时函数内部的回调函数的this指向全局对象window(当然我们可以通过bind方法改变其内部函数的this指向)
看下边代码.
//默认情况下代码
function Person() {
this.age = 0;
setTimeout(function() {
console.log(this);
}, 3000);
}
const p = new Person();//3秒后返回 window 对象
==============================================
//通过bind绑定
function Person() {
this.age = 0;
setTimeout((function() {
console.log(this);
}).bind(this), 3000);
}
const p = new Person();//3秒后返回构造函数新生成的对象 Person{...}
规则
- 默认绑定全局变量
function fn() {
console.log( this.a );
}
const a = 2;
fn(); // 2 -- fn单独调用,this引用window
- 隐式绑定
- 1、隐式调用的意思是,函数调用时拥有一个上下文对象,就好像这个函数是属于该对象的一样。
function fn() {
console.log( this.a );
}
var obj = {
a: 2,
fn: fn
};
obj.fn(); // 2 -- this引用obj。
- 2、需要说明的一点是,最后一个调用该函数的对象是传到函数的上下文对象(绕懵了)。
function fn() {
console.log( this.a );
}
var obj2 = {
a: 42,
fn: fn
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.fn(); // 42 -- this引用的是obj2.
- 3、还有一点要说明的是,失去隐式绑定的情况。
function fn() {
console.log( this.a );
}
var obj = {
a: 2,
fn: fn
};
var bar = obj.fn; // 函数引用传递
var a = "全局"; // 定义全局变量
bar(); // "全局"
- 显示绑定
- 1、学过bind()\apply()\call()函数的都应该知道,它接收的第一个参数即是上下文对象并将其赋给this。
function fn() {
console.log( this.a );
}
var obj = {
a: 2
};
fn.call( obj ); // 2
- 2、如果我们传递第一个值为简单值,那么后台会自动转换为对应的封装对象。如果传递为null,那么结果就是在绑定默认全局变量
function fn() {
console.log( this.a );
}
var obj = {
a: 2
};
var a = 10;
fn.call( null); // 10
- new新对象绑定
- 1、如果是一个构造函数,那么用new来调用,那么绑定的将是新创建的对象。
function fn(a) {
this.a = a;
}
var bar = new fn( 2 );
console.log( bar.a );// 2
用法
- 在一般函数方法中使用 this 指代全局对象
function test(){
this.x = 1;
alert(this.x);
}
test(); // 1
- 作为对象方法调用,this 指代上级对象
function test(){
alert(this.x);
}
var o = {};
o.x = 1;
o.m = test;
o.m(); // 1
- 作为构造函数调用,this 指代new 出的对象
function test(){
this.x = 1;
}
var o = new test();
alert(o.x); // 1
//运行结果为1。为了表明这时this不是全局对象,我对代码做一些改变:
var x = 2;
function test(){
this.x = 1;
}
var o = new test();
alert(x); //2
- apply 调用 ,apply方法作用是改变函数的调用对象,此方法的第一个参数为改变后调用这个函数的对象,this指代第一个参数
var x = 0;
function test(){
alert(this.x);
}
var o={};
o.x = 1;
o.m = test;
o.m.apply(); //0
//apply()的参数为空时,默认调用全局对象。因此,这时的运行结果为0,证明this指的是全局对象。如果把最后一行代码修改为
o.m.apply(o); //1
箭头函数中的this
- 1、箭头函数中的this是在定义函数的时候绑定,而不是在执行函数的时候绑定,所谓的定义时候绑定,就是this是继承自父执行上下文!
- 2、箭头函数的this绑定看的是this所在的函数定义在哪个对象下,绑定到哪个对象则this就指向哪个对象
- 3、如果有对象嵌套的情况,则this绑定到最近的一层对象上
- 4、不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 5、不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 6、不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
解析
- 由于箭头函数不绑定this, 它会捕获其所在(即定义的位置)上下文的this值, 作为自己的this值
- 1、call()/apply()/bind() 方法对于箭头函数来说只是传入参数,对它的this毫无影响。
function Person() {
this.age = 0;
setInterval(() => {
// 回调里面的 `this` 变量就指向了期望的那个对象了
this.age++;
}, 3000);
}
const p = new Person();
- 2、考虑到 this 是词法层面上的,严格模式中与 this 相关的规则都将被忽略。
const adder = {
base : 1,
add : function(a) {
var f = v => v + this.base;
return f(a);
},
addThruCall: function inFun(a) {
const f = v => v + this.base;
const b = {
base : 2
};
return f.call(b, a);
}
};
console.log(adder.add(1)); // 输出 2
console.log(adder.addThruCall(1)); // 仍然输出 2(而不是3,其内部的this并没有因为call() 而改变,其this值仍然为函数inFun的this值,指向对象adder
- 对于是否严格模式示例代码
const f = () => {'use strict'; return this};
const p = () => { return this};
console.log(1,f() === window);
console.log(2,f() === p());
//1 true
//2 true
- 以上的箭头函数都是在方法内部,总之都是以非方法的方式使用,如果将箭头函数当做一个方法使用会怎样呢?
const obj = {
i: 10,
b: () => console.log(this.i, this),
c: function() {
console.log( this.i, this)
}
}
obj.b(); // undefined window{...}
obj.c(); // 10 Object {...}