在javascript中,每一个执行上下文能够抽象成一组对象javascript
而this
是与执行上下文相关的特殊对象,任何对象均可以用做this
上下文的值,一个重要的注意事项就是this
值是执行上下文的属性,但不是变量对象的属性。这样的话,与变量相反,this
值不会参与标识符解析,即在访问代码时,他的值直接来自执行上下文,也没有任何做用域链查找,在进入上下文中,this
只能肯定一次。因此this
的值是和其所处的上下文环境有关系。java
在历届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的调用位置
首先要介绍的最多见的函数调用类型:独立函数调用。能够把这条规则看做是没法应用其余规则时的默认规则浏览器
function foo(){ console.log(this.a); } var a = 2; foo(); //2
由于foo()
在全局执行上下文中调用,因此this指向全局变量
若是使用严格模式,则不能将全局对象用于默认绑定,所以this会绑定到undefined:app
function foo(){ "use strict" console.log(this.a); } var a = 2; foo(); //error
虽然this
的绑定规则彻底取决于调用位置,可是只有foo()
运行在非严格模式下时。默认绑定才能绑定到全局对象;在严格模式下调用foo()
则不影响默认绑定函数
function foo(){ console.log(this.a); } var a = 2; (function(){ foo(); //2 })()
function foo(){ console.log(this.a); } var obj = { a: 42, foo: foo }; obj.foo();
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象,由于调用foo()
时this
被绑定到obj,所以this.a
和obj.a
是同样的
对象属性引用链中只有上一层或者说最后一层在调用位置起做用oop
function foo(){ console.log(this.a); } var obj2 = { a: 42, foo: foo } var obj1 = { a: 2, obj2: obj2 } obj1.obj2.foo(); //42
被隐式绑定的函数会丢失绑定对象,也就是说他会应用默认绑定,从而把this绑定到全局对象或者undefined
this
function foo(){ console.log(this.a); } var obj = { a:2, foo: foo }; var bar = obj.foo; //函数别名 var a = "oops, global"; //a是全局对象的属性。 bar(); //"oops, global"
虽然bar
引用了obj.foo
这个引用,但实际上他引用的是foo
函数的自己。也就是说bar()
是一个在全局上下文中调用的函数,所以this
指向了全局对象。
这种情形页出如今参数传递中。编码
function foo(){ console.log(this.a); } function doFoo(fn){ fn(); } var obj = { a: 2, foo: foo }; var a = "oops, global"; //a是全局对象的属性 doFoo(obj.foo);
参数传递其实就是一种隐式赋值,所以咱们传入函数也会被隐式赋值。spa
在分析隐式绑定时,咱们必须在一个对象内部包含一个指向函数的属性,并经过这个属性间接引用函数,从而把this
间接(隐式)绑定到这个都对象上。
当咱们不想再对象内部间接包含引用函数,而像在某个对象上强制调用函数。咱们能够用javascript中内置的apply
和call
的方法来实现,这两个方法的第一个参数是一个对象,是给this
准备的,接着再调用函数时将其绑定到this
。由于你能够直接指定this
的绑定对象,所以咱们称之为显式绑定。
function foo(){ console.log(this.a); } var obj = { a: 2 } foo.call(obj); //2
经过foo.call(...)
咱们能够在调用foo时强制把他的this
绑定到obj
上。若是你传入一个原始值(字符串类型,布尔类型或者数字类型)来看成this
的绑定对象,这个原始值会被转换成他的对象形式,也就是“装箱”
咱们经过显示绑定的变种解决绑定丢失的问题
function foo(){ console.log(this.a); } var obj = { a: 2 } var bar = function(){ foo.call(obj); }; bar(); //2 setTimeout(bar, 100); //2 //硬绑定的bar不可能在修改他的this bar.call(window); //2
硬绑定的典型应用场景就是建立一个包裹函数,负责接收参数并返回值
function foo(something){ console.log(this.a, something); return this.a + something; } var obj = { a: 2 } var bar = function(){ return foo.apply(obj, arguments); } var b = bar(3); //2. 3 console.log(b); //5
另外一种方式则是建立一个能够重复使用的辅助函数
function foo(something){ console.log(this.a, something); return this.a + something; } var obj = { a: 2 } function bind(fn, obj){ return function(){ return fn.apply(obj, arguments); } } var bar = bind(foo, obj); var b = bar(3); //2 3 console.log(b); //5
以前介绍了apply
和call
能够改变this
的指向,如今来说讲他们的区别以及ES5新增的方法bind
apply
和call
之间最主要的区别在于传入参形式的不一样。他俩的第一个参数都是指定了函数体内的this
指向。
而第二个参数apply
传入为一个带下标的集合,这个集能够为数组,也能够为类数组。apply
方法把这个集合中的元素做为参数传递给被调用的函数
var func = function(a,b,c){ alert([a, b, c]); //1 2 3 } func.apply(null, [1, 2, 3])
call
传入的参数数量不固定,跟apply
相同的是,第一个参数也是函数体内的this
指向,从第二个参数开始日后,每一个参数依次传入函数。
var func = function(a, b, c){ alert([a, b, c]); //1 2 3 } func.call(null, 1, 2, 3);
当使用call
或者apply
的时候,若是咱们传入的第一个参数为null,函数体内的this
会指向默认的宿主对象,在浏览器是window
大多数的高级浏览器已经实现了bind
方法用来指定函数内部的this
的指向
function foo(){ console.log(this.a); } var obj = { a: 2 } var bar = foo。bind(obj); bar(); //2
bind(..)
会返回一个硬编码的新函数,他会把你指定的参数设置为this
的上下文并调用原始函数
咱们也能够用apply
模仿一个bind
Function.prototype.bind = function(){ var self = this; var context = Array.prototype.shift().call(arguments); var args = Array.prototype.slice().call(arguments); return function(){ this.apply(context, Array.prototype.concat.call(args, Array.prototype.shift().call(arguments);)) } } var obj = { name: 'foo' }; var func = function(a, b, c, d){ console.log(this.name); console.log([a, b, c, d]) // }.bind(obj, 1, 2); func(3,4);
在javascript中,构造函数只是一些使用new
操做符时调用的函数,它们并不会属于某个类,也不会是实例化一个类。
使用new
来调用函数,或者说发生构函数调用时,会自动执行下面的操做
1.建立(或者说构造)一个去全新的对象。
2.这个新对象会被执行[[prototype]]链接
3.这个新对象会绑定到函数调用的this
4.若是函数没有其余返回对象,那么new
表达式中的函数调用会自动返回这个新对象。
function foo(a){ this.a = a; } var bar = new foo(2); console.log(bar.a); //2
前面咱们说过硬绑定这种方式绑定以后没法修改this
值,会下降函数灵活性。
若是能够给默认绑定指定一个全局对象和undefined
之外的值,那就能够实现和硬绑定相同过的效果,同hi是保留隐式绑定或者显示绑定修改this
的能力
Function.prototype.softbind = function(){ var self = this; var context = [].shift.call(arguments); var args = [].slice.call(arguments); var bound = function(){ return self.apply((!this|| this === (window || global))?obj:this, [].concat.call(args, [].slice.call(arguments))); } bound.prototype =Object.create(self); return bound; } function foo(){ console.log(this.name); } var obj = { name: 'obj' } var obj1 = { name: 'obj1' } var fooobj = foo.softbind(obj, 1); fooobj(); //name: obj obj1.foo = foo.softbind(obj); obj1.foo(); //name: obj1 setTimeout(obj1.foo, 10); //name: obj
能够看到,软绑定版本的foo()能够手动讲this
绑定到obj1上,但若是应用默认绑定,则会将this
绑定到obj上
ES6中出现了不一样与以上四种规则的特殊函数类型: 箭头函数。它是根据外层(外层或者全局)做用域来决定的
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 不是3!
foo()
内部建立的箭头函数会捕获调用时foo()
的this
,因为foo()
的this
绑定到obj1,bar
的this
也会绑定到obj1
,箭头函数的绑定没法被更改。