this
this到底是什么?
this是在运行时绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个记录(有时候也称为执行上下文)。这个记录会包含函数函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在函数执行的过程中用到。
如果不使用this,那就需要显示传入一个上下文对象,this提供了一种更优雅的方式来隐式“传递”一个对象的引用。
仅仅是因为无法猜对this的用法,就放弃学习this而去使用词法作用域,就不能算是一种很好的解决办法。
声明在全局作用域中的变量就是全局对象的一个同名属性。它们本质上就是同一个东西,并不是通过复制得到的。
this的绑定规则完全取决于调用位置。注:只有在non-strict mode下时,默认绑定才能绑定到全局对象。
隐式丢失
一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否是严格模式。
显示绑定
call(...)和apply(...)方法,它们的第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个this。
如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当做this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String()...)。这通常被称为装箱。
由于硬绑定是一种非常常用的模式,所以在es5中提供了内置的方法Function.prototype.bind。
function foo(sth){ return this.a + sth } function bind(fn,obj){ return function(){ return fn.apply(obj,arguments) } } var obj = { a:'zjy' } var bar = bind(foo,obj) var b = bar(' work hard') console.log(b)
bind(...)会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。
判断this
- 函数是否在new中调用?如果是的话,this绑定的是新创建的对象。
- 判断函数是否通过call、apply或者硬绑定调用?如果是的话,this绑定的是指定的对象。
- 函数是否在某个上下文对象中调用?如果是的话,this绑定的是那个上下文对象。
- 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。
被忽略的this
如果把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
软绑定
硬绑定这种方式可以把this强制绑定到指定的对象(除了使用new时),防止函数调用应用默认绑定规则。问题在于,硬绑定会大大降低函数的灵活性,使用硬绑定之后就无法使用隐式绑定或者显示绑定来修改this。
如果可以给默认绑定指定一个全局对象和undefined以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显示绑定修改this的能力。
意思就是说:使用硬绑定之后就无法使用隐式绑定或者显示绑定来修改this,那么就不使用硬绑定,实现一个软绑定(softBind),其功能就是给默认绑定指定一个全局对象和undefined以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显示绑定修改this的能力。
Function.prototype.softBind = Function.prototype.softBind || function(obj,...rest){ let fn = this let bound = function(...args){ return fn.apply( (!this || this === (window || global)) ? obj : this, [...rest,...args] ) } bound.prototype = Object.create(fn.prototype) return bound }
箭头函数并不是使用function关键字定义的,而是使用被称为”胖箭头“的操作符 => 定义的。箭头函数不适用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。
- 箭头函数的外层如果有普通函数,那么箭头函数的 this 就是外层普通函数的this
- 箭头函数的外层如果没有普通函数,那么箭头函数的 this 就是全局变量
- 当对箭头函数使用 call 或 apply 方法时,只会传入参数并调用函数,并不会改变箭头函数中 this 的指向;
- 当对箭头函数使用 bind 方法时,只会返回一个预设参数的新函数,并不会绑定新函数的 this 指向。
经典箭头函数面试题
function foo(n) { var f = () => arguments[0] + n; return f(); } let res = foo(2); console.log(res); // 4
function A() { this.foo = 1 } A.prototype.bar = () => { console.log(this.foo) } let a = new A() a.bar() //undefined
let res = (function pt() { return (() => this.x).bind({ x: 'inner' })(); }).call({ x: 'outer' }); console.log(res) // 'outer'
window.name = 'window_name'; let obj1 = { name:'obj1_name', print:() => console.log(this.name) } let obj2 = {name:'obj2_name'} obj1.print() // 'window_name' obj1.print.call(obj2) // 'window_name'
let obj1 = { name:'obj1_name', print:function(){ return () => { console.log(this.name) } } } let obj2 = {name:'obj2_name'} obj1.print()() // 'obj1_name obj1.print().call(obj2) // 'obj1_name' obj1.print.call(obj2)() // 'obj2_name'