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);
new操作符具体干了什么呢?
var Func=function(){ };
var f=new Func ();
new共经过了4几个阶段:
1:创建一个空对象 var obj=new Object();
2:设置原型链,让空对象的__proto__指向函数的原型prototype。 obj.__proto__= Func.prototype;
3:让Func()中的this指向空对象obj,并执行Func()的函数体;var result =Func.call(obj);
4:判断Func()的返回值类型
如果是值类型,返回obj。如果是引用类型,就返回这个引用类型的对象。
使用new来创建对象时,如果return的是非对象(数字、字符串、布尔类型、null等)或没有返回值时会忽略该返回值,返回的是新创建的对象 ;如果return的是对象,则返回该对象。
(1)返回的是非对象 function Person(name){ this.name=name; return name; } var p=new Person('tom'); console.log(p);//Person {name: "tom"} (2)返回的是对象 function Person(name){ this.name=name; return {'name1':name}; } var p=new Person('tom'); console.log(p);//{name1: "tom"}
构造函数与普通函数的区别
构造函数也是一个普通函数,创建方式和普通函数一样。
(1)调用方式:构造函数在调用时使用new关键字 new Fun();普通函数调用则直接调用fun()
(2)this指向:在构造函数内部,this指向的是构造出来的新对象;在普通函数内部this指向window全局对象
(3)return返回值:构造函数会默认返回this,也就是新的实例对象;普通函数如果没有return,返回undefined,如果使用了return,那么返回值会根据return的类型而判断。
练习题
在执行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只创建一个新的函数,不会自动执行,需要手动调用。
call()会比apply()执行速度快,主要是因为在apply方法中,对数组参数有多次判断,执行步骤比call多。
(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指向的功能,还有柯里化的功能,即把一个函数拆成多个单元。
柯里化就是把一个多参数的函数,转换为单参数函数。只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
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仍指向原函数。
拓展:
1、手写bind()函数
考虑到构造函数的情况:
Function.prototype.bind2=function(context){ var self=this; if(typeof this !='function'){//要求调用bind的必须是函数 throw new Error("Function.prototype.bind") } var oArgs=Array.prototype.slice.call(arguments,1); var fNOP=function(){} var fBound=function(){ var iArgs=Array.prototype.slice.call(arguments); var args=oArgs.concat(iArgs); //当作为普通函数时,this指向window,self指向绑定函数,此时结果为false;当结果为false时,令this指向context //如果bind后的新函数要做new操作,那么this指向新函数,所以this instanceof self==true return self.apply(this instanceof fBound?this:context,args); } fNOP.prototype=this.prototype; fBound.prototype=new fNOP();//用于构造函数时,使fBound是构造函数的实例 return fBound; } function fun(name){ this.name=name return this.name; } var obj={ name:'obj' } var f=fun.bind2(obj,'an'); console.log(new f());
知识点:
(1) Array.prototype.slice.call(arguments)能够将类数组对象转换为数组。
(2)Array.prototype.slice.call(arguments,1);//截取,获取外部函数的第一个参数之后的所有参数,参数1表示被返回的数组包含从第二个参数开始的所有参数,即去除函数自己的方法名
2、实现一个call()函数
1.将函数对象设置为对象的属性 2.执行这个函数 3.删除这个函数 Function.prototype.myCall=function(context){ //判断是否传入指定的对象 context=context||window; //把调用的函数添加到对象中 context.fn=this; var arg=[...arguments].slice(1); //执行函数 let res=context.fn(arg); //执行函数后从对象中删除函数 delete context.fn; return res; } //执行过程类似:obj.f=fun;obj.f();
3、实现一个apply()函数
Function.prototype.myApply=function(context,arr){ context=context||window; context.fn=this; if(arr&&!Array.isArray(arr)){ throw new TypeError('not funciton'); return; } let res=context.fn(arr); delete context.fn; return res; }
4、模拟new的实现过程
//把构造函数作为第一个参数传入 function objectFactory(){ var obj=new Object(); //取出第一个参数,就是我们要传入的构造函数,因为shift会改变原数组,所以arguments会被去除第一个参数 var fun=Array.prototype.shift.call(arguments); //将obj的原型指向构造函数,这样obj就可以访问到构造函数原型中的属性了 obj.__proto__=fun.prototype; //改变构造函数this的指向 var res=fun.apply(obj,arguments); return typeof res=='object'?res:obj;//判断函数的返回值,如果返回值为对象,则返回这个对象,否则返回新创建的对象 } var q=objectFactory(func,'an');
5、函数柯里化
var newArgs=[]; function curry(fn,args){ var length=fn.length;//指fn总共的参数个数 var args=args||[]; return function(){ newArgs=args.concat(Array.prototype.slice.call(arguments)); if(newArgs.length<length){ return curry.call(this,fn,newArgs); } else{ return fn.apply(this,newArgs); } } } function multiFn(a,b,c){ return a*b*c; var multi=curry(multiFn); console.log(multi(2,3)(4)); console.log(multi(2)(3)(4));
三、箭头函数中的this
由于箭头函数不绑定this,他会捕获其所在(即定义位置)上下文的this值,作为自己的this。
所以call()、apply()、bind()方法对于箭头函数来说只是传入参数,对他的this毫无影响。