this的指向在函数定义时是确定不了的,只有在函数执行时才能确定,this最终指向调用它的对象。
this在js中主要有四种用法:
1、作为普通函数使用
2、作为对象方法来使用
3、call和apply
4、作为构造函数来使用
1、作为普通函数使用
var name='window'; function s(){ var name='myself'; console.log(this.name);//window } s();
2、作为对象方法来使用
var name='window'; var obj={ name:'obj', sayName:function(){ console.log(this.name); } } obj.sayName();//obj;
这个很简单,this指向自己,所以this.name就用hello;
(在全局里面this指向window,在某个对象里面this指向该对象,在闭包里面this指向window)
var user="the Window"; var box={ user:'the box', getThis:function(){ return this.user; }, getThis2:function(){ return function (){ return this.user; } } }; alert(this.user);//the Window alert(box.getThis());//the box alert(box.getThis2()());//the Window (由于使用了闭包,这里的this指向window) alert(box.getThis2().call(box));//the box 对象冒充(这里的this指向box对象)
3、call和apply
所有函数对象都有两个方法:apply和call,这两个方法可以让我们构建一个参数数组传递给调用函数,也允许我们改变this值。
使sayName中的this指向b,改变this的指向 var name='window'; var obj={ name:'obj', sayName:function(){ console.log(this.name); } } var b={name:'abcd'}; //改变this指向 var newobj=obj.sayName; newobj();//将this指向全局 newobj.call(b);//abcd 将this指向b 改变this的指向并且执行调用函数
4、作为构造函数来使用
function test(){ this.name=1; } var myobj=new test(); console.log(myobj.name);
练习题
在执行person1.sayName()时,this指向person1对象
在执行person2.sayName()时,sayName()方法并没有执行,而是将sayName()赋值给fun变量,fun()是普通函数调用模式,this指向window,所以输出全局name
执行console.log(b.n)时,b对象有自己的属性n值
执行console.log(c.n)时,c对象没有自己的属性n值,会向上查找,找的A对象中的属性n值
var getColor=test.getColor相当于把方法函数赋值给全局变量,
故getColor()中的this指向window
二、改变this指向的方式
以下属于函数的方法
改变this的指向并且执行调用函数
(1)通过一个对象的方法来定义函数,并用该对象调用方法。
var name='window'; var obj={ name:'obj', sayName:function(){ console.log(this.name); } } var b={}; b.x='is o' b.fun=obj.sayName; b.fun();
call()方法存在于Function的原型上,Function.prototype.call(),因此每个函数都可以通过原型链继承下来。
属于函数的方法,只有函数对象可以调用
相同点:两个方法产生的作用是完全一样的,都用来改变当前函数调用的对象。
不同点:调用的参数不同,比较精辟的总结:
foo.call(this,arg1,arg2,arg3) == foo.apply(this, arguments)==this.foo(arg1, arg2, arg3)
1.call的使用
call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
window.color="red"; var o={ color:"blue" }; function sayColor(){ console.log(this.color); } sayColor();//指向window sayColor.call(this) ; //指向自己 sayColor.call(window); //red sayColor.call(o); //blue
解析:最后一个之所以输出的是blue,是因为call()方法的第一个参数指的是在其中运行函数的作用域,所以在sayColor.call(o)中,它把函数的作用域定在了对象o中,所以函数sayColor()中的this.color即为o.color,因此输出的是对象o的color的值bule.
2.apply()
apply与call的功能几乎一样,第一个参数意义都一样,只是第二个参数有点不同apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,call从第二个参数开始,依次传值给调用函数的参数
call、apply与bind的区别:前两个可以自动执行,bind只创建一个新的函数,不会自动执行,需要手动调用。
(3)bind()
bind()会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。如
var color='red'; var o1={ color:'blue' }; var o2={ color:'yellow' }; function sayColor(){ console.log(this.color); } //call()自动执行 sayColor.call(o1);//blue //bind()需手动执行 var say=sayColor.bind(o2); say();//yellow
bind()除了改变函数this指向的功能,还有柯里化的功能,即把一个函数拆成多个单元。
柯里化是指这样一个函数,他接收函数A作为参数,运行后能够返回一个新的函数,并且这个新的函数能够处理函数A的剩余参数。
function add(a,b,c){ console.log(a+b+c); } var func=add.bind(undefined,100);//第一个参数为undefined、null指不需要改变this func(1,2);//103 绑定时传入一个参数100,在调用时传入第二个和第三个参数 var func2=func.bind(undefined,200); func2(10);//310 之前绑定a=100,所以200绑定到b上
为什么要使用柯里化呢?
比如需要写一个获取一套配置的函数,不同页面下的配置可能是不同的,在同一模块下,其中有些参数是相同的,不同页面获取时只需要传入otherOptions即可,把一个函数拆分成子函数,使代码重用。
bind与new
function foo(){ this.b=100; return this.a; } var func=foo.bind({a:1}); console.log(func());//1 console.log(new func());//{b:100}
使用new 函数时,函数中的return除非是对象,否则会把this作为返回值,并且this会被初始化一个默认的空对象,对象的原型就是foo.prototype,所以new一个对象时,即使方法以bind了一个对象,this仍指向原函数。
手写bind()函数 柯里化
结合 (this与指向对象颜色相同)
function foo(){ this.b=100; return this.a; } var func=foo.bind({a:1}); console.log(func());//1 console.log(new func());//{b:100}
考虑到构造函数的情况:
Function.prototype.bind=function(context){//context指bind函数的第一个参数var args=Array.prototype.slice.call(arguments,1); var self=this;//this仍然指调用bind()的函数对象 var fun=function(){}; var fBound=function(){ var innerArgs=Array.prototype.slice.call(arguments);//获取内部函数的传入的所有参数 var outerArgs=args.concat(innerArgs); return self.apply(this instanceof fun?this:context,outerArgs); };//self就是相当于调用的foo() 该处this指func()执行时的this即window,所以fun=oThis //当new func()时this指向foo(),所以this instanceof fun为true //Array.prototype.slice.call(arguments)是后面传的参数b和c,用拼接的方式获取a,b,c完整的参数 fun.prototype=this.prototype; fBound.prototype=new fun();//以上两句使fBound是fun的实例 return fBound;//fBound作为返回值,相当于生成的函数对象func };
知识点:
(1) Array.prototype.slice.call(arguments)能够将类数组对象转换为数组。
(2)Array.prototype.slice.call(arguments,1);//截取,获取外部函数的第一个参数之后的所有参数,参数1表示被返回的数组包含从第二个参数开始的所有参数,即去除函数自己的方法名
(3)this instanceof fun?this:context //这段代码判断通过bind方法绑定得到的函数,是直接调用还是用构造函数通过new来调用。
(4)通过设置一个中转构造函数fun,使绑定后的函数与调用bind()的函数处于同一原型链上,用new操作符调用绑定后的函数,返回的对象也能正常使用instanceof。