this of javascript
三大特点
this令人困惑。
this运行时绑定的产物
依赖函数调用的上下文环境。
javascript的作者当时戏说他也弄不清,就记住那四种方式(下文抛出)就ok。知识点1,一个函数调用会有一个执行环境。这个执行环境包含了是如何被调用的、传递的参数信息、this也是其中的一个属性。知识点2,多次提到的上下文,看一下它的生命周期。
;
大多数人的误区错把this和词法作用域混淆,上图也可以看出创建阶段计算出this和作用域链。那他们有关系吗?明确一下javascript中的this不会以任何方式指向词法作用域。作用域好像是一个将所有可用标识符作为属性的对象,这从内部来说是对的。但是JavasScript代码不能访问作用域“对象”。它是引擎的内部实现。
meaning of exsit
引入一个函数传参的例子,感受一下通过一个context传递调用的对象与this隐式传递的对比。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| // use param context
function animal(context) { return context.name; }
function bigDog(context) { console.log( `hello,${animal(context)}`); }
var name = 'anni';
bigDog(window); //hello anni
// use this
function animal() { return this.name; }
function bigDog() { console.log(`hello,${animal()}`) }
var name = 'anni';
bigDog(); //hello anni
|
- 更加优雅的传递引用
- 更加干净的API设计和更容易的复用
辨认规则:
前人经过长期使用总结出一套使用规律。this与它所在方法的调用点有关,与方法所在声明没有关系,看下文这个例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| // obj声明位置 var obj = { name: 'i am obj', test: function() { console.log(this.name); } } // obj1声明位置 var obj1 = { name: 'i am obj1', test1: obj.test } // obj1调用位置 obj1.test1(); // i am obj1
|
wow, 厉害,我能辨认this了,不!还差得远呢。接着看
1 2 3 4 5 6 7 8 9 10 11 12 13
| // egg.1 function sayName() { console.log(this.name); } var name = 'i am window'; sayName(); // i am window
//egg.2 var obj = { name: 'i am obj', say: sayName } setTimeout(obj.say, 1000); // i am window
|
what is the wrong? what is the fuck ?
然后又有一群人经过实验规律总结: this与它所在方法的调用点有关这条理论完全正确,不过这仅仅是规则,还需要配合方法论使用。确认where is this?前提是这条理论,还要配合以下四条方法论。
- 默认绑定,函数独立调用
1 2 3 4 5 6
| // egg.1 function sayName() { console.log(this.name); } var name = 'i am window'; sayName(); // i am window
|
egg.1 就是默认绑定的具体应用。首先调用点是函数直接调用的。那么为什么默认绑定使用于这里?因为没有其他规则适用了。No way, 无法反驳,游戏的规则就是这样。
- 隐含绑定,如若隐含丢失则退回默认绑定, 调用点一般是一个拥有者或者容器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // egg.2 // obj声明位置 var obj = { name: 'i am obj', test: function() { console.log(this.name); } } // obj1声明位置 var obj1 = { name: 'i am obj1', test1: obj.test } // obj1调用位置 obj1.test1(); // i am obj1
|
egg.2就是隐含绑定的具体应用。调用点是obj1,test的调用位置点指向obj1的引用,这个时候隐含绑定规则就会说这个对象的函数调用应该被this绑定。
隐含丢失,即隐含绑定失效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| //egg.3 var animal1 = { name: 'litchi', say: function() { console.log(this.name); } } var animal2 = { name: 'hyc', say: function() { var test = animal1.say; test(); } } var name = 'hello';
animal2.say(); // hello
// egg.4 setTimeout setInterval 匿名函数执行的当前对象 var animal1 = { name: 'litchi', say: function() { console.log(this.name); } } var name = 'hyc'; setTimeout( animal1.say, 1000);
|
egg.3与egg.4 就是典型的隐含绑定中this丢失的实例。
- 明确绑定,call、apply
1 2 3 4 5 6 7 8 9 10
| // egg.5 function foo() { console.log( this.name ); }
var obj = { name: 'litchi' };
foo.call( obj ); // litchi
|
call应用,即明确绑定,apply和call没有除调用参数外无任何区别。但是它不能解决this绑定丢失、函数覆盖等等问题。
硬绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function sayName() { console.log(this.name); }
var name = 'hyc';
var obj = { name: 'litchi' }
function say() { return sayName.call(obj); }
say();
say.call(obj);
setTimeout(say, 1000);
//litchi //litchi //litchi
|
明确绑定的小变种, 通过一个函数包装的形式将this强制绑定到obj上。无论怎样都不会被覆盖。硬绑定 是一个如此常用的模式,它已作为 ES5 的内建工具提供:Function.prototype.bind。
1 2 3 4 5 6 7 8 9 10 11 12 13
| function sayName() { console.log(this.name); }
var name = 'hyc';
var obj = { name: 'litchi' } var a = sayName.bind(obj);
a(); //litchi setTimeout(a, 1000); //litchi
|
自己封装一个bind
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function bindObj( fn, obj) { return function () { return fn.call(obj); } } function sayName() { console.log(this.name); }
var name = 'hyc';
var obj = { name: 'litchi' } var a = bindObj(sayName, obj);
a(); //litchi setTimeout(a, 1000); //litchi
|
接收我们回调的函数故意改变调用的 this。那些很流行的 JavaScript 库(jquery等)中的事件处理器就十分喜欢强制你的回调的 this 指向触发事件的 DOM 元素。
- new绑定,使用新构建的对象
首先,让我们重新定义 JavaScript 的“构造器”是什么。在 JS 中,构造器仅仅是一个函数,它们偶然地与前置的new操作符一起调用。它们不依附于类,它们也不初始化一个类。它们甚至不是一种特殊的函数类型。它们本质上只是一般的函数,在被使用 new 来调用时改变了行为。new的默认行为如下:
1. 新对象被构建分配内存
2. 将新创建的对象接入原型链
3. 将新创建的对象设置函数调用的this绑定
4. 除非函数返回一个它自己的其他对象,否则这个被new调用的函数将自动返回这个新构建的对象。
1 2 3 4 5
| function foo(name) { this.name = name || ''; } var bar = new foo('litchi'); console.log(bar.name); //litchi
|
构建了一个新对象bar, 把bar作为函数调用的this。
es6中的箭头函数
this是根据函数声明的时候确定的,也就是跟随词法作用域。
node中的this
node cli 与浏览器环境保持一致,脚本环境不一致,不做讨论。