this
关键字在 JavaScript 中是一个关键字,也是小升初的必考题。
函数的调用方式决定了 this
的值(运行时绑定)。 this
不能在执行期间被赋值,并且在函数每一次被调用时 this
的值也有可能不同
—MDN
我们初步可以理解为使用我,我 this
就指向谁, this
作为一个指针,始终指向调用者。
什么是 this ?
this
是函数在运行时,函数体内部自动生成的一个对象,只能在函数体内部使用
简单的函数调用:
function test() {
this.name = "code"
console.log(this === window)
console.log(this.name)
}
test() // code
复制代码
如果我们在 test 函数中打印 this === window,得到结果为 true ,展开 window 对象可以看到有个 name 属性,值是 code
作为对象方法的调用:
// demo1
function test() {
console.log(this === window)
console.log(this.name)
}
let obj = {name: 'wccode'}
obj.fun = test
obj.fun() // false, wccode
// demo2
let demo1 = {name:'demo1',fun:test}
let demo2 = {name:'demo2',fun:test}
demo1.fun() //demo1
demo2.fun() //demo2
复制代码
**this
** 的值取决于函数被调用的方式。
理解this?
JavaScript 语言之所以有 this 的设计,跟内存里面的数据结构有关系:
数据类型:
值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。
引用数据类型:对象(Object)、数组(Array)、函数(Function)。
数据结构:
在计算机科学中, 对象是指内存中的可以被 标识符引用的一块区域,在 Javascript 里,对象可以被看作是一组属性的集合。
数据属性的特性(Attributes of a data property)
表格样式垮掉,建议读一读MDN ⚠
强烈建议阅读:JavaScript 数据类型和数据结构 | MDN
将一个对象赋值给变量 obj
:
let obj = { name: "code" }
// 对象是引用类型,JS引擎在内存生成对象后,其实是把对象的内存地址赋值给了变量 obj 。也就是说,变量 obj 是一个地址(reference)
obj.name
// JS 引擎先从 obj 拿到内存地址,然后再从地址中读出原始对象,返回它的 name 属性
复制代码
函数也属于引用类型,所以函数可以在不同的运行环境中执行,所以需要有一种机制,可以在函数体内部获得当前的运行环境(context), this
的出现就是为了这个需求,目的就是在函数体内部,获得函数当前的运行环境。 this
也很无辜~
几种使用场景
全局环境
全局环境使用 this
,this指向是顶层对象。
this === window // true
function f() {
console.log(this === window);
}
f() // true
复制代码
构造函数
构造函数中使用 this
, this 指向实例对象。
var Obj = function (p) {
this.p = p;
};
var o = new Obj('Hello World!');
o.p // "Hello World!"
复制代码
对象内的方法
这条规则水很深,你把握不住的
正常情况:
var obj ={
foo: function () {
console.log(this);
}
};
obj.foo() // obj obj.foo 执行,内部this指向obj
复制代码
例外情况 01 :
参考的阮老师文章,阮老师这里写的有点不好理解
// 情况一
(obj.foo = obj.foo)() // window
// 情况二
(false || obj.foo)() // window
// 情况三
(1, obj.foo)() // window
复制代码
上面三种情况可以理解为:
let test = obj.foo
test() // window
复制代码
前面写过,函数是引用类型,对象内的函数也如此,obj 和 obj.foo 是两个储存地址。上面几种情况相当于将 foo 内存地址给到 test,test() 直接调用,运行环境就是全局环境,所以 this 指向全局环境。
例外情况 02:
对象包对象,this 不会像变量一样往上查找,牢记 this 指向调用者。
var a = {
p: 'Hello',
b: {
m: function() {
console.log(this.p);
}
}
};
a.b.m() // undefined
// 相当于
var b = {
m: function() {
console.log(this.p);
}
};
var a = {
p: 'Hello',
b: b
};
(a.b).m() // 等同于 b.m()
复制代码
改造:
var a = {
b: {
m: function() {
console.log(this.p);
},
p: 'Hello'
}
};
var hello = a.b.m;
hello() // undefined
复制代码
为什么 hello() 会是 undefined,画重点哈,又遇到了,function是引用类型,m 这个其实是函数的一个地址,直接使用 hello = a.b.m
相当于将函数地址赋值给 hello 变量,执行 hello()
。这个时候在全局环境执行,所以 this.p
是 undefined。
箭头函数
箭头函数没有定义this绑定
用之前的例子作对比
var obj ={
foo: ()=> {
console.log(this);
}
};
obj.foo() // window
复制代码
切换/绑定 this
JavaScript 提供了call
、apply
、bind
这三个方法,来切换/固定this
的指向,这里不多做介绍
call & apply
// 对象可以作为 bind 或 apply 的第一个参数传递,并且该参数将绑定到该对象。
var obj = {a: 'Custom'};
// 声明一个变量,并将该变量作为全局对象 window 的属性。
var a = 'Global';
function whatsThis() {
return this.a; // this 的值取决于函数被调用的方式
}
whatsThis(); // 'Global' 因为在这个函数中 this 没有被设定,所以它默认为 全局/ window 对象
whatsThis.call(obj); // 'Custom' 因为函数中的 this 被设置为obj
whatsThis.apply(obj); // 'Custom' 因为函数中的 this 被设置为obj
复制代码
bind
ECMAScript 5 引入了
Function.prototype.bind()
。调用f.bind(someObject)
会创建一个与f
具有相同函数体和作用域的函数,但是在这个新函数中,this
将永久地被绑定到了bind
的第一个参数,无论这个函数是如何被调用的。
function f(){
return this.a;
}
var g = f.bind({a:"azerty"});
console.log(g()); // azerty
复制代码
bind使用要慎重
bind() 方法每次运行,都会创建一个新函数。
一是会有性能问题,二是会产生闭包,应用在事件绑定中,则无法取消。
最佳实践
-
通过将
this
值分配给封闭的变量,可以解决this
问题。 -
避免使用全局变量,在局部作用域中保存其他对象的引用
参考链接:
红宝石 | javascript 高级程序设计