深入理解JS中this关键字
栏目: JavaScript · 发布时间: 5年前
内容简介:看个例子这里函数identify中的this指向变量me,如果不使用this的话,想实现这种效果,就需要显示的传入上下文对象,例如当模式越来越复杂的时候,显示的传递上下文对象就会变得不太好管理,而this提供了一种更优雅的方式,隐式的"传递"一个对象的引用,因此可以将API设计的更加简洁并且易于复用。
看个例子
function indetify() { retun this.name.toUpperCase() } var obj = { name: 'zz' } indetify.call(obj) // 'ZZ' 复制代码
这里函数identify中的this指向变量me,如果不使用this的话,想实现这种效果,就需要显示的传入上下文对象,例如
function indetify(context) { retun this.name.toUpperCase() } var obj = { name: 'zz' } indetify(obj) // 'ZZ' 复制代码
当模式越来越复杂的时候,显示的传递上下文对象就会变得不太好管理,而this提供了一种更优雅的方式,隐式的"传递"一个对象的引用,因此可以将API设计的更加简洁并且易于复用。
对于this的误解
- this指向自身
- his指向函数作用域(需要明确的是,this在任何情况下都不指向函数的词法作用域)
this到底是什么
his是在运行的时候被绑定的,并不是编写的时候,它的上下文取决于函数调用的各种条件。当一个函数被调用时,会创建一个活动记录(即执行上下文),这个记录包含函数的调用栈(call Stack),调用方式,传入参数等信息,this就是这个记录的一个属性,在函数执行的过程中找到。
要想找到this的绑定对象,首先得找到函数的调用位置,调用位置就在当前执行函数的前一个调用中。
function baz () { // 当前调用栈是baz // 当前调用位置是全局作用域 console.log('baz') bar() // bar的调用位置 } function bar () { // 当前的调用栈是baz -> bar // 所以当前的调用位置在baz中 console.log('bar') foo() // foo的调用位置 } function foo () { // 当前的调用栈是baz -> bar -> foo // 所以当前的调用位置在bar中 console.log('foo') } baz() // baz的调用位置 复制代码
找到调用位置,接下来就要分析this的绑定规则了。
this绑定规则
- 默认绑定规则
function foo () { console.log(this.a) } var a = 1 foo() // 1 复制代码
因为foo()是直接不带任何修饰的函数引用进行调用的,所以只能使用默认绑定。非严格模式下指向window,严格模式下绑定到undefined
- 隐私绑定规则
分析隐式绑定时,必须在一个对象内部包含一个指向函数的属性,通过这个属性间接引用函数
function foo () { console.log(this.a) } var obj = { a: 2, foo: foo } obj.foo() // 2 复制代码
首先要声明的是,无论foo函数是先声明还是直接在obj对象中定义,这个函数严格来说,都不属于obj对象。
隐式绑定会遇到一个问题,就是会丢失绑定对象,也就是会应用默认绑定。比如
function foo () { console.log(this.a) } var obj = { a: 2, foo: foo } var another = obj.foo // 函数别名 var a = '隐式丢失' obj.foo() // 2 another() // '隐式丢失' 复制代码
这里虽然bar是obj.foo的一个引用,但实际上引用的是foo函数本身,因此bar()是不带任何修饰的函数调用,所以也是默认绑定。
还有一种更微妙的情况,发生在传入回调函数的时候
function foo () { console.log(this.a) } var obj = { a: 2, foo: foo } function doFun (fn) { // fn 其实是引用foo // 相当于var fn = foo fn() } var a = '又隐式丢失' obj.foo() // 2 doFun(obj.foo) // '又隐式丢失' 复制代码
实际上传递参数,实际上就是一种赋值操作,所以结果和上面一样
- 显示绑定规则
通常情况,我们使用js提供的call和apply方法实现显示绑定
这俩个方法的第一个参数是一个对象,是给this准备的,在调用函数是将函数绑定到this,因此称为显示绑定。第二个参数就是一个参数列表或参数对象,就是传递函数的调用的参数。
function foo (name) { console.log(this.a+name) } var obj = { a: 1 } foo.call(obj,'显示绑定') // 1'显示绑定' 复制代码
但是,显示绑定还是会出现绑定丢失的情况,能有办法解决吗?当然有
- 硬绑定
function foo () { console.log(this.a) } var obj = { a: 1 } var bar = function () { foo.call(obj) } var a = '会丢失吗' bar() // 1 // 现在不会丢失了 setTimeOut(bar, 1000) // 1 bar.call(window) // 1 复制代码
我们创建了函数bar(),并且在内部手动调用foo.call(obj),强制将foo的this绑定到了obj,无论后面如何调用函数bar,都会手动在obj上调用foo
- API调用的“上下文”
js语言和宿主环境中许多新的内置函数,都提供了一个可选参数,通常称为“上下文”(context),作用和bind(..)一样,确保回调函数使用指定的this
function foo (el) { console.log(el, this.id) } var obj = { id: 'awesome' } // 调用foo的同时把this绑定到obj上 [1,2,3].forEach(foo, obj) // 1'awesome' 2'awesome' 3'awesome' 复制代码
- new绑定规则
js中的new的机制和面向类的语言完全不同
我们习惯把new的函数叫做构造函数,实际上,只是使用new操作符时被调用的函数,不会实例化一个类,因为实例化的类与类之间是复制,是两位完全没关系的,但js不是。只能说这些函数是被new操作符调用的普通函数而已。
首先,我们看看new操作符会做些什么
-
创建(或者说构造)一个全新的对象
-
这个对象会被执行[[prototypt]]链(原型链)
-
新的对象会绑定到函数调用的this上
-
如果函数没有返回对象,那new表达式中的函数会自动返回这个新对象
function foo (a) { console.log(a) } var bar = new foo(1) bar() // 1 复制代码
优先级
既然this有这么多种绑定方式,肯定会存在绑定的优先级
首先,毫无疑问,默认绑定的优先级是最低的
- 那隐式绑定和显示绑定的优先级谁高?
funtion foo (){ console.log(this.a) } var obj1 = { a: 1, foo: foo } var obj2 = { a: 2, foo: foo } obj1.foo() // 1 obj2.foo() // 2 // 显示绑定改变了this的指向 obj1.foo.call(obj2) // 2 obj.foo.call(obj1) // 1 复制代码
很显然,显示绑定的优先级比隐式绑定的高。
- 隐式绑定和new绑定谁的优先级高
function foo (something) { this.a = somethig } var obj1 = { foo: foo } var obj2 = {} obj1.foo(1) console.log(obj1.a) // 1 obj1.foo.call(obj2, 2) console.log(obj2.a) // 2 var bar = new obj1.foo(3) console.log(obj2.a) // 2 console.log(bar.a) // 3 复制代码
看来new绑定的优先级是比隐式绑定高的,最后我们看一下new和显示绑定谁的优先级高,因为new和call/apply无法一起使用,所以没法通过new foo.call(obj1)来直接测试,我们选择用硬绑定来测试。
回忆一下硬绑定是如何工作的,Function.prototype.bind(..)会创建一个新的包装函数,这个函数会忽略它当前的this绑定,并把提供的对象绑定到this上。
- new和显示绑定谁的优先级高
function foo (something) { this.a = somethig } var obj1 = {} var bar = foo.bind(obj1) bar(1) console.log(bar.a) // 1 var baz = new bar(2) console.log(obj1.a) // 1 console.log(baz.a) // 2 复制代码
bar被硬绑定到obj1上,但是new bar(2),并没有将obj1.a修改成2,相反,new修改了硬绑定(到obj1的)调用bar(..)中的this。这样看来new调用的函数,新创建的this替换了硬绑定的参数,所以new的优先级是最高的。
那我们判定优先级的方法就是从优先级由高往下去判定。
绑定例外
凡事都有例外,不是所有的绑定都遵循这个规则的。
-
如果是call(null)或者apply(undefined),这里对应的其实是默认绑定,因为其实,null和undefined只是基础的数据类型,并不是对象。
-
软绑定
硬绑定可以强制绑定到指定对象,但是这大大降低了函数的灵活性,之后无法使用隐式或显示绑定修改this的指向,所以我们来实现一下软绑定
// 实现软绑定 if (!Function.prototype.softBind) { Function.prototype.softBind = function (obj) { var fn = this // 捕获所有curried参数 var curried = [].slice.call(arguments, 1) var bound = function () { return fn.apply( (!this || this === (window || global)) ? obj : this, curried.concat.apply(curried, arguments) ) } bound.prototype = Object.create(fn.prototype) return bound } } // 验证 function foo () { console.log("name: " + this.name) } var obj1 = { name: "obj1"} var obj2 = { name: "obj2"} var obj3 = { name: "obj3"} var fooOBJ = foo.softBind(obj) fooOBJ() // "obj" obj2.foo = foo.softBind(obj) obj2.foo() // "obj2" fooOBJ.call(obj3) // "obj3" setTimeOut(obj2.foo, 10) // "obj" 复制代码
可以看到,软绑定的foo()可以手动的将this绑定到obj2或者obj3,但如果应用默认绑定则将this绑定到obj1上。
- this词法
此前的4条规则使用与大部分函数,但在ES6中的箭头函数却不适用,因为箭头函数不是function关键字定义的,而是使用被称为“胖箭头”的操作符 => 定义的。箭头函数不使用this的四种标准规则,而是根据外层的作用域决定this的。所以叫做词法
function foo () { // 返回一个箭头函数 return (a) => { // this继承自foo() console.log(this.a) } } var obj1 = { a: 2 } var obj2 = { a: 3 } var bar = foo.call(obj1) bar.call(obj2) // 2 复制代码
foo内部创建的箭头函数会捕获调用时foo()的this,由于foo()的this是绑定到obj1上的,bar(只是引用箭头函数)的this也会绑定到obj1上,箭头函数的绑定无法修改。类似于
function foo () { var self = this setTimeout(function () { console.log(self.a) }, 100) } 复制代码
关于this的理解就说这么多,欢迎指正和交流。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- final关键字深入解析
- 深入分析 synchronized 关键字
- 深入理解Java中的volatile关键字
- 深入浅出Golang关键字"go"
- 深入浅出Golang关键字"go" 2
- 深入理解Swift中static和class关键字
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
用UML构建Web应用
科纳尔伦 (Conallen Jim) / 陈起 / 中国电力出版社 / 2003-11 / 39.0
用UML构建Web应用(第2版),ISBN:9787508315577,作者:(美)Jim Conallen著;陈起,英宇译;陈起译一起来看看 《用UML构建Web应用》 这本书的介绍吧!