彻底深刻理解js之this

2018-07-14  本文已影响5人  幸宇

http://0313.name/archives/77#more-77
1.this的指向是在函数被调用的时候确定的;
2.在函数执行过程中不可修改this的指向;

var a = 10;
var obj = {
    a: 20
}

function fn () {
    this = obj; // 这句话试图修改this,运行后会报错
    console.log(this.a);
}

fn();

两种情况的this指向
1.全局对象中的this
全局环境中的this指向它本身

// 通过this绑定到全局对象
this.a2 = 20;

// 通过声明绑定到变量对象,但在全局环境中,变量对象就是它自身
var a1 = 10;

// 仅仅只有赋值操作,标识符会隐式绑定到全局对象
a3 = 30;

// 输出结果会全部符合预期
console.log(a1);
console.log(a2);
console.log(a3);

2.函数中的this
结论:在一个函数上下文中,this由调用者提供,由调用函数的方式来决定,

如果调用者函数在一个对象中,被某个对象所拥有,那么该函数在调用时,内部的this指向该对象;

但若函数独立调用,在严格模式下use strict,该this指向undefined;非严格模式下自动指向全局对象;

//demo1
var a=20;
function fn(){
console.log(this.a)
}
fn();//20  调用者函数独立调用,非严格模式下指向全局对象,所以打印为20 ;严格模式下为undefined;
//demo2
var a=20;
function fn(){
  function foo(){
  console.log(this.a)
}
foo();
}
fn()  //20  调用者函数独立调用,非严格模式下指向全局对象,所以打印为20 ;严格模式下为undefined;
//demo3
var a=20;
var obj={
  a:10,
  c:this.a+20,
  fn:function(){
    return this.a;
  }
}
console.log(obj.c);   //40
console.log(obj.fn())  //10

这个地方需要注意,当调用者不是一个函数时,无论在什么地方调用,这里的this都指向全局对象,所以obj.c输出为40,而obj.fn()在对象中,被对象所拥有,内部的this就指向该对象;

若把obj放在函数中返回,则遵循以上结论,当obj在函数环境中声明时,这个this在严格模式下指向undefined,非严格模式下自动转向全局对象,可运行如下例子查看区别:

   var a=20;
        function foo(){
            var obj={
                a:10,
                c:this.a+20,
                fn:function(){
                    return this.a;
                }
            }            
            return obj.c;  //40
            // return obj.fn;  10
        }

        console.log(foo()) //40

如下加深理解:

var a=20;
var foo={
  a:10,
getA:function(){
  return this.a;
}
}
console.log(foo.getA())  //10
var test = foo.getA;
console.log(test());//20 test()作为调用则尽管与foo.getA的引用相同,但它是独立调用,非严格模式下自动指向全局;

修改

var a=20;
function getA(){
  return this.a;
}
var foo={
  a:10,
getA:getA
}
console.log(foo.getA()); //10

独立调用:

       function foo(){
            console.log(this.a)
        }
        function active(fn){
            fn();  //真实调用者,为独立调用
        }
        var a=20;
        var obj={
            a:10,
            getA:foo
        }
        active(obj.getA)  //20

手动设置this指向的问题:

使用call,apply显示指定this
JavaScript内部提供了一种机制,让我们可以自行手动设置this的指向。它们就是call与apply。所有的函数都具有着两个方法。它们除了参数略有不同,其功能完全一样。它们的第一个参数都为this将要指向的对象。

如下例子所示。fn并非属于对象obj的方法,但是通过call,我们将fn内部的this绑定为obj,因此就可以使用this.a访问obj的a属性了。这就是call/apply的用法。

function fn(){
  console.log(this.a);
}
var obj={
  a:10
}
fn.call(obj)//10

而call与applay后面的参数,都是向将要执行的函数传递参数。其中call以一个一个的形式传递,apply以数组的形式传递。这是他们唯一的不同。

function fn(num1,num2){
  console.log(this.a+num1+num2);
}
var obj={
  a:10
}
fn.call(obj,100,100) //210
fn.apply(obj,[20,20]) //50

call()和apply将类数组转成数组

function exam(a,b,c,d){
  
  var arg=[].slice.call(arguments);
console.log(arg);
}
exam(1,3,4,5) // [1,3,4,5]
//也常常使用该方法将DOM中的nodelist转换为数组
// [].slice.call( document.getElementsByTagName('li') );

根据自己的需要灵活修改this指向

  var foo={
    name:'zx',
showname:function(){
  console.log(this.name)
}
}
var fn={
name:'zqq'
}
foo.showname.call(fn)  //zqq

实现继承

 var person={
      init:function(name,age){
          this.name=name;
          this.age=age;
          this.gender=['man','woman']
      }
  }


var student={
  init:function(name,age,hight){
    person.call(this,name,age);
    this.hight=hight;
 }
}

student.prototype.message=function(){
    console.log('name:'+this.name+', age:'+this.age+', high:'+this.high+', gender:'+this.gender[0]+';')
}

new student.init('xiaom', 12, '150cm').message()

简单给有面向对象基础的朋友解释一下。在Student的构造函数中,借助call方法,将父级的构造函数执行了一次,相当于将Person中的代码,在Sudent中复制了一份,其中的this指向为从Student中new出来的实例对象。call方法保证了this的指向正确,因此就相当于实现了基层。Student的构造函数等同于下。

var Student = function(name, age, high) {
    this.name = name;
    this.age  = age;
    this.gender = ['man', 'woman'];
    // Person.call(this, name, age); 这一句话,相当于上面三句话,因此实现了继承
    this.high = high;
}

在向其他执行上下文的传递中,确保this的指向保持不变
如下面的例子中,我们期待的是getA被obj调用时,this指向obj,但是由于匿名函数的存在导致了this指向的丢失,在这个匿名函数中this指向了全局,因此我们需要想一些办法找回正确的this指向。

var obj = {
    a: 20,
    getA: function() {
        setTimeout(function() {
            console.log(this.a)
        }, 1000)
    }
}

obj.getA();

常规的解决办法很简单,就是使用一个变量,将this的引用保存起来。我们常常会用到这方法,但是我们也要借助上面讲到过的知识,来判断this是否在传递中被修改了,如果没有被修改,就没有必要这样使用了。

var obj = {
    a: 20,
    getA: function() {
        var self = this;
        setTimeout(function() {
            console.log(self.a)
        }, 1000)
    }
}

另外就是借助闭包与apply方法,封装一个bind方法。

function bind(fn, obj) {
    return function() {
        return fn.apply(obj, arguments);
    }
}

var obj = {
    a: 20,
    getA: function() {
        setTimeout(bind(function() {
            console.log(this.a)
        }, this), 1000)
    }
}

obj.getA();

当然,也可以使用ES5中已经自带的bind方法。它与我上面封装的bind方法是一样的效果。

var obj = {
    a: 20,
    getA: function() {
        setTimeout(function() {
            console.log(this.a)
        }.bind(this), 1000)
    }
}
上一篇下一篇

猜你喜欢

热点阅读