JavaScript 是一门集成了函数编程和面向对象编程的动态语言。它的对象是一个容器,封装了属性(property)和方法(method)。JavaScript的面向对象实现不是基于类,而是基于构造函数(constructor)和原型链(prototype)实现的。
this 关键字
- this对象的含义?
this
关键字总是返回一个对象,此对象就是当前property
所在的运行对象。对象的属性值可以是对象或者函数等,其的属性值为地址,调用时,当前环境对象针对地址所对应的对象进行调用。 具体见:this的含义 - 给this绑定对象的方法?
this
的动态切换,为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。我们通过call
、apply
、bind
这三个方法,来切换/固定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.对象的方法
-
如果对象的方法里面包含
this
,this
的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变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 创造了巨大的灵活性,但也使得编程变得困难和模糊。我们通过call
、apply
、bind
这三个方法,来切换/固定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
方法的参数,应该是一个对象。如果参数为空、null
和undefined
,则默认传入全局对象。 -
如果
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()
-
作用概述,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
-
在方法内部赋值的时候,将内部的this绑定到原对象
var counter = { count: 0, inc: function () { this.count++; } }; var func = counter.inc.bind(counter); func(); counter.count // 1
-
将
this
绑定到其他的对象上,也可以var counter = { count: 0, inc: function () { this.count++; } }; var obj = { count: 100 }; var func = counter.inc.bind(obj); func(); obj.count // 101
-
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
-
如果bind方法的第一个参数是null或undefined,等于将this绑定到全局对象,函数运行时this指向顶层对象(浏览器为window)
function add(x, y) { return x + y; } var plus5 = add.bind(null, 5); plus5(10) // 15
-
使用注意点,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);
-
使用注意点,结合回调函数使用,回调函数是 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
-
使用注意点,数组中某些方法会接受一些函数作为参数,这些函数中使用
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()