1调用位置
调用栈:为了到达当前执行位置所调用的所有函数。
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的调用位置
2绑定规则:
2.1默认绑定:
function foo(){ console.log(this.a); } var a=2; foo();//2
函数调用时应用了this的默认绑定,因此this指定全局对象。
如果使用严格模式(strict mode),则不能将全局对象用于默认绑定,因此this会绑定到undefined。
function foo(){ //严格模式下声明 "use strict"; console.log(this.a); } var a=2; foo();//TypeError:this is undefined
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
虽然this的绑定规则完全取决于调用的位置,但是只有foo()运行在非strict mode下时,默认绑定才能绑定到全局对象。
在严格模式下调用foo()则不影响默认绑定。!!!!
function foo(){ console.log(this.a); } var a=2; (function(){ //严格模式下调用 "use strict"; foo();//2 })();
2.2隐式绑定
需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,!!不过这种说法可能会造成一些误导。
funtion foo(){ console.log(this.a); } var obj={ a:2, foo:foo } obj.foo();//2
无论是直接在obj中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。
然而,调用位置会使用obj上下文来引用函数,因此可以说函数被调用时obj对象“拥有”或者“包含”函数引用。
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
!!!!!!!!!!!!!!!!
对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。(函数直接关联层起作用)
function foo(){ console.log(this.a); } var obj2={ a:42, foo:foo } var obj1={ a:2, obj2:obj2 } obj1.obj2.foo();//42
隐式丢失:
被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否在严格模式。
function foo(){ console.log(this.a); } var obj={ a:2, foo:foo } var bar=obj.foo; var a='oops,global'; bar();//'oops,global'
虽然bar是obj.foo的一个引用,但实际上,它引用的是foo函数本身,因此此时bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
function foo(){ console.log(this.a); } function doFoo(fn){ fn(); } var obj={ a:2, foo:foo } var a='oops,global'; doFoo(obj.foo);//'oops,global';
参数传递其实就是一种隐式赋值。
function foo(){ console.log(this.a); } var obj={ a:2, foo:foo } var a="oops,global"; setTimeout(obj.foo,100);//"oops, global";
JavaScript环境中内置的setTimeout()函数实现和下面的伪代码类似:
function setTimeout(fn,delay){ //等待delay毫秒 fn();//调用位置 }
2.3显式绑定 (call,apply,bind)
function foo(){ console.log(this.a); } var obj={ a:2 } foo.call(obj);//2
通过foo.call(...),我们可以在调用foo时强制把它的this绑定到obj上。
如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(…)、new Boolean(…)或者new Number(…))。这通常被称为“装箱”。
1.硬绑定: 显式的强制绑定
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(windwo);//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; } //简单的辅助绑定函数 function bind(fn,obj){ return function(){ fn.appl(obj,arguments); }; } var obj={ a:2 }; var bar=bind(foo,obj); var b=bar(3);//2 3 console.log(b);//5
ES5提供了内置的方法Function.prototype.bind
function foo(something){ console.log(this.a, something); return this.a+something; } var obj={ a:2 }; var bar=foo.bind(obj); var b=bar(3); //2 3 console.log(b); //5
bind(…)会返回一个硬编码的新函数,它会把你指定的参数设置为this的上下文并调用原始函数。
2.API调用的“上下文”
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
2.4new绑定
包括内置对象函数在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。注:实际上并不存在所谓的“”构造函数“”,只有对于函数的“”构造调用“”。
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
1.创建(或者说构造)一个全新的对象。
2.这个新对象会被执行 [[Prototype]]连接
3.这个新对象会绑定到函数调用的this。
4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
function foo(a){ this.a=a; } var bar=new foo(2);//函数没有返回其他对象,new表达式中的函数调用会自动返回新对象。 console.log(bar.a)//2
使用new来调用foo(…)时,我们会构造一个新对象并把它绑定到foo(…)调用中的this上。
3优先级:
判断this:
1.函数是否在new调用(new绑定)?如果是的话this绑定的是新创建的对象。 var bar=new foo();
2.函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。 var bar=foo.call(obj2);
3.函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。 var bar=obj1.foo();
4.如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。 varbar=foo();
隐式绑定和显示绑定比较显示绑定的优先级更高。例:
function foo(){ console.log(this.a); } var obj1={ a:2, foo:foo } var obj2={ a:3, foo:foo } obj1.foo();//2 obj2.foo();//3 obj1.foo.call(obj2);//3 obj2.foo.call(obj1);//2
new绑定比隐式绑定的优先级高。例:
function foo(something){ this.a=something; } var obj1={ foo:foo } var obj2={} obj1.foo(2); console.log(obj1.a);//2 obj1.foo.call(obj2,3); console.log(obj2.a);//3 var bar=new obj1.foo(4); console.log(obj1.a);//2 console.log(bar.a);//4
new和bind比较:
function foo(something){ this.a=something; } var obj1={}; var bar=foo.bind(obj1); bar(2); console.log(obj1.a);//2 var baz=new bar(3); console.log(obj1.a);//2 console.log(baz.a);//3
4绑定例外;
1被忽略的this:
如果把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
function foo(){ console.log(this.a); } var a=2; foo.call(null);//2
应用:
apply(…)来“展开”一个数组,并当做参数传入一个函数。
bind(…)可以对参数进行柯里化(预先设置一些参数)。
function foo(a,b){ console.log("a: "+a+",b: "+b); } //把数组“展开”成参数 foo.apply(null,[2,3]);//a:2,b:3 //使用bind(…)进行柯里化 var bar=foo.bind(null,2); bar(3);//a:2,b:3
更安全的this:
一种“更安全”的做法是传入一个特殊的对象,把this绑定到这个对象不会对你的程序产生任何副作用。
在JavaScript中创建一个空对象最简单的方法就是Object.create(null)。Object.create(null)和{}很像,但是并不会创建Object.prototype这个委托,所以它比{}“更空”:
function foo(a,b){ console.log("a:"+a+",b:"+b); } //我们的DMZ空对象 var ♓=Object.create(null); //把数组展开成参数 foo.apply(♓,[2,3]); //使用bind(…)进行柯里化 var bar=foo.bind(♓,2); bar(3);//a:2,b:3
2间接引用:
function foo(){ console.log(this.a); } var a=2; var o={a:3,foo:foo}; var p={a:4}; o.foo();//3 (p.foo=o.foo)();//2
赋值表达式p.foo=o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo()。根据我们之前所说的,这里会应用默认绑定。
注意:
对于默认绑定来说,决定this绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式,this会被绑定到undefined,否则this会被绑定到全局对象。
3软绑定
可以给默认绑定指定一个全局对象和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===(windwo || global))?obj:this, curried.concat.apply(curried,arguments) ); }; bound.prototype=Object.create(fn.prototype); return bound; }; }
softBind实现软绑定功能:
function foo(){ console.log("name: "+this.name); } var obj={name:"obj"}, obj2={name:"obj2"}, 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,100);//obj
5this词法:
箭头函数
箭头函数并不是使用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 不是3 绑定到创建箭头函数时的this上,绑定无法被修改
foo()内部创建的箭头函数会捕获调用是foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new 也不行!)
箭头函数最常用于回调函数中,例如事件处理器或者定时器。
function foo(){ setTimeout(()=>{ //这里的this在词法上继承自foo() console.log(this.a); },100); } var obj={ a:2 }; foo.call(obj);//2