JavaScript 面向对象编程【二】(this 关键字)

  • A+

JavaScript 是一门集成了函数编程和面向对象编程的动态语言。它的对象是一个容器,封装了属性(property)和方法(method)。JavaScript的面向对象实现不是基于类,而是基于构造函数(constructor)和原型链(prototype)实现的。

this 关键字

  1. this对象的含义? this关键字总是返回一个对象,此对象就是当前property所在的运行对象。对象的属性值可以是对象或者函数等,其的属性值为地址,调用时,当前环境对象针对地址所对应的对象进行调用。 具体见:this的含义
  2. 给this绑定对象的方法? this的动态切换,为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。我们通过callapplybind这三个方法,来切换/固定this的指向。具体见:绑定 this 的方法

一.this的含义

1.this的含义

  • this关键字总是返回一个对象,此对象就是当前property所在的运行对象。
  • 由于属性是可以被赋值的,所以this所对应的对象也是动态的。
  • JavaScript 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this就是函数运行时所在的对象(环境)。
    //一. this指向person对象
    var person = {
      name: 'Joey',
      describe: function () {
        return 'name:'+ this.name;
      }
    };
    
    person.describe()
    // "name:Joey"
    
    //二. this所在函数被引用,指向不同对象
    function f() {
      return 'name:'+ this.name;
    }
    
    var A = {
      name: 'Joey',
      describe: f
    };
    
    var B = {
      name: 'Chandler',
      describe: f
    };
    
    A.describe() // "name:Joey"
    B.describe() // "name:Chandler"
    
    //三. this被重新赋值,动态转换了对象,此处对象是顶层对象
    var A = {
      name: 'Joey',
      describe: function () {
        return 'name:'+ this.name;
      }
    };
    
    var name = 'Chandler';
    var f = A.describe;
    f() // "name:Chandler"
    
    //四. 实际使用范例,判断输入框,每输入一个值,此值是否在指定范围,this代表文本框对象
    <input type="text" name="age" size=3 onChange="validate(this, 18, 99);">
    
    <script>
    function validate(obj, lowval, hival){
      if ((obj.value < lowval) || (obj.value > hival))
        console.log('Invalid Value!');
    }
    </script>
    

二.this的使用场合

1.全局环境

  • 全局环境使用this,它指的就是顶层对象window
  • 不管是不是在函数的内部,只要在全局环境下运行,this就是指顶层对象window
    this === window // true
    
    function f() {
      console.log(this === window);
    }
    f() // true
    

2.构造环境

  • 构造函数中的this,指的是实例对象。this赋值了以后,在对象中生成了一个属性。如下:
    var Obj = function (p) {
      this.p = p;
    };
    var o = new Obj('Hello World!');
    o.p // "Hello World!"
    

3.对象的方法

  • 如果对象的方法里面包含thisthis的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this的指向。
    var obj ={
      foo: function () {
        console.log(this);
      }
    };
    
    obj.foo() // obj
    
    // 情况一
    (obj.foo = obj.foo)() // window
    //等同于
    (obj.foo = function () {
      console.log(this);
    })()
    // 情况二
    (false || obj.foo)() // window
    //等同于
    (false || function () {
      console.log(this);
    })()
    // 情况三
    (1, obj.foo)() // window
    //等同于
    (1, function () {
      console.log(this);
    })()
    

三.使用误区

1.避免多层 this

  • 由于this的指向是不确定的,所以切勿在函数中包含多层的this。
    //内部this指向了window对象
    var o = {
      f1: function () {
        console.log(this);
        var f2 = function () {
          console.log(this);
        }();
      }
    }
    
    o.f1()// Object// Window
    //上面执行如下
    var temp = function () {
      console.log(this);
    };
    
    var o = {
      f1: function () {
        console.log(this);
        var f2 = temp();
      }
    }
    
    
  • 解决方案,引入中间变量或者使用严格模式
    //一. 引入中间变量
    var o = {
      f1: function() {
        console.log(this);
        var that = this;
        var f2 = function() {
          console.log(that);
        }();
      }
    }
    
    o.f1()
    // Object
    // Object
    //二. 使用严格模式,若内部this指向顶层对象,则会抛出异常
    var counter = {
      count: 0
    };
    counter.inc = function () {
      'use strict';
      this.count++
    };
    var f = counter.inc;
    f()
    // TypeError: Cannot read property 'count' of undefined
    

2.避免数组处理方法中的 this

  • 数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。
    //一. 出现问题,将函数作为参数传递给forEach,this指向的是顶层对象
    var o = {
      v: 'hello',
      p: [ 'a1', 'a2' ],
      f: function f() {
        this.p.forEach(function (item) {
          console.log(this.v + ' ' + item);
        });
      }
    }
    
    o.f()
    // undefined a1
    // undefined a2
    
    //二. 解决方案,使用中间变量
    var o = {
      v: 'hello',
      p: [ 'a1', 'a2' ],
      f: function f() {
        var that = this;
        this.p.forEach(function (item) {
          console.log(that.v+' '+item);
        });
      }
    }
    
    o.f()
    // hello a1
    // hello a2
    
    //三.forEach中,使用this参数来固定运行对象
    var o = {
      v: 'hello',
      p: [ 'a1', 'a2' ],
      f: function f() {
        this.p.forEach(function (item) {
          console.log(this.v + ' ' + item);
        }, this);
      }
    }
    
    o.f()
    // hello a1
    // hello a2
    

3.避免回调函数中的 this

  • 避免回调函数中的 this
    //输出false,因为this指向点击按钮的dom对象中
    var o = new Object();
    o.f = function () {
      console.log(this === o);
    }
    
    // jQuery 的写法
    $('#button').on('click', o.f);
    

四.绑定 this 的方法

  • this的动态切换,为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。我们通过callapplybind这三个方法,来切换/固定this的指向。

1.Function.prototype.call()

  • 函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。
    var obj = {};
    
    var f = function () {
      return this;
    };
    
    f() === window // true
    //call将f函数的执行环境绑定为obj
    f.call(obj) === obj // true
    
  • call方法的参数,应该是一个对象。如果参数为空、nullundefined,则默认传入全局对象。
  • 如果call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入call方法。
    //一. 空对象
    var n = 123;
    var obj = { n: 456 };
    
    function a() {
      console.log(this.n);
    }
    
    a.call() // 123
    a.call(null) // 123
    a.call(undefined) // 123
    a.call(window) // 123
    a.call(obj) // 456
    
    //二. 原始值
    var f = function () {
      return this;
    };
    
    f.call(5)
    // Number {[[PrimitiveValue]]: 5}
    
  • call方法的描述func.call(thisValue, arg1, arg2, ...),它接受多个参数,第一个为需要指向的对象,后面是参数
    function add(a, b) {
      return a + b;
    }
    
    add.call(this, 1, 2) // 3
    
  • call方法的一个应用是调用对象的原生方法
    var obj = {};
    obj.hasOwnProperty('toString') // false
    
    // 覆盖掉继承的 hasOwnProperty 方法
    obj.hasOwnProperty = function () {
      return true;
    };
    obj.hasOwnProperty('toString') // true
    //跳过对象方法重写,调用对象原始方法
    Object.prototype.hasOwnProperty.call(obj, 'toString') // false
    

2.Function.prototype.apply()

  • apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数:func.apply(thisValue, [arg1, arg2, ...])
    //apply方法的第一个参数也是this所要指向的那个对象,如果设为null或undefined,则等同于指定全局对象。
    function f(x, y){
      console.log(x + y);
    }
    
    f.call(null, 1, 1) // 2
    f.apply(null, [1, 1]) // 2
    
  • Function.prototype.apply()的应用
    一. 找出数组最大元素
    var a = [10, 2, 4, 15, 9];
    Math.max.apply(null, a) // 15
    
    二. 将数组的空元素变为undefined,空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined
    Array.apply(null, ['a', ,'b'])
    // [ 'a', undefined, 'b' ]
    
    三. 转换类似数组的对象,利用数组对象的slice方法
    Array.prototype.slice.apply({0: 1, length: 1}) // [1]
    Array.prototype.slice.apply({0: 1}) // []
    Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
    Array.prototype.slice.apply({length: 1}) // [undefined]
    
    四. 绑定回调函数的对象。下面代码,点击按钮以后,控制台将会显示true。由于apply方法(或者call方法)不仅绑定函数执行时所在的对象,还会立即执行函数,因此不得不把绑定语句写在一个函数体内。
    var o = new Object();
    
    o.f = function () {
      console.log(this === o);
    }
    
    var f = function (){
      o.f.apply(o);
      // 或者 o.f.call(o);
    };
    
    // jQuery 的写法
    $('#button').on('click', f);
    

3.Function.prototype.bind()

  1. 作用概述,bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数
    var d = new Date();
    d.getTime() // 1481869925657
    
    var print = d.getTime;
    print() // Uncaught TypeError: this is not a Date object.
    //上述范例调用print会报错,是因为,getTime内部的this已经不指Dat对象的实例了
    //使用bind方法解决此问题,将将getTime方法内部的this绑定到d对象
    var print = d.getTime.bind(d);
    print() // 1481869925657
    
  2. 在方法内部赋值的时候,将内部的this绑定到原对象
    var counter = {
      count: 0,
      inc: function () {
        this.count++;
      }
    };
    
    var func = counter.inc.bind(counter);
    func();
    counter.count // 1
    
  3. this绑定到其他的对象上,也可以
    var counter = {
      count: 0,
      inc: function () {
        this.count++;
      }
    };
    
    var obj = {
      count: 100
    };
    var func = counter.inc.bind(obj);
    func();
    obj.count // 101
    
  4. bind还可以接受更多的参数,将这些参数绑定原函数的参数
    //将add函数绑定到obj,并指定第一个参数
    var add = function (x, y) {
      return x * this.m + y * this.n;
    }
    
    var obj = {
      m: 2,
      n: 2
    };
    
    var newAdd = add.bind(obj, 5);
    newAdd(5) // 20
    
  5. 如果bind方法的第一个参数是null或undefined,等于将this绑定到全局对象,函数运行时this指向顶层对象(浏览器为window)
    function add(x, y) {
      return x + y;
    }
    
    var plus5 = add.bind(null, 5);
    plus5(10) // 15
    
  6. 使用注意点,bind方法每次都返回一个新函数
    //一.若写为如下方式,click事件绑定bind方法生成的一个匿名函数,绑定后将无法解除绑定
    element.addEventListener('click', o.m.bind(o));
    element.removeEventListener('click', o.m.bind(o));
    
    //二.正确使用方式
    var listener = o.m.bind(o);
    element.addEventListener('click', listener);
    element.removeEventListener('click', listener);
    
  7. 使用注意点,结合回调函数使用,回调函数是 JavaScript 最常用的模式之一,但是一个常见的错误是,将包含this的方法直接当作回调函数。
    //若不使用bind函数,直接将counter作为函数的参数,则this将指向顶层对象
    var counter = {
      count: 0,
      inc: function () {
        'use strict';
        this.count++;
      }
    };
    
    function callIt(callback) {
      callback();
    }
    
    callIt(counter.inc.bind(counter));
    counter.count // 1
    
  8. 使用注意点,数组中某些方法会接受一些函数作为参数,这些函数中使用this需要注意,如下:
    var obj = {
      name: 'Joey',
      times: [1, 2, 3],
      print: function () {
        this.times.forEach(function (n) {
          console.log(this.name);
        });
      }
    };
    obj.print()
    // 没有任何输出。this.times指向的是times数组,而this.name在调用时指向的是顶层对象,它的调用和下面方法一致
    obj.print = function () {
      this.times.forEach(function (n) {
        console.log(this === window);
      });
    };
    obj.print()// true // true // true
    
    //解决方案
    obj.print = function () {
      this.times.forEach(function (n) {
        console.log(this.name);
      }.bind(this));
    };
    
    obj.print()
    
zhangfeng

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: