一、this
概述:
this
是 JavaScript 语言的一个关键字。它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。函数的不同使用场合,
this
有不同的值。总的来说,this
就是函数运行时所在的环境对象。下
1. 纯粹的函数调用(默认绑定)
非严格模式下this指向全局对象
window
,严格模式下函数内的this指向undefined
// 非严格模式
var name = 'window';
var test = function () {
console.log(this.name);
}
test(); // 'window'
let name2 = 'window2';
let test = function () {
console.log(this === window);
console.log(this.name2);
}
test() // true, undefined
// 严格模式
'use strict'
var name = 'window';
var test = function () {
console.log(typeof this === 'undefined');
console.log(this.name);
}
test(); // true,// 报错,因为this是undefined
注意:
如果把 var
改成了 let
或 const
,变量是不会被绑定到window上
的,所以此时会打印出三个undefined
2. 作为对象方法的调用(隐式绑定)
函数还可以作为某个对象的方法调用,这时
this
就指这个上级对象。
var obj = {
a: 2,
foo1: function () {
console.log(this.a) // 2
},
foo2: function () {
setTimeout(function () {
console.log(this) // window
console.log(this.a) // 3
}, 0)
}
}
var a = 3
obj.foo1()
obj.foo2()
对于
setTimeout
中的函数,这里存在隐式绑定的this丢失,也就是当我们将函数作为参数传递时,会被隐式赋值,回调函数丢失this绑定,因此这时候setTimeout中函数内的this是指向window
3. 构造函数调用
所谓构造函数,就是通过这个函数,可以生成一个新对象。这时,
this
就指这个新对象。
function Student(name) {
this.name = name;
console.log(this); // {name: '小明'}
// 相当于返回了
// return this;
}
var result = new Student('小明');
4. 箭头函数调用
箭头函数和普通函数的重要区别:
- 没有自己的
this
、super
、arguments
和new.target
绑定。- 不能使用
new
来调用。- 没有原型对象。
- 不可以改变
this
的绑定。- 形参名称不能重复。
箭头函数中没有this
绑定,必须通过查找作用域链来决定其值。 如果箭头函数被非箭头函数包含,则this
绑定的是最近一层非箭头函数的this
,否则this
的值则被设置为全局对象。
var obj = {
name: 'obj',
foo1: () => {
console.log(this.name) // window
},
foo2: function () {
console.log(this.name) // obj
return () => {
console.log(this.name) // obj
}
}
}
var name = 'window'
obj.foo1()
obj.foo2()()
二、 call、apply、bind
(显示绑定)
三者的区别:
- 三者都可以显式绑定函数的this指向
- 三者第一个参数都是this要指向的对象,若该参数为undefined或null,this则默认指向全局window
- 传参不同:apply是数组、call是参数列表,而bind可以分为多次传入,实现参数的合并
- call、apply是立即执行,bind是返回绑定this之后的函数,如果这个新的函数作为构造函数被调用,那么this不再指向传入给bind的第一个参数,而是指向新生成的对象
2.1 call、apply
语法: fun.call(thisArg, arg1, arg2, ...)
thisArg
在fun
函数运行时指定的this
值。需要注意的是,指定的this
值并不一定是该函数执行时真正的this
值,如果这个函数处于非严格模式下,则指定为null
和undefined
的this
值会自动指向全局对象(浏览器中就是window
对象),同时值为原始值(数字,字符串,布尔值)的this
会指向该原始值的自动包装对象。
arg1, arg2, ...
指定的参数列表
返回值
返回值是你调用的方法的返回值,若该方法没有返回值,则返回undefined
。
call
和 apply
不同点: apply
只接收两个参数,第二个参数可以是数组也可以是类数组,其实也可以是对象,后续的参数忽略不计。call
接收第二个及以后一系列的参数。
// 例子1:浏览器环境 非严格模式下
var test = function (a, b) {
console.log(this);
console.log([a, b]);
}
test.apply(null, [1, 2]); // this是window // [1, 2]
test.apply(0, [1, 2]); // this 是 Number(0) // [1, 2]
test.apply(true); // this 是 Boolean(true) // [undefined, undefined]
test.call(undefined, 1, 2); // this 是 window // [1, 2]
test.call('0', 1, { a: 1 }); // this 是 String('0') // [1, {a: 1}]
// 例子2:浏览器环境 严格模式下
'use strict';
var test2 = function (a, b) {
console.log(this);
console.log([a, b]);
}
test2.call(0, 1, 2); // this 是 0 // [1, 2]
test2.apply('1'); // this 是 '1' // [undefined, undefined]
test2.apply(null, [1, 2]); // this 是 null // [1, 2]
(1) 模拟实现 call
思路分析:
- 执行调用
call方法
的函数
- 改变this指向 也就是指向第一个参数
- 返回执行函数后的返回值
Function.prototype.myCall = function (context) {
// context为undefined或null时,则this默认指向全局window
if (context === undefined || context === null) {
context = window;
}
// var args = []
// for (var i = 1; i < arguments.length; i++) {
// args.push(`arguments[${i}]`)
// }
// var fn = createUUID()
// es6 写法
var args = [...arguments].slice(1);
var fn = Symbol();
context[fn] = this; // this就是调用者 添加到第一个参数上
// var result = eval(`context[fn](${args})`)
var result = context[fn](...args) // 谁调用this指向谁 这样就this指向传入的第一个参数
delete context[fn]
return result // 返回执行后的返回值
}
// 测试代码
var obj = { name: 'obj' }
function fn(a, b) {
console.log(this.name);
return a + b
}
fn.myCall(obj, 1, 2)
// 生成UUID 通用唯一识别码
function createUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
(2) 模拟实现 apply
唯一的不同点就是call是传多个参数 apply传入的是数组
Function.prototype.myApply = function (context, args) {
if (context === undefined || context === null) {
context = window;
}
var fn = Symbol();
context[fn] = this;
var result = context[fn](...args)
delete context[fn]
return result
}
// 测试代码
var obj = { name: 'obj' }
function fn(a, b) {
console.log(this.name);
return a + b
}
fn.myApply(obj, [1, 2])
2.2 bind
思路分析:
- 函数调用bind()方法返回一个函数
- 原函数使用bind()方法绑定函数之后,原函数可以作为构造函数使用的。
Function.prototype.myBind = function (context, ...args) {
if (typeof this !== 'function') {
throw new Error('The bind method is not available');
}
if (context === undefined || context === null) {
context = window;
}
// this 原函数(谁调的bind方法) fn唯一的键
let self = this
let fn = Symbol()
// 要返回的函数 需要调用
const result = function (...args1) {
if (this instanceof self) {
// 返回函数作为构造函数调用,this指向新new出的实例对象
this[fn] = self
let res = this[fn](...args, ...args1)
delete this[fn]
return res
} else {
// 返回函数作为普通函数被调用
context[fn] = self;
let res = context[fn](...args, ...args1)
delete context[fn]
return res
}
}
// 继承原函数的原型对象的属性方法
result.prototype = Object.create(self.prototype);
return result
}
// 测试代码
var name = '炒米粉';
var obj = {
name: '程序员米粉'
};
function fn(age, weight) {
console.log(this.name);
console.log(age, weight);
this.phoneNumber = '188888888'
}
fn.prototype.haveMoney = 'NO';
var getFn = fn.myBind(obj, 18);
// 通过使用bind()方法返回的绑定函数,可以使用new创建实例对象。fn的this不再指向obj,而是新创建的对象newObj
var newObj = new getFn(150);
// fn函数
// this.name => undefined;
// age, weight => 18, 50
console.log(newObj.haveMoney); // => 'NO'
console.log(newObj.phoneNumber); // => '188888888'
this原理 阮一峰老师
深化this理解
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!