- js 函数中 this 指向并不是在函数定义的时候确定的,而是在函数调用的时候确定的,所以函数的调用方式决定 this 的 指向。this 永远指向最后调用它的那个对象。
- 普通的函数有 3 种调用方式:直接调用,方法调用和 new 调用。除此之外还有通过 bind() 将函数绑定到对象之后再调用,通过 call()、apply() 进行调用等。es6 引入箭头函数之后,其 this 指向又有所不同。
- this 既不是指向函数自身也不指向函数作用域,this 实际上是在函数被调用是发生绑定的,它的指向完全取决于函数在哪里调用。
用代码解释:this 永远指向最后调用它的那个对象
1 | var a = { |
this 执行会有不同,主要是几种在这几个场景中:
- 作为构造函数执行,构造函数中
- 作为对象属性上执行,上述代码中的 a.fn()
- 作为普通函数执行,上述代码中的 fn1()
- 用于 bind,call,apply 上述代码中 a.fn.call({name:’B’})
默认绑定
在 JavaScript 中,最常用的函数调用类型就是独立函数调用。如果在调用函数的时候,函数不带任何修饰,也就是光秃秃的调用,那就会应用默认绑定规则,默认绑定的指向的是全局作用域。
1 | <script type="text/javascript" charset="utf-8"> |
a()
函数在全局作用域中被调用,因此第 1 句中的 this
就绑定在了全局对象上。b()
函数在 a()
函数里面调用,即使这样第二句中的 this
指代的仍然是全局对象,即使 a()
函数设置了 name
属性。这就是默认绑定规则,它是 js
中最常见的一种函数调用模式,this
的绑定规则也是最简单的一种,就是绑定在全局作用域上
** 但是如果使用了严格模式,则 this
不能绑定到全局对象,在严格模式下,把 this
绑定到全局对象上时,实际上绑定的是 underfined
,因此上面的代码会报错 **
1 | var name = 'g' |
1 | var name = 'g' |
1 | var name = 'g' |
1 | var name = 'g' |
1 | var name = 'g' |
1 | var name = 'g' |
所以通过上面的实验可以充分的得出一个结论,this 确实不是指向的当前作用域的,this 和词法作用域是完全不同的。
隐式绑定
当函数在调用时,如果函数有所谓的 “ 落脚点 ”, 即有上下文对象时,隐式绑定规则会把函数中的 this 绑定到这个上下文对象。如果觉得上面这段话不够直白的话,还是来看代码。
1 | function say() { |
obj1 , obj2 就是所谓 say 函数的落脚点,专业一点的说法就是上下文对象,给函数指定了这个上下文对象的时,函数内部的 this 自然指向了这个上下文对象。这是很常见的函数调用模式
** 对象属性引用链中只有最顶层或者说是最后一层会影响调用位置 **
1 | function foo() { |
隐式绑定上下文的时丢失上下文
1 | function say() { |
由于在 js 中,函数是对象,对象之间是引用传递,而不是值传递,因此第一句代码只是 alias = obj.say= say, 也就是 alias = say,obj.say 只是起了一个桥梁的作用,alias 最终引用的是 say 函数的地址,而与 obj 这个对象无关了,这就是所谓的 “ 丢失上下文 ”。最终执行 alias 函数只不过简单的执行了 say 函数
1 | function foo() { |
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值。回调函数丢失 this 是很常见的情况,除此之外回调函数的函数可能会修改 this,实际上你无法控制回调函数的执行方式,因此就没办法控制会影响绑定的调用位置
显示绑定
显示绑定,显示的将 this 绑定到一个上下文,js 中,提供了三种显示绑定的方法,apply,call , bind。apply 和 call 的用法基本相似,他们之间的区别是:
apply(obj,[arg1.arg2,…]); 被调用函数的参数以数组的形式给出
call(obj,arg1,arg2,arg3,…); 被调用函数的参数依次给出
bind 函数执行后,返回的是一个新函数。
** 硬性绑定的应用场景:**
1 | function foo(something) { |
另一种使用方法是创建一个
1 | function speak() { |
带参数:
1 | // 带参数 |
因此可以看出,apply , call 的作用就是给函数绑定一个执行上下文,且是显示绑定的。因此函数内的 this 自然而然就绑定在了 call 或者 apply 所调用的对象上。而 bind 函数,则返回一个绑定了制定的执行上下文的新函数:
1 | // 带参数 |
所以 bind 方法只是返回了一个新的函数,这个函数内的 this 指定了执行上下文,而返回这个新函数可以接受参数。
new 绑定最后要将的一种 this 绑定规则,是指通过 new 操作符调用构造函数时发生的 this 绑定。构造函数也仅仅是普通函数而已,只不过构造函数以答谢字母开头,也只不过它可以通过 new 操作符调用而已。
1 | function Person(name, age) { |
定义的 Person 函数,既可以普通调用,也可以构造函数的形式上的调用,当普通函数调用时,则按正常的函数执行,输出一个字符串。如果通过一个 new 操作符,则构造了一个新的对象。
两种调用方式的不同之处:
- ** 普通函数调用时,应用启用默认绑定规则 **,this 绑定在全局上,此时全局对象上回分别增加 name 和 age 两个属性。
- 当通过 new 操作符时,会产生一个新对象,并且会把构造函数内的 this 绑定到这个对象上,事实上,在 js 中,使用 new 来调用函数,会自动执行下面的操作
- 创建一个全新的对象。
- 这个新对象或被执行原型链连接
- 这个新对象会绑定到函数调用的 this
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象
四种绑定的优先级
这四种绑定规则基本上涵盖了所有函数调用情况。但是同时应用了这四种规则中的两种甚至更多,又该是怎么样的一个情况,或者说这四种绑定的优先级顺序又是怎么样的。** 默认优先级最低 < 隐式绑定第二 < 显示绑定第三 < new 绑定最高 **
箭头函数中的 this
箭头函数的 this 是根据外层的 ( 函数或则全局 ) 作用于来决定的,函数体内的 this 对象指的是定义时所在的对象,而不是之前介绍的调用时绑定的对象。箭头函数的 this 始终指向函数定义时的 this,而非执行时。箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined。
1 | var a = 1 |
箭头函数的 this 强制性的绑定在了箭头函数定义时所在的作用域,而且无法通过显示绑定,如 apply,call 方法来修改
1 | function Person(name, age) { |
以上就是 javascript 中所有 this 绑定的情况,在 es6 之前,前面所说的四种绑定规则可以涵盖任何的函数调用情况,es6 标准实施以后,对于函数的扩展新增了箭头函数,与之不同的是,箭头函数的作用于位于箭头函数定义时所在的作用域。
1 | var obj = { |
cool() 函数丢失了同 this 之间的绑定, var self = this 这种方案可以圆满解决了理解和正确使用 this 绑定的问题。
1 | function Obj(name) { |
1 | function identify() { |
1 | function foo(num) { |
执行 foo.count = 0 时,的确向函数对象 foo 添加了一个属性 count。但是函数内部代码 this.count 中的 this 并不指向那个函数,所以虽然属性名相同,跟对象却并不相同。这段代码在五一中创建了一个全局变量 count。
** 匿名函数无法指向自身 **,arguments.callee 是唯一一种从匿名函数对象中引用自身的方法,已被弃用。,然而更好的是避免使用匿名函数,至少在需要自引用时使用时使用具名函数
*this 在任何情况下都不指向函数的词法作用域 **,作用域 “ 对象 ” 无法通过 js 代码来访问,它存在于 js 引擎内部 \*
1 | function foo() { |
试图用 this 联通 foo() 和 bar() 的词法作用域,从而让 bar 可以 i 访问 foo() 变量的 a,这是不可能实现的,** 不能使用 this 来引用一个词法作用域内部的东西 **,每当想要把 this 和此法作用域混用的时候,一定要提醒自己,这是无法实现的
综合题
1
1 | var names = '宋伟老师' |
2
1 | var big = '万达老师' |
3
1 | function a(a,b,c){ |