this用法总结
创始人
2024-04-09 14:53:07
0

文章目录

      • 1.常规下this的指向
        • 1.1 全局环境中的this
        • 1.2 上下文对象调用中的this
        • 1.3 this指向绑定事件的元素
        • 1.4 箭头函数的this指向
      • 2. 改变this指向
        • 2.1 call - Function.prototype.call( )
          • 2.1.1 call的第一个参数
          • 2.1.2 call接受多个参数
          • 2.1.3 调用对象的原生方法
        • 2.2 apply - Function.prototype.apply( )
          • 2.2.1 apply 与call 的区别
          • 2.2.2 利用apply传入数组的特性,实现一些小功能
        • 2.3 bind - Function.prototype.bind( )
          • 2.3.1 bind传入一个参数时
          • 2.3.1 bind传入多个参数时
          • 2.3.2 第一个参数为null或undefined时
          • 2.3.3 bind使用时的注意点(即:使用bind的几个场景)
            • 2.3.3.1 bind每一次返回一个函数
            • 2.3.3.2 将包含 *this* 的方法直接当作回调函数
            • *2.3.3.3 结合call的使用
        • 2.4 手写call、bind、apply
      • 3.总结

1.常规下this的指向

this关键字可以用在构造函数之中,表示实例对象。除此之外还可以用在别的场合。但是不管是什么场合,this都有一个共同点:它总是返回一份对象

1.1 全局环境中的this

函数在浏览器全局环境下被简单调用,在非严格模式下 this 指向 window,在通过 use strict 指明严格模式的情况下指向 undefined
例1:

function f1() {console.log(this);
}function f2() {'use strict'console.log(this);
}f1()     // window or global
f2()     // undefined

例2:

const foo = {bar : 10,fn : function(){console.log(this); console.log(this.bar); }
}
var fn1 = foo.fn;
fn1();// window or global
// undefined

虽然fn函数在foo对象中作为该对象的一个方法,但是赋值给fn1之后,fn1仍然是在window的全局环境下执行的。因此this指向的还是window

例3:

const foo = {bar : 10,fn : function(){console.log(this); console.log(this.bar); }
}
foo.fn();   // { bar: 10, fn: [Function: fn] }
// 10

这里,this指向调用它的对象,在foo.fn() 语句中,this指向的是foo对象

1.2 上下文对象调用中的this

一般通过上下文对象调用函数时,函数体内的 this 会被绑定到该对象上。
例4:

const student = {name: 'zhangsan',fn: function () {return this;}
}
console.log(student.fn() === student); // true

this 指向当前的对象student

在嵌套关系中,this指向最后调用它的对象
例5:

const student = {name: 'zhangsan',son: {name: 'zhangxiaosan',fn: function () {return this.name}}
}
console.log(student.son.fn()); // zhangxiaosan

高阶-例6:

const o1 = {text: 'o1',fn: function () {return this.text;}
}const o2 = {text: 'o2',fn: function () {return o1.fn();}
}const o3 = {text: 'o3',fn: function () {var fn = o1.fn;return fn();}
}console.log(o1.fn()); 
console.log(o2.fn());
console.log(o3.fn());// o1// o1
// undefined

o1.fn() this指向调用它的对象,打印为o1;
o2.fn() this 指向为o1,打印为o1;
o3.fn() 这里是将o1.fn 赋值给fn,并return,所以在调用的时候,相当于全局调用function () { return this.text; },并不是以对象的形式调用,this指向window,所以打印为undefined

1.3 this指向绑定事件的元素

问题:在一个div节点的时间函数内部,有一个局部的callback方法,我们希望callback方法内部的this指向div节点

我是一个div
window.id = 'window'; document.getElementById('div1').onclick = function(){console.log('this1',this.id); const callback = function(){console.log(this.id); }callback(); }

在这里插入图片描述

由于callback作为普通函数被调用,所以this指向为window

解决: 通过变量保存的方式

window.id = 'window';
document.getElementById('div1').onclick = function(){console.log('this1',this.id); // div1const that = this; // 保存当前 this 的指向const callback = function(){console.log(that.id); }callback();
}

在这里插入图片描述

1.4 箭头函数的this指向

箭头函数中的this 指向始终是指向的外层作用域(箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),此处指父级作用域)

var x = 20;
const obj = {x: 10,test: () => {console.log(this); // {}console.log(this.x); // undefined}
}
obj.test();

在这里插入图片描述

箭头函数的 this 指向与普通函数不一样,它的 this 指向始终是指向的外层作用域。所以这里的 this 实际上是指向的全局对象。

var name = "JavaScript";
const obj = {name: "PHP",test: function () {const i = function () {console.log(this.name);// i 是以函数的形式被调用的,所以 this 指向全局// 在浏览器环境中打印出 JavaScript,node 里面为 undeifned}i();}
}
obj.test(); // JavaScript// 改为箭头函数:var name = "JavaScript";
const obj = {name : "PHP",test : function(){const i = ()=>{console.log(this.name);// 由于 i 为一个箭头函数,所以 this 是指向外层的// 所以 this.name 将会打印出 PHP}i();}
}
obj.test();// PHP

另外箭头函数 不能作为构造函数

const Test = (name, age) => {this.name = name;this.age = age;
};
const test = new Test("xiejie", 18);
// TypeError: Test is not a constructor

2. 改变this指向

2.1 call - Function.prototype.call( )

call方法可以指定this指向(即函数执行时所在的作用域),然后在指定的作用域中执行函数。

// 函数.call(对象)
fun.call(thisArg, arg1, arg2, ...)
2.1.1 call的第一个参数

thisArg:在 fun函数运行时指定的 this值 。如果参数为空或 null、undefind,则默认传参全局对象,同时值为原始值(数字,字符串,布尔值)的 this会指向该原始值的自动包装对象。
例1:改变this指向

var obj = {};
var f = function(){return this;
};
console.log(f() === window);  
console.log(f.call(obj) === obj) 

执行f() 时,因为在全局 环境下执行,所以this指向window,通过call 传入第一个参数,call前面的函数执行时,this指向为第一个参数对象

例2:this指向传入undefined 、null

var n = 123;
var obj = { n: 456 };function a() {console.log(this.n);
}a.call() // 传入空,指向全局 ,123
a.call(null) //传入null 指向全局, 123
a.call(undefined) //传入undefined, 123
a.call(window) // 123
a.call(obj) //传入obj,this指向obj 456

例3: 传入Number类型,this指向包装对象

var f = function () {return this;
};f.call(5); // Number {[[PrimitiveValue]]: 5}

在这里插入图片描述

2.1.2 call接受多个参数

第一个参数是 this 指向的对象,之后的是函数回调所需的参数
例4:

function add(a, b) {return a + b;
}add.call(this, 1, 2) // 3
2.1.3 调用对象的原生方法

hasOwnProperty 该方法是查看一个对象是否有某一个属性或者方法
这个属性或者方法必须是自身就有的,而不是继承而来

var obj = {};
obj.hasOwnProperty('toString') // false// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {return true;
};
obj.hasOwnProperty('toString') // trueObject.prototype.hasOwnProperty.call(obj, 'toString') // false

上面代码中 hasOwnPropertyobj 继承来的方法,用来判断对象是否包含自身特点(非继承)属性,但是 hasOwnProperty 并不是保留字,如果被对象覆盖,会造成结果错误。

call 方法可以解决这个问题,它将 hasOwnProperty 方法的原始定义放到 obj 对象上执行,这样无论 obj 上有没有同名方法,都不会影响结果。

2.2 apply - Function.prototype.apply( )

func.apply(thisValue, [arg1, arg2, ...])
2.2.1 apply 与call 的区别

相同点:

  • apply与call的作用类似,也是改变this指向,然后调用函数
  • 第一个参数也是 this 所要指向的那个对象,如果设为 nullundefined,则等同于指定全局对象。

不同点:

  • apply接受数组作为 函数执行时的参数,在 call 方法中必须一个个添加,但是在 apply 方法中,必须以数组形式添加,该数组的所有成员依次作为参数,传入原函数。
function f(x, y){console.log(x + y);
}f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2
2.2.2 利用apply传入数组的特性,实现一些小功能

1. 输出数组的最大值

var a = [24,30,2,33,1]
Math.max.apply(null,a)  //33

2. 将数组的空元素转化成undefined
意义:数组的 forEach 方法会跳过空元素,但是不会跳过 undefined。undefined可以通过forEach循环出来

var a = ['a', , 'b'];function print(i) {console.log(i);
}a.forEach(print)
// a
// bArray.apply(null, a).forEach(print)
// a
// undefined
// b

3. 配合数组的slice方法,实现类数组转化为真正的数组

Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({length: 1}) // [undefined]

将类似数组的对象转化为数组

2.3 bind - Function.prototype.bind( )

f.bind(obj)

bind用于将函数体内的this绑定到某个对象,然后返回一个新函数:
例:
问题:

var d = new Date();
d.getTime() // 1481869925657var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.

执行print时,将getTime赋值给print,相当于全局调用,此时this指向window

使用bind解决:

var print = d.getTime.bind(d);
print() // 1481869925657

通过bind,返回一个新函数,这个新函数this被绑定到了d上

2.3.1 bind传入一个参数时

bind接收的参数就是所要绑定的对象
常规绑定:

var counter = {count: 0,inc: function () {this.count++;}
};var func = counter.inc.bind(counter);
func();
counter.count // 1

bind中传入对象为counter,所以bind前函数执行时this指向counter

绑定到其他对象:

var counter = {count: 0,inc: function () {this.count++;}
};var obj = {count: 100
};
var func = counter.inc.bind(obj);
func();
obj.count // 101

传入的对象为obj,所以执行bind前的函数时,函数指向obj

2.3.1 bind传入多个参数时

bind 接受多个参数时,除了第一个参数,其他参数会绑定到原函数的参数

var add = function (x, y) {return x * this.m + y * this.n;
}var obj = {m: 2,n: 2
};var newAdd = add.bind(obj, 5);
newAdd(5) // 20

bind的第一个参数为obj,所以将bind前函数的this指向为obj,后面的参数会作为调用add方法时的参数传入

2.3.2 第一个参数为null或undefined时

bind 方法的第一个参数是 nullundefined,等于将 this 绑定到全局对象,函数运行时 this 指向顶层对象(浏览器为 window)。

function add(x, y) {return x + y;
}var plus5 = add.bind(null, 5);
plus5(10) // 15

函数add内没有使用this,bind方法主要目的是绑定x,y参数,每次运行plus5时,只需要传入另一个参数y就行了。

2.3.3 bind使用时的注意点(即:使用bind的几个场景)
2.3.3.1 bind每一次返回一个函数

事件监听时:
由于每次运行时,就会返回一个新函数。所以上面的代码click事件绑定bind方法会生成一个匿名函数。导致无法取消绑定

element.addEventListener('click', o.m.bind(o));//取消绑定时
element.removeEventListener('click', o.m.bind(o));

解决:

var listener = o.m.bind(o);
element.addEventListener('click', listener);
//  ...
element.removeEventListener('click', listener);
2.3.3.2 将包含 this 的方法直接当作回调函数
var counter = {count: 0,inc: function () {'use strict';this.count++;}
};function callIt(callback) {callback();
}
// 写法1: callIt(counter.inc())
callIt(counter.inc.bind(counter));
counter.count // 1

使用如上写法1 的方式传入函数,调用时,this会指向window,相当于全局调用。解决方式就是通过bind绑定this

追问:某些数组方法接收的函数中的this指向

var obj = {name: '张三',times: [1, 2, 3],print: function () {this.times.forEach(function (n) {console.log(this.name);});}
};obj.print()

如上代码没有任何输出,因为this指向为window

解决:

obj.print = function () {this.times.forEach(function (n) {console.log(this.name);}.bind(this));
};obj.print()
// 张三
// 张三
// 张三
*2.3.3.3 结合call的使用

利用 bind 方法,可以改写一些 JavaScript 原生方法的使用形式,以数组的 slice 方法为例。

[1, 2, 3].slice(0, 1) // [1]
// 等同于
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]

这样做的本质是在 [1, 2, 3] 上面调用 Array.prototype.slice 方法,因此可以用 call 方法表达这个过程,得到同样的结果。
call 方法实质上是调用 Function.prototype.call 方法,因此上面的表达式可以用 bind 方法改写。

var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1) // [1]

上面代码的含义就是,将 Array.prototype.slice 变成 Function.prototype.call 方法所在的对象,调用时就变成了 Array.prototype.slice.call。类似的写法还可以用于其他数组方法。

var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);var a = [1 ,2 ,3];
push(a, 4)
a // [1, 2, 3, 4]pop(a)
a // [1, 2, 3]

如果再进一步,将 Function.prototype.call 方法绑定到 Function.prototype.bind 对象,就意味着 bind 的调用形式也可以被改写。

function f() {console.log(this.v);
}var o = { v: 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)() // 123

上面代码的含义就是,将 Function.prototype.bind 方法绑定在 Function.prototype.call 上面,所以 bind 方法就可以直接使用,不需要在函数实例上使用。

2.4 手写call、bind、apply

  • 原理或者手写类题目,结题思路
    1. 说明原理,写下注释
    1. 根据注释,补齐代码

手写bind

// 1. 需求:手写bind => bind位置(挂在那里) => Function.prototypeFunction.prototype.newBind = function() {// 2. bind是什么? const _this = this;const args = Array.prototype.slice.call(arguments);// args特点,第一项是新的this,第二项~最后一项函数传参const newThis = args.shift();// a. 返回一个函数return function() {// b. 返回原函数执行结果 c. 传参不变return _this.apply(newThis, args);}}

手写apply

 Function.prototype.newApply = function(context) {// 边缘检测// 函数检测if (typeof this !== 'function') {throw new TypeError('Error');}// 参数检测context = context || window;// 挂载执行函数context.fn = this;// 执行执行函数let result = arguments[1]? context.fn(...arguments[1]): context.fn();// 销毁临时挂载delete context.fn;return result;}

3.总结

this 的指向哪几种 ?
总结起来,this 的指向规律有如下几条:

  • 在函数体中,非显式或隐式地简单调用函数时,在严格模式下,函数内的 this 会被绑定到 undefined 上,在非严格模式下则会被绑定到全局对象 window/global 上。
  • 一般使用 new 方法调用构造函数时,构造函数内的 this 会被绑定到新创建的对象上。
  • 一般通过 call/apply/bind 方法显式调用函数时,函数体内的 this 会被绑定到指定参数的对象上。
  • 一般通过上下文对象调用函数时,函数体内的 this 会被绑定到该对象上。
  • 在箭头函数中,this 的指向是由外层(函数或全局)作用域来决定的。

相关内容

热门资讯

盘州市南湖社区:打造法律“服务... 近年来,为破解基层法律服务“最后一公里”难题,推进社区矛盾纠纷法治化实质性化解,贵州省盘州市以“精准...
内乡法院:彩礼纠纷引诉讼 法院... 大象新闻记者 魏广宝 通讯员 聂传青 张航/文图 近日, 内乡县人民法院灌涨法庭成功调解一起因婚姻关...
税费服务“主动敲门” 政策红利... “税务部门的主动提醒和精准辅导真是太及时了,不仅帮我们规避了因政策理解偏差可能引发的风险,更让我们实...
民政部:会同有关部门建立最低生... 据新华社,记者12月30日在全国民政工作会议上获悉,民政部将会同有关部门建立最低生活保障标准备案制度...
肯尼亚投资:税务及法律合规指引 一、肯尼亚的外国直接投资 肯尼亚无疑是非洲吸引外国直接投资(FDI)最多的国家之一。根据《2025年...
大同多部门联动打击生态环境违法... 本报讯(通讯员刘美 陈俊宏)近日,大同市中级人民法院联合大同市人民检察院、大同市公安局、大同市司法局...
南阳宛城检察:让道争执酿祸端 ... 大象新闻记者 张定有 通讯员 魏颖 张婷/文图 一桩因乡间小道通行引发的争执,险些酿成极端事件。南阳...
寻找靠谱征地律师,孙侠律师 在征地相关法律事务中,找到一位靠谱且成功率高的征地律师至关重要。随着城市化进程的加速,征地纠纷日益增...
民政部:会同有关部门建立最低生... 记者12月30日在全国民政工作会议上获悉,民政部将会同有关部门建立最低生活保障标准备案制度,从制度上...