[TOC] ## this概述 函数第二层开始的this指向全局 `this` 总是总是返回一个对象 ``` var person = { name: '张三', describe: function () { return '姓名:'+ this.name; } }; person.describe(); //"姓名:张三" ``` this 指向此刻的对象 ``` function f() { return '姓名:'+ this.name; } var A = { name: '张三', describe: f }; var B = { name: '李四', describe: f }; A.describe() // "姓名:张三" B.describe() // "姓名:李四" ``` 在html 中的例子 this 是指向输入框,所有函数中this 可以获取输入框的vlaue ``` <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` 在浏览器中运行,全局环境使用`this`,它指的就是顶层对象`window` ``` var f = function () { console.log(this.x); } var x = 1; var obj = { f: f, x: 2, }; // 单独执行 f() // 1 // obj 环境执行 obj.f() // 2 ``` ### 构造函数 `this` 构造函数中的`this`,指的是实例对象。 ``` var Obj = function (p) { this.p = p; }; var o = new Obj('Hello World!'); o.p // "Hello World!" ``` ### 对象的方法 `this`的指向就是方法运行时所在的对象。该方法**赋值**给另一个对象,就会改变`this`的指向 ``` var obj ={ foo: function () { console.log(this); } }; obj.foo() // obj ``` ``` // 情况一 (obj.foo = obj.foo)() // window // 等同于 (function () { console.log(this); })() // 情况二 (false || obj.foo)() // window //等同于 (false || function () { console.log(this); })() // 情况三 (1, obj.foo)() // window //等同于 (1, function () { console.log(this); })() ``` 若对象指向非第一层,则指向当前层的对象,且不会想上继承 ``` var obj ={ a:"asd", b:{ foo:function () { this.a } } } obj.b.foo() //undefined ``` ## 使用注意点 ### 避免多层 this ``` 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(); } } ``` 在验证模式下如果函数内部的 `this` 指向顶层对象,则会报错 ``` var counter = { count: 0 }; counter.inc = function () { 'use strict'; this.count++ }; var f = counter.inc; f() // TypeError: Cannot read property 'count' of undefined ``` ### 避免数组处理方法中的 this 多层`this`,内部的`this` 内层的`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 that =this`,内容调用 `that` 方法二 :`this`当作`foreach`方法的第二个参数,固定它的运行环境 ``` 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 ``` ### 避免回调函数中的 this this 指向DOM 而不是o 对象 ``` var o = new Object(); o.f = function () { console.log(this === o); } obj.f() //true // jQuery 的写法 $('#button').on('click', o.f); //false ``` ## 绑定 this 的方法 ### Function.prototype.call() 指定this 可指定函数内部的 `this` 指向, 可做一些类似工厂的方法 ``` //在浏览器内执行 var o ={} var f=function () { return this } f() === window; f.call(o) === o; ``` `call`方法的参数,应该是一个对象。如果参数为空、`null`和`undefined`,则默认传入全局对象 ``` 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 ``` 第二个参数开始,就是调用函数的所需的参数 ``` func.call(thisValue, arg1, arg2, ...) ``` ### Function.prototype.apply() 指定this 参数已数组形式传入 与call,相同,区别是参数需要数组形式传入,es6 可使用 `...[]`形式 `func.apply(thisValue, [arg1, arg2, ...]) ` ``` function f(x, y){ console.log(x + y); } f.call(null, 1, 1) // 2 f.apply(null, [1, 1]) // 2 ``` 应用:可用于对多个元素的数组,进行两两比较时候 #### 找出数组最大元素 ``` var a = [10, 2, 4, 15, 9]; Math.max.apply(null, a) // 15 ``` #### 将数组的空元素变为undefined 通过apply方法,利用Array构造函数将数组的空元素变成undefined。 ``` Array.apply(null, ['a', ,'b']) // [ 'a', undefined, 'b' ] ``` 空元素与`undefined`的差别在于,`foreach` 会跳过空元素,但不会跳过`undefined` #### 转换类似数组的对象 `slice`与`call` 都可实现 ``` 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] ``` ### Function.prototype.bind() `getTime`方法内部的`this`,绑定`Date`对象的实例,赋给变量`print`以后,内部的`this`已经不指向`Date`对象的实例了 ``` var d = new Date(); d.getTime() // 1481869925657 var print = d.getTime; print() // Uncaught TypeError: this is not a Date object. ``` 正确: ``` var d =new Date() var f = d.getTime.bind(d) f(); //1551324238247 ``` 又一实例 ``` var counter = { count: 0, inc: function () { this.count++; } }; var func = counter.inc.bind(counter); func(); counter.count // 1 ``` 绑定到其他对象上 ``` var counter = { count: 0, inc: function () { this.count++; } }; var obj = { count: 100 }; var func = counter.inc.bind(obj); func(); obj.count // 101 ``` `bind`还可以接受更多的参数,将这些参数绑定原函数的参数(添加默认参数)。 ``` 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 ``` 设置函数的默认值,第一个参数可填写`null`或`undefined` ``` function add(x, y) { return x + y; } var plus5 = add.bind(null, 5); plus5(10) // 15 ``` #### 注意 #### 每一次返回一个新函数 error ``` element.addEventListener('click', o.m.bind(o)); element.removeEventListener('click', o.m.bind(o)); //或取消监听失败 ``` good ``` var listener = o.m.bind(o); element.addEventListener('click', listener); // ... element.removeEventListener('click', listener); ``` #### 结合call方法使用 ``` var push = Function.prototype.call.bind(Array.prototype.push); var pop = Function.prototype.call.bind(Array.prototype.pop); var a = [1 ,2 ,3]; push(a, 4) a // [1, 2, 3, 4] pop(a) a // [1, 2, 3] ``` ``` function f() { console.log(this.v); } var o = { v: 123 }; var bind = Function.prototype.call.bind(Function.prototype.bind); bind(f, o)() // 123 ```