this指向及改变this指向的方法

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();

(2)call()和apply()

  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毫无影响。

 

posted @ 2018-03-26 16:29  安xiao曦  阅读(544)  评论(0编辑  收藏  举报