this关键字
与其他语言相比,函数的
this关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。在绝大多数情况下,函数的调用方式决定了
this的值(运行时绑定)。
this不能在执行期间被赋值,并且在每次函数被调用时this的值也可能会不同。ES5 引入了 bind 方法来设置函数的this值,而不用考虑函数如何被调用的。ES2015 引入了箭头函数,箭头函数不提供自身的 this 绑定(this的值将保持为闭合词法上下文的值)。
引用取自MDN描述,个人理解:this就是一个指针,它指示的就是当前的一个执行环境,可以用来对当前执行环境进行一些操作。因为它指示的是执行环境,所以在定义这个变量时,其实是不知道它真正的值的,只有运行时才能确定他的值。谁调用函数,函数的this就指向谁
this指向哪里
在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变。
- 在方法中,this 表示调用该方法的对象。
- 如果单独使用,this 表示全局对象。
- 在函数中,this 表示全局对象。
- 在函数中,在严格模式下,this 是未定义的(undefined)。
- 在事件中,this 表示接收事件的元素。
- 类似 call() 和 apply() 方法可以将 this 引用到任何对象。
我们来看看如下代码:
//let,const 声明的变量不会绑定给window对象 而var会
// let name='我是 window 上的 名字'
const name='我是 window 上的 名字'
function getName() {
'use strict';
console.log('我的this:',this);
console.log('我的名字:',this.name);
}
/*
Uncaught TypeError: Cannot read properties of undefined (reading 'name')
*/
getName() // 报错
该段代码会报错,let和const由于作用域不会给window上增加属性。在该场景下getName调用,没有明确调用者是谁,this失效默认绑定为 window 对象上,又由于我们使用的是 严格模式下 所以this指向的 undefined的,undefined.name 所以会报错
修改后:
var name='我是 window 上的 名字'
function getName() {
console.log('我的this:',this);
console.log('我的名字:',this.name);
}
getName() // window 我是 window 上的 名字
我们来分析下以下的例子:
var name = '我是 window 上的 名字'
function getName() {
console.log('我的名字:', this.name);
}
getName()
var Person = {
name: '我是Person的名字',
showName: function () {
console.log(this)
console.log('我的名字:', this.name);
},
getName
}
Person.showName()
Person.getName()
var showName = Person.showName
showName()
执行结果:
分析:
函数showName在也是一个引用类型数据,所以在Person的showName执行的内存地址,Person.getName也是一样的,在全局属性 showName 指向 在Person的showName执行的内存地址。执行后, Person.showName()调用着是 Person 所以 执行的this是Person,showName 直接执行的时候没有明确调用者是谁,this失效默认绑定为 window 对象上。
会出现 this 的场景:
// 构造函数 和 原型函数
//构造函数中的this,指的是实例对象。
function Obj(name) {
this.name = name;
};
const obj=new Obj('张三')
obj.name // 张三
// 原型对象中
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
// dom 事件处理
var oBox = document.getElementById('box');
oBox.onclick = function () {
console.log(this) //oBox
}
// setTimeout 和 setInterval
//默认情况下代码
function Person() {
this.age = 0;
setTimeout(function() {
// 此处是window执行
console.log(this);
}, 3000);
}
var p = new Person();//3秒后返回 window 对象
this怎么改变
call
函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。
var obj = {};
var f = function () {
return this;
};
f() === window // true
f.call(obj) === obj // true
手动实现
// arguments 实参类数组的对象
Function.prototype.myCall = function () {
// 将类数组变成数组
const args=Array.prototype.slice.call(arguments)
let context = args[0]
context = context || window
let args = [], result;
const argClone =args.slice(1)
context.fn = this
result = context.fn(...args)
delete context.fn
return result;
}
es6实现
// ...args 将数组变成数组
Function.prototype.myCall = function (...args) {
let context = args[0]
// 如果不存在第一个参数就指向 window
context = context || window
let args = [], result;
const argClone = args.slice(1)
argClone.forEach((item, index) => {
args.push(item)
})
//使用谁调用指向谁的原理
context.fn = this
result = context.fn(...args)
delete context.fn
return result;
}
apply
apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。
function f(x, y){ console.log(x + y); }
f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2
手动实现:
Function.prototype.myApply = function () {
let result = null
// 判断传入的对象是否存在,在浏览器中默认是window, 在node.js中默认是Object
let context = arguments[0]
context = context || window
// 把当前调用的函数赋值给传入对象的
// context.fn 可以理解为: context.prototype.
context.fn = this
if (arguments[1]) {
result = context.fn([...arguments[1]]) // 调用赋值的函数
}else{
result = context.fn()
}
delete context.fn
return result
}
bind
bind()方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。
function showName() {
console.log('this.name', this.name);
}
const name = showName.bind(Person)
name()
手动实现:
Function.prototype.myBind = function() {
// 获取参数
const context = arguments[0]
let args = [...arguments].slice(1) // 第一个是对象
context.fn = this // 调用对象
return function Fn() {
return fn.apply(this instanceof Fn ? this : context, args.concat(...arguments))
}
}
以上三个方法如果context不存在的话就默认指向windos,这个实现逻辑有很多种,这个是我的实现方法。核心点就是,为context绑定当前调用方法,然后执行完毕得到结果返回,删除 context 绑定的该方法
特殊的箭头函数
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的
this,arguments,super或new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承 this。创建的时候已经指定了this,指向词法作用域,也就是外层调用者,使用者。
通过 call 或 apply 调用,由于 箭头函数没有自己的 this 指针,通过 call() 或 apply() 方法调用一个函数时,只能传递参数,他们的第一个参数会被忽略