前言
上篇文章我们讲到new的原理时,我们提及到了this的绑定,而上文并没有详细介绍,今天这篇文章我们就来详细聊聊js中关键字this的用法,打开this世界的大门,熟练运用this能让你对js代码的脉络能加清晰
1.为什么要有this
function identify(context){
return context.name.toUpperCase()
}
function speak(context){
var gretting = "hello, i am "+identify(context)
console.log(gretting);
}
var me = {
name:"Tom"
}
speak(me);
这段代码不难理解,就是调用speak函数然后把对象me作为实参传入,运行过程中又调用了函数identify,此时又将函数speak中的实参me作为函数identify的实参,我们这么一看这么这实参形参关系这么乱嘞,能不能搞点大家伙都好理解的,那么 this 的作用就凸显出来了
function identify() {
return this.name.toUpperCase()
}
function speak() {
var gretting = 'Hello i am' + ' ' + identify.call(this);
console.log(gretting)
}
var me = {
name: 'tom'
}
speak.call(me)
看这段代码,我们得到了同样的结果,但是函数形参实参都因为this的出现消失了,this一定程度上代替了形参实参的作用,代码也看着相对简洁明了了,我们大致能知道this的作用:
this 让函数可以自动引用合适的上下文对象
2.认识this
在英语里面大家对this应该都不陌生吧,this是一个代词,我可以指着手边的苹果说this apple ,这个this就像我的手指一样,指向了这个苹果。而js里面的this也是一个代词,在js中永远代指某一个域,且this只存在于域中才有意义
我们先来点开我们的浏览器的控制台试试,我们看到它指向的是window(全局)
而在node里面,它指向的是global,也是全局作用域。
3.this的指向
现在我们要来聊点干货了,this的指向,也可以用绑定这个说法
1.默认绑定(指向):函数独立调用,this指向window
2.隐式绑定:当函数的引用有上下文对象时(当函数被某一个对象所拥有时且调用),this指向该上下文对象
3.隐式丢失:当函数的引用有一连串(多个)上下文对象,this指向最近的那个对象(就近原则)
4.显示绑定:call apply bind 显示的将函数的this绑定到一个对象上
我们先来聊聊函数的独立调用和非独立调用吧,
函数独立调用 直接调用函数 例如foo()
非独立调用 函数被某一个对象所拥有时且调用 例如obj.foo()
现在我们可以来看看
默认绑定
function bar() {
let person = {
name: 'lisi',
age: 19
}
foo()
}
bar()
function foo() {
console.log(this)
}
我们可以看到我们是直接调用了函数foo,因此这里输出的this指向的还是全局
隐式绑定
function foo() {
console.log(this)
}
const obj = {
a: 1,
foo: foo
}
obj.foo()
这段代码我们可以看到是函数foo的非独立调用,对象obj中左边的foo是自定义的key,值为foo,没有括号就是引用,相当于foo:function foo() { console.log(this) },因此这时候this指向的是对象obj
隐式丢失
function foo() {
console.log(this)
}
const obj = {
a: 1,
foo: foo//左边的foo的自定义的key,值为foo,没有括号就是引用,相当于foo:function foo() { console.log(this) }
//foo: foo() 下面的obj.foo()指向window
}
// obj.foo()//非独立调用
const obj2 = {
a: 2,
obj: obj
}
obj2.obj.foo()
我们看到这里对foo的控制权还是在obj身上,也就是说函数的隐式绑定遵循就近原则
显示绑定
我们可以人为的用call apply bind 显示的将函数的this绑定到一个对象上,我们这里就只拿call来举例,想要了解apply和bind的伙伴们请自行去查阅用法
function foo() {
console.log(this.a)
}
var obj = {
a: 1
}
foo() //undefined 这时候this指向的时window window里面没有a属性
foo.call(obj) // call方法的参数是this的指向
我们看到第七行调用foo函数得到的是undefined,这是因为这里的foo是直接调用的,也就是此时它的this指向全局,而全局中我们并不能找到属性a,因此就输出了undefined,但是第八行,我们人为的用call方法将foo的this绑定到了obj身上,此时它的this指向对象obj,这时属性a能找到值为1
function foo(x, y) {
console.log(this.a, x + y)
}
var obj = {
a: 1
}
foo.call(obj, 2, 3)
如果我们的函数要显示绑定的同时也要穿参数我们也可以用call方法帮我们把参数给函数
小知识:
call方法在Function.prototype.call(),也就是构造函数身上显示原型
call方法调用时就直接调用了函数,能帮函数接收参数
4、箭头函数
(在不用this的情况下,箭头函数和普通函数没有区别)
1.箭头函数没有this,写在了箭头函数中的this那也是它外层的非箭头函数的
function foo() {
let bar = function () {
let baz = () => { // 箭头函数里的this指向外层的非箭头函数
console.log(this)
}
baz()
}
bar()
}
foo()
因为this在箭头函数不存在,所以this是外层的bar()函数,而bar又被独立调用,所以this指向全局global
2.箭头函数不能作为构造函数使用
let Foo = () => {
this.name = 'zhangsan'
}
let foo = new Foo()
console.log(foo)
这里的箭头函数是不能作为构造函数的,因为new的过程中,需要使用this,而箭头函数中没有this,详细的可以了解我上一篇文章 传送门
进阶 手写call方法
function foo(x) {
console.log(this.a, x)
}
const obj = {
a: 1
}
Function.prototype.myCall = function (...args) {
//1.将foo引用到obj上
//2.让obj调用foo
//3.移除obj上的foo
const context = args[0]
const arg = args.slice(1) //从第二个一路切
context.fn = this
const res = context.fn(...arg)//解构,传参
delete context.fn
return res
}
foo.myCall(obj, 2)