此文主要总结于《你不知道的JavaScript 上卷》,虽然讲解this的文章已经烂大街了,可是依旧但愿,这篇文章能够帮助到那些仍是对this有些疑惑的哥们javascript
this关键字是JavaScript中最复杂的机制之一,它不是一个特殊的关键字,被自动定义在全部的函数做用域中。php
老规矩,咱们直接看例子:前端
function identify(){
console.log(this.name)
return this.name.toUpperCase();
}
function speak() {
var gretting = 'Hello I am '+identify.call(this)
console.log(gretting);
}
var me = {
name:'Neal'
}
var you = {
name:'Nealyang'
}
identify.call(me);
identify.call(you);
speak.call(me);
speak.call(you);复制代码
关于运行结果你们能够自行运行查看,若是对于this是如何工做的这里咱们仍是存在疑惑,那么别急,咱们后面固然会继续深刻探讨下,这里,先说下关于this
的一些误解的地方java
一般新手都会认为this就是指向函数自己,至于为何在函数中引用他本身呢,可能就是由于递归这种状况的存在吧。可是这里,我想说,this并非指向函数自己的es6
function foo(num) {
console.log("foo:"+num);
this.count++;
}
foo.count = 0;
for(var i = 0;i<10;i++){
foo(i);
}
console.log(foo.count);复制代码
经过运行上面的代码咱们能够看到,foo函数的确是被调用了十次,可是this.count彷佛并无加到foo.count上。也就是说,函数中的this.count并非foo.count。面试
因此,这里咱们必定要记住一个,就是函数中的this并非指向函数自己的。上面的代码修改以下:bash
function foo(num) {
console.log("foo:"+num);
this.count++;
}
foo.count = 0;
for(var i = 0;i<10;i++){
foo.call(foo,i);
}
console.log(foo.count);复制代码
运行如上代码,此时咱们就能够看到foo函数中的count的确已经变成10了微信
另外一种对this的误解是它不知怎么的指向函数的做用域,其实从某种意义上来讲他是正确的,可是从另外一种意义上来讲,这的确是一种误解。app
明确的说,this不会以任何方式指向函数的词法做用域,做用域好像是一个将全部可用标识符做为属性的对象,这从内部来讲他是对的,可是JavaScript代码不能访问这个做用域“对象”,由于它是引擎内部的实现。ide
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo(); //undefined复制代码
上面的代码不止一处错误,这里不作讨论,仅仅用于看代码,首先,视图this.bar()来视图访问bar函数,的确他作到了。虽然只是碰巧而已。然而,写下这段代码的开发者视图使用this在foo和bar的词法做用域中创建一座桥,是的bar能够访问foo内部变量做用域a。固然,这是不可能的,不可能使用this引用在词法做用域中查找东西。
因此说了这么coder对this的误解,那么究竟什么是this呢。记住,this不是在编写时候绑定的,而是在运行时候绑定的上下文执行环境。this绑定和函数申明无关,反而和函数被调用的方式有关系。
当一个函数被调用的时候,会创建一个活动记录,也成为执行环境。这个记录包含函数是从何处(call-stack)被调用的,函数是 如何 被调用的,被传递了什么参数等信息。这个记录的属性之一,就是在函数执行期间将被使用的this引用。
为了完全弄明白this的指向问题,咱们还必须明白什么是调用点,即一个函数被调用的位置。考虑调用栈(即便咱们到达当前执行位置而被d调用的全部方法堆栈)是很是重要的,咱们关心的调用点就是当前执行函数的以前的调用
function baz() {
// 调用栈是: `baz`
// 咱们的调用点是global scope(全局做用域)
console.log( "baz" );
bar(); // <-- `bar`的调用点
}
function bar() {
// 调用栈是: `baz` -> `bar`
// 咱们的调用点位于`baz`
console.log( "bar" );
foo(); // <-- `foo`的call-site
}
function foo() {
// 调用栈是: `baz` -> `bar` -> `foo`
// 咱们的调用点位于`bar`
console.log( "foo" );
}
baz(); // <-- `baz`的调用点复制代码
上面代码你们简单感觉下什么是调用栈和调用点,比较简单的东西。
咱们必须考察调用点,来判断下面即将要说的四中规则哪种适用。先独立解释下四中规则的每一种,而后再来讲明下若是多种规则适用调用点时他们的优先级。
所谓的默认绑定,就是独立函数的调用形式。
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2复制代码
为何会是2呢,由于在调用foo的时候,JavaScript对this实施了默认绑定,因此this就指向了全局对象。
咱们怎么知道这里适用 默认绑定 ?咱们考察调用点来看看foo()是如何被调用的。在咱们的代码段中,foo()是被一个直白的,毫无修饰的函数引用调用的。没有其余的咱们将要展现的规则适用于这里,因此 默认绑定 在这里适用。
须要注意的是,对于严格模式来讲,默认绑定全局对象是不合法的,this被置为undefined。可是一个很微妙的事情是,即使是全部的this绑定规则都是基于调用点的,若是foo的内容没有严格模式下,默认绑定也是合法的。
调用点是否有一个环境对象,也成为拥有者和容器对象。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2复制代码
foo被申明,而后被obj添加到其属性上,不管foo()是否一开始就在obj上被声明,仍是后来做为引用添加(如上面代码所示),都是这个 函数 被obj所“拥有”或“包含”。
这里须要注意的是,只有对象属性引用链的最后一层才影响调用点
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42复制代码
this绑定最让人头疼的地方就是隐含绑定丢失了他的绑定,其实明确了调用位置,这个也不是难点。直接看代码
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数引用!
var a = "oops, global"; // `a`也是一个全局对象的属性
bar(); // "oops, global"复制代码
因此如上的调用模式,咱们又退回到了默认绑定模式。
还能hold住,那么接着看代码:
function foo() {
console.log( this.a );
}
function doFoo(fn) {
// `fn` 只不过`foo`的另外一个引用
fn(); // <-- 调用点!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // `a`也是一个全局对象的属性
doFoo( obj.foo ); // "oops, global"复制代码
参数传递,仅仅是一种隐含的赋值,并且由于咱们是传递一个函数,他是一个隐含的引用赋值,因此最终结果和咱们前一段代码同样。
因此,在回调函数中丢失this绑定是一件很常见的事情,可是还有另外一种状况,接受咱们回调的函数故意改变this的值。那些很受欢迎的事件处理JavaScript包就十分喜欢强制你的回调的this指向触发事件的DOM元素。
无论哪种意外改变this的方式,你都不能真正地控制你的回调函数引用将如何被执行,因此你(还)没有办法控制调用点给你一个故意的绑定。咱们很快就会看到一个方法,经过 固定 this来解决这个问题。
如上,咱们必定要清除的是引用和调用。记住,找this,咱们只看调用,别被引用所迷惑
在JavaScript中,咱们能够强制制定一个函数在运行时候的this值。是的,call和apply,他们的做用就是扩充函数赖以生存的做用域。
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
foo.call( obj ); // 2复制代码
上面代码,咱们使用foo,强制将foo的this指定为obj
若是你传递一个简单原始类型值(string,boolean,或 number类型)做为this绑定,那么这个原始类型值会被包装在它的对象类型中(分别是new String(..),new Boolean(..),或new Number(..))。这一般称为“boxing(封箱)”。
可是,单独的依靠明确绑定仍然不能为咱们先前提到的问题,提供很好的解决方案,也就是函数丢失本身本来的this绑定。
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// `bar`将`foo`的`this`硬绑定到`obj`
// 因此它不能够被覆盖
bar.call( window ); // 2复制代码
咱们建立了一个函数bar(),在它的内部手动调用foo.call(obj),由此强制this绑定到obj并调用foo。不管你事后怎样调用函数bar,它老是手动使用obj调用foo。这种绑定即明确又坚决,因此咱们称之为 硬绑定(hard binding)
这个比较简单,当函数前面加入new关键字调用的时候,其实就是当作构造函数调用的。其内部其实完成了以下事情:
var bar = new Foo();复制代码
var bar = foo.call(obj);复制代码
var bar = obj.foo();复制代码
var bar = foo();复制代码
第一种状况就是将null和undefined传给call、apply、bind等函数,而后此时this采用的绑定规则是默认绑定
第二种状况这里举个例子,也是面试中经常会出现的例子
function foo() {
console.log(this.a);
}
var a = 2;
var o = {
a:3,
foo:foo
}
var p = {a:4};
(p.foo = o.foo)();复制代码
如上调用,其实foo采用的也是默认绑定,这里咱们须要知道的是,p.foo = o.foo的返回值是目标函数的引用,因此最后一句其实就是foo()
es6中的箭头函数比较简单,因为箭头函数并非function关键字定义的,因此箭头函数不适用this的这四中规则,而是根据外层函数或者全局做用域来决定this
function foo() {
// 返回一个arrow function
return (a) => {
// 这里的`this`是词法上从`foo()`采用
console.log( this.a );
};
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是3!复制代码
这里foo内部建立的箭头函数会自动获取foo的this。
var a=10;
var foo={
a:20,
bar:function(){
var a=30;
console.log(this)
return this.a;
}
};
foo.bar()
(foo.bar)()
(foo.bar=foo.bar)()
(foo.bar,foo.bar)()复制代码
第二题
function t(){
this.x=2;
}
t();
console.log(window.x);复制代码
第三题
```javascript
var obj = {
x: 1,
y: 2,
t: function() {
console.log(this.x)
}
}
obj.t();
var dog={x:11};
dog.t=obj.t;
dog.t();
show=function(){
console.log('show'+this.x);
}
dog.t=show;
dog.t();
- 第四题
```javascript
name = 'this is window';
var obj1 = {
name: 'php',
t: function() {
console.log(this.name)
}
};
var dog1 = {
name: 'huzi'
};
obj1.t();
dog1.t = obj1.t;
var tmp = dog1.t;
tmp(); //this原本指向window
(dog1.t = obj1.t)();
dog1.t.call(obj1);复制代码
var number=2;
var obj={
number:4,
/*匿名函数自调*/
fn1:(function(){
var number;
this.number*=2;//4
number=number*2;//NaN
number=3;
return function(){
var num=this.number;
this.number*=2;//6
console.log(num);
number*=3;//9
alert(number);
}
})(),
db2:function(){
this.number*=2;
}
}
var fn1=obj.fn1;
alert(number);
fn1();
obj.fn1();
alert(window.number);
alert(obj.number);复制代码
扫码关注个人我的微信公众号,分享更多原创文章。点击交流学习加我微信、qq群。一块儿学习,一块儿进步。共同交流上面的题目吧
欢迎兄弟们加入:
Node.js技术交流群:209530601
React技术栈:398240621
前端技术杂谈:604953717 (新建)