为何要使用this关键字
看个例子bash
function indetify() {
retun this.name.toUpperCase()
}
var obj = {
name: 'zz'
}
indetify.call(obj) // 'ZZ'
复制代码
这里函数identify中的this指向变量obj,若是不使用this的话,想实现这种效果,就须要显示的传入上下文对象,例如app
function indetify(context) {
retun context.name.toUpperCase()
}
var obj = {
name: 'zz'
}
indetify(obj) // 'ZZ'
复制代码
当模式愈来愈复杂的时候,显示的传递上下文对象就会变得不太好管理,而this提供了一种更优雅的方式,隐式的"传递"一个对象的引用,所以能够将API设计的更加简洁而且易于复用。ide
对于this的误解
this究竟是什么
this是在运行的时候被绑定的,并非编写的时候,它的上下文取决于函数调用的各类条件。当一个函数被调用时,会建立一个活动记录(即执行上下文),这个记录包含函数的调用栈(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的绑定规则了。ui
this绑定规则
function foo () {
console.log(this.a)
}
var a = 1
foo() // 1
复制代码
由于foo()是直接不带任何修饰的函数引用进行调用的,因此只能使用默认绑定。非严格模式下指向window,严格模式下绑定到undefinedthis
分析隐式绑定时,必须在一个对象内部包含一个指向函数的属性,经过这个属性间接引用函数
spa
function foo () {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
obj.foo() // 2
复制代码
首先要声明的是,不管foo函数是先声明仍是直接在obj对象中定义,这个函数严格来讲,都不属于obj对象。prototype
隐式绑定会遇到一个问题,就是会丢失绑定对象,也就是会应用默认绑定。好比设计
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
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'
复制代码
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
复制代码
很显然,显示绑定的优先级比隐式绑定的高。
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上。
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上。
此前的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的理解就说这么多,欢迎指正和交流。