this
this 机制是 JavaScript 特有的,它是为了解决在对象内部的方法中使用对象内部的属性的需求。
如果不使用 this 来实现对对象内部的属性 JavaScript 可以使用对象.属性访问到:
let people = {
name: 'litangmm',
logName: function(){
console.log(people.name)
}
}
这种方法,方法与对象强耦合。对象变量变化,方法必须接着改变。如果有一个匿名对象数组,那么对象位置改变,方法就得改。
let peoples = [
{name:'litangmm',logName: function(){console.log(peoples[0].name)}},
{name:'litangmm1',logName: function(){console.log(peoples[1].name)}},
{name:'litangmm2',logName: function(){console.log(peoples[2].name)}},
]
于是,this 出现了。
What is This?
概念: this可以理解为一个特殊的变量。它和执行上下文绑定,在运行时才确定。this 的指向遵循以下几条规则:
- 在全局环境下,this 指向全局的 window。
- 作为函数执行时,如果是作为对象的函数执行,则指向该对象本身,否则指向全局的window。
- 1 2都是非严格模式下的,全局模式下,this 拿不到全局 window 而是
undefined
。
实验一下:
'use strict';
// console.log(that);
let people = {
name: 'litangmm on people',
logName,
}
function logName(){
console.log(this.name)
}
people.logName();
下面就来分析一下代码运行的结果:
console.log(that); // Uncaught ReferenceError: that is not defined
学过简单的JavaScript语法都知道,要用一个变量,我们就得先定义它,否则代码会报错。那为什么this
可以当做一个变量来用呢?那肯定是有人帮我们创建了它,就像我们直接用window|document
一样。
let people = {
name: 'litangmm on people',
logName,
}
let people2 = {
name: 'litangmm2 on people',
logName,
}
function logName(){
console.log(this.name)
}
people.logName(); // litangmm on people
people2.logName(); // litangmm2 on people
可以理解为,当代码执行时,执行器会在上下文中声明一个this
变量,如果代码当前执行的是对象
的对象函数
,this
将指向该对象
,这样我们就能通过this
访问到该对象
的属性。
那我们可以改变this
的指向吗? 当然,this
是一个变量,JavaScript允许我们能够将任何值付给一个变量。
bind apply call 都是属于函数的原型方法,可以改变函数执行的 this 的指向。它们的利用了this的第2条原则:
作为函数执行时,如果是作为对象的函数执行,则指向该对象本身。
call 和 apply
执行效果
- 改变 thisarg
- 接收函数入参
- 执行函数
说明
- 对于 thisarg ,在非严格模式下,如果 thisarg 为 null 或 undefined ,thisarg 会为 全局 window。
- 对于函数入参,call 接收 的是多个参数值,apply接收的是一个参数列表。
// mycall.js
Function.prototype.mycall = function(thisarg,...args){
let context = thisarg || window
context.fn = this
const res = context.fn(...args)
delete context.fn
return res
}
function logNameAndAge(age, sex){
console.log(this.name,age,sex)
}
logNameAndAge.mycall({name:"litangmm"},21,'man') // litangmm 21 man
context.fn = this
这里做了一件事,就是在 context
上定义了一个属性fn
,赋值为 this
。**this
是调用mycall
的函数A。**这样,在下一行,函数A就作为 context
对象的对象函数被调用,函数A执行的this
自然指向context
了。
当然,这代码还是有优化空间的:
- 判断 this 是否为 函数。
- 如果传入的 thisarg 上有 fn 属性,那么该属性会被覆盖。
我们考虑这些情况,实现apply
。
// myapply.js
Function.prototype.myapply = function(thisarg,args){
if(typeof this !== 'function'){
throw new TypeError('error')
}
let context = thisarg || window
const fn = Symbol()
context.fn = this
const res = context.fn(...args)
delete context.fn
return res
}
function logNameAndAge(age, sex){
console.log(this.name,age,sex)
}
logNameAndAge.myapply({name:"litangmm"},[21,'man']) // litangmm 21 man
说明:
- 通过
typeof
判断调用的是否是一个函数。 - 通过
Symbol
来解决属性冲突。
bind
执行效果
- 改变 thisarg。
- 接收函数入参。
- 返回函数。 对于返回函数:
- 可以接收入参。
- 可以作为构造函数使用。
- 作为构造函数使用时,bind 绑定的
this
会被忽略。
难点
bind
执行时接收可以接收参数(类似call接收多个参数),返回的函数还可以接收参数。- 判断返回函数是作为构造函数被使用。
// mybind.js
Function.prototype.mybind = function(thisarg,...args1){
if(typeof this !== 'function'){
throw new TypeError('error');
}
const that = this;
let fBound = function(...args2){
return that.apply(this instanceof fBound ? this: thisarg,args1.concat(args2));
}
let fNop = function(){};
fNop.prototype = that.prototype;
fBound.prototype = new fNop();
return fBound;
}
bind 本质是一个函数装饰器:
- 参数拼接。
- 如果是普通调用,使用bind的this。
- 当做返回函数作为构造函数,则使用返回函数的this。
说明
之所以可以通过this instanceof F
来判断是被当作构造函数被使用的,可以参考new 的原理:
-
创建一个空对象。
-
指定空对象的
__proto__
为构造函数的prototype
。 -
绑定该空对象到构造函数。
-
执行绑定后的构造函数。
-
如果构造函数有返回值,则返回返回值,否则返回对象。
!注意2步,因为返回的构造函数 是 fBound
,而,我们希望得到的that
上的原型,所以,有代码
fBound.prototype = that.prototype;
这代码是存在问题的,因为fBound.prototype=== that.prototype === 原型对象地址
,所以操作fBound.prototype
修改会改变 that.prototype
。所以,我们可以使用原型链来实现fBound
与 that
的连接。
let fNop = function(){};
fNop.prototype = that.prototype;
fBound.prototype = new fNop();
这样,fBound.prototype
就是一个对象,对象的__proto__
指向that.prototype
。可以参考这篇文章。
!注意3、4步,构造函数执行的时候,空对象的__proto__
已经指向了构造函数的prototype
。所以,绑定后的构造函数的this
是可以在原型链上找到构造函数的。