不管学习什么知识,习惯于把自己所学习的知识列成一个list,会有助于我们理清思路,是一个很好的学习方法。强烈推荐。

以下篇幅有点长,希望读者耐心阅读。

以下内容会分为如下部分:

1.涵义

  1.1:this涵义

  1.2:this指向的可变性

2.使用场合

  2.1:全局环境

  2.2:构造函数

  2.3:对象的方法

3.使用注意点

  3.1:避免多层嵌套this

  3.2:避免数组处理方法中的this

  3.3:避免回调函数中的this

1.涵义

  1.1:this涵义

  在我写的一篇关于 构造函数与new关键字 的关系的博文中谈及,new关键字总是会返回一个对象。这个对象可以是new调用构造函数时返回的空对象,也可以是在构造函数中使用return语句返回的复杂数据类型(包括对象,数组等)。

  同样,与new关键字相同,this关键字总是返回一个对象。再说的详细一些,就是属性或方法“当前”所在的对象。

1     var Keith = {
2 firstName: 'Chou',
3 describe: function() {
4 return this.firstName;
5 }
6 };
7
8 console.log(Keith.describe()); //'Chou'

  上面代码中,this.firstName表示describe方法当前所在对象的firstName属性。也就是说,在全局作用域下调用describe方法时,describe方法所在的当前对象是Keith,所以就是调用Keith.firstName。

  1.2:this指向的可变性

  由于对象的属性可以赋给另外一个对象,所以属性所在的当前对象是可变的。也就是说,this的指向是可变的。

 1     var Keith = {
2 firstName: 'Chou',
3 describe: function() {
4 return this.firstName;
5 }
6 };
7
8 var Rascal={
9 firstName: 'King'
10 }
11
12 Rascal.describe=Keith.describe;
13 console.log(Rascal.describe()); //'King'

  上面代码中,Keith对象中的describe属性赋给Rascal,于是describe方法中当前所在的对象就是Rascal,所以this.firstName就指向Rascal。因为是传址传递,所以修改firstName会对原对象有影响。如有疑问,请移步 这篇文章 。这个例子可能不便于理解,再看看下面这个例子。

 1     function f(){
2 return this.firstName;
3 }
4
5 var Keith = {
6 firstName: 'Chou',
7 describe:f
8 };
9
10 var Rascal={
11 firstName: 'King',
12 describe:f
13 }
14
15 console.log(Keith.describe()); //'Chou'
16 console.log(Rascal.describe()); //'King'

  上面代码中,把方法移到全局作用域下,函数f内部使用了this关键字。随着f所在的对象不同,this指向也就不同。

  在全局作用域下this关键字会指向顶层对象(也就是window对象)。

1     var name='keith';
2 function person(){
3 var name='rascal';
4 return this.name;
5 }
6
7 console.log(person()); //'keith'

  上面代码中,返回的是keith而不是rascal。原因在于this指向的是全局作用域。在全局作用域中定义一个函数,默认是指向window对象,而不是函数本身。但是,如果在函数内部不使用var来声明一个局部变量,那结果也会不同。

1     var name='keith';
2 function person(){
3 name='rascal';
4 return this.name;
5 }
6
7 console.log(person()); //'rascal'

  上面代码中,在函数内部没有使用var来声明一个局部变量,那么此时函数内部的name属性不是局部变量,而是全局变量。所以会覆盖掉前面的name属性。如果对于局部变量和全局变量不了解,可以访问 这篇文章。 

  只要函数被赋给另外一个变量,this的指向会发生改变。

 1     var Keith={
2 name:'keith',
3 describe:function(){
4 return this.name;
5 }
6 }
7
8 var name='rascal';
9 var f=Keith.describe;
10 console.log(f()) //'rascal'

  上面代码中,返回的是rascal,而不是keith。因为Keith.describe被赋值给了f变量,而全局作用域下有一个name变量,所以Keith内部的函数的this指向就会指向f运行时所在的对象(顶层对象,也就是window对象)

  总结一下:

  1.javascript语言中,一切皆为对象(除了 undefined 和 null 之外),运行环境也是对象,所以函数都是在某个对象之中运行,this就是这个对象(环境)。

  2.this的指向是动态的。如果函数在全局作用域中,那么this就会指向全局环境;如果函数位于某个对象中,那么this就会指向该对象。

2.使用场合

  this的使用场合可以分为以下几个场合。

  2.1:全局环境(全局作用域)

  在全局作用域中使用this对象,它指向的就是顶层对象,也就是window对象。

1     function keith() {
2 return (this === window)
3 }
4
5 console.log(keith()) //true

  上面代码中,不管是不是在函数内部,只要在全局作用域下运行,this就是指向顶层对象window。

  

  2.2:构造函数

  构造函数中的this,指向的是将要创建的对象实例。

1     function Keith() {
2 this.sex = 'boy';
3 }
4
5 var person = new Keith();
6 console.log(person.sex); //'boy'

  上面代码中,在全局作用域下定义了Keith构造函数,然后调用构造函数并赋值给person对象实例。

  构造函数创建的三个基本要求:函数名首字母大写;构造函数内部使用this关键字来指向即将生成的对象实例;使用new关键字来调用构造函数并返回对象实例。

  如果想更进一步深入了解构造函数与new关键字的关系,请移步至 这篇文章 。

  2.3:对象的方法

  当A对象的方法被赋予B对象,该方法中的this就从指向A对象变成指向B对象。所以要特别小心,将某个对象的方法赋值个另外一个对象时,会改变this的指向。

 1     var keith = {
2 sex: 'boy',
3 foo: function() {
4 return this.sex;
5 }
6 };
7 var rascal = {
8 sex: 'girl'
9 };
10 rascal.foo = keith.foo;
11 console.log(keith.foo()); //'boy'
12 console.log(rascal.foo()); //'girl'

  上面代码中,把keith的foo函数赋值给了rascal,那么this的指向就从keith变成了rascal。这里涉及对象引用的知识,如有疑问,请移步至 这篇文章 。

  如果某个方法位于多层对象的内部,这时为了简化书写,把该方法赋值给一个变量,往往会得到不一样的结果。

 1     var a = {
2 b: {
3 p: 'keith',
4 c: function() {
5 return this.p;
6 }
7 }
8 };
9
10 var person = a.b.c;
11 console.log(person()); //undefined

  上面代码中,c是两层对象里面的一个方法。为求简便,将其赋值给全局变量person,结果调用时,this指向了顶层对象window。而在window中变量p默认值为undefined。

  要解决这个问题,可以只将c所在的对象赋值给person变量,或者是直接调用。

1     var person = a.b;
2 console.log(person.c()); //'keith'
3 console.log(a.b.c()); //'keith'

3.使用注意点

  3.1:避免多层嵌套this

  当在闭包中使用多层this,则this都会指向window。

1     function keith() {
2 console.log(this);
3 return function() {
4 return this;
5 }
6 }
7 keith(); //window
8 keith()(); //window

  上面代码中,在一个函数中返回另外一个匿名函数是闭包的特点之一,可以看出,当在闭包中使用this对象都会指向全局作用域中的window对象。

  如果在函数外包含一个对象,则内部this指向全局作用域,而外部this对象指向当前作用域。

 1     var o = {
2 f1: function() {
3 console.log(this);
4 (function() {
5 console.log(this)
6 })();
7 }
8 };
9
10 o.f1(); //Object , Window

  上面代码包含两层this,结果运行后,第一层指向当前对象,第二层指向全局对象。

  实际执行的是如下代码。

 1     function keith() {
2 console.log(this);
3 }
4 var o = {
5 f1: function() {
6 console.log(this);
7 var f2 = keith();
8 }
9 };
10
11 o.f1(); //Object , Window

  要实现多层this嵌套,有两种解决方法:

  一是在第二层中改用一个指向外层this的变量。

 1     var o = {
2 f1: function() {
3 console.log(this);
4 var that = this;
5 (function() {
6 console.log(that);
7 })();
8 }
9 };
10
11 o.f1(); //Object , Object

  上面代码中,定义了局部变量that,固定指向了外层的this,然后在内层中使用that,就不会发生this指向的改变。但是如果函数外部内有嵌套一个对象,this还是会指向全局。

  二是Javascript中的严格模式。在严格模式下,如果内部函数的this指向了window对象,就会报错。

 1     var a = {
2 count: 0,
3 fun: function() {
4 'use strict';
5 return this.count++;
6 }
7 }
8
9 var f = a.fun;
10 console.log(f()) //'TypeError: this is undefined'

  上面代码中,fun方法使用严格模式声明。把a对象中的fun方法赋值给全局变量f,那么this此时指向window对象,在严格模式下,就会报错。如果函数外部没有嵌套一个对象,那么不会报错,而是会返回undefined。

  

  3.2:避免数组处理方法中的this

   数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。

 1     var keith = {
2 a: 'Hello',
3 b: ['b1', 'b2'],
4 c: function() {
5 this.b.forEach(function(item) {
6 console.log(this.a + ' ' + item);
7 })
8 }
9 };
10 keith.c();
11 //undefined b1
12 //undefined b2

  上面代码中,forEach方法的回调函数中的this,其实指向的是window对象,因此取不到keith.a的值,同上也属于避免多层嵌套this。也就是说,内层的this不指向外部函数,而是指向顶层对象。

  要解决这个方法,可以使用that变量来代替回调函数中的this。

 1     var keith = {
2 a: 'Hello',
3 b: ['b1', 'b2'],
4 c: function() {
5 var that = this;
6 this.b.forEach(function(item) {
7 console.log(that.a + ' ' + item);
8 })
9 }
10 };
11 keith.c();
12 //Hello b1
13 //Hello b2

  另外一种方法,就是让this做为forEach方法的第二个参数,来固定它的运行环境。

 1     var keith = {
2 a: 'Hello',
3 b: ['b1', 'b2'],
4 c: function() {
5 this.b.forEach(function(item) {
6 console.log(this.a + ' ' + item);
7 }, this)
8 }
9 };
10 keith.c();
11 //Hello b1
12 //Hello b2

  

  3.3:避免回调函数中的this

  回调函数中的this往往会改变指向。

1     var o = {
2 f: function() {
3 console.log(this === o);
4 }
5 };
6
7 o.f(); // true;

  上面代码中,调用o对象的f方法,返回true。

  但是,如果将f方法指定给某个按钮的click事件,this的指向就变了。

1     $('button').on('click',o.f);

  上面代码中,使用了jquery方法来获取button元素,并绑定click事件。点击按钮后控制台会显示false。原因是此时this不再指向o对象了,而是指向按钮的DOM对象,因为f方法是在按钮对象的环境中被调用的。

  总结一下:

   a:如果想要多层嵌套this关键字,最常用的解决方法就是使用that变量,固定指向外层的this,然后在内层中使用that变量。就不会发生内层this指向全局的问题。

   b:如果在回调函数中使用this关键字,注意this的指向问题。

  鉴于篇幅的原因,会在下次的分享中谈及如何改变this的指向问题,会详细介绍call,apply,bind这三个方法。

  完。

  感谢大家的阅读。

《你不知道的JavaScript》整理(二)——this

最近在读一本进阶的JavaScript的书《你不知道的JavaScript(上卷)》,这次研究了一下“this”。

当一个函数被调用时,会创建一个活动记录(执行上下文)。

这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。

this就是记录的其中一个属性,会在函数执行的过程中用到。

this既不指向函数自身也不指向函数的作用域

this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用

一、调用位置

调用位置就在当前正在执行的函数的前一个调用中,源码查看

function baz() {
// 当前调用栈是:baz
// 因此,当前调用位置是全局作用域
console.log("baz");
bar(); // <-- bar 的调用位置
} function bar() {
// 当前调用栈是 baz -> bar
// 因此,当前调用位置在 baz 中
console.log("bar");
foo(); // <-- foo 的调用位置
} function foo() {
// 当前调用栈是 baz -> bar -> foo
// 因此,当前调用位置在 bar 中
console.log("foo");
}
baz(); // <-- baz 的调用位置

二、绑定规则

你必须找到调用位置,然后判断需要应用下面四条规则中的哪一条。

1)默认绑定

最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则

function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 2

2)隐式绑定

隐式绑定的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含

function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2

但有时候会出现隐式丢失

function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数
var a = "oops, global"; //
bar(); // "oops, global"

虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身

因此此时的bar()其实是一个不带任何修饰的函数调用,应用了默认绑定

3)显式绑定

使用函数的call(..)和apply(..)方法。

function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
foo.call(obj); // 2

在很多库中经常能看到bind方法,这是一种硬绑定,一种显式的强制绑定,下面是一种bind实现。

function foo(something) {
console.log(this.a, something);
return this.a + something;
}
// 简单的辅助绑定函数
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments);
};
}
var obj = {
a: 2
};
var bar = bind(foo, obj);
var b = bar(3); // 2 3
console.log(b); // 5

4)new绑定

使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:

1. 创建(或者说构造)一个全新的对象。

2. 这个新对象会被执行[[原型]]连接。

3. 这个新对象会绑定到函数调用的this

4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log(bar.a); // 2

三、优先级

默认绑定的优先级是四条规则中最低的。

1)显式绑定优先级比隐式绑定要更高

function foo() {
console.log(this.a);
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
}; obj1.foo(); // 2
obj2.foo(); // 3 obj1.foo.call(obj2); // 3
obj2.foo.call(obj1); // 2

2)new绑定比隐式绑定优先级高

function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
}; obj1.foo(2);
console.log(obj1.a); // 2 var bar = new obj1.foo(4);
console.log(obj1.a); // 2
console.log(bar.a); // 4

3)new绑定会修改显示绑定中this

function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments);
};
}
function foo(something) {
this.a = something;
} var obj1 = {};
var bar = foo.bind(obj1); //bar被硬绑定到obj1上
bar(2);
console.log( obj1.a ); // 2 var baz = new bar(3); //
console.log( obj1.a ); // 没有把obj1.a 修改为3,还是为2
console.log( baz.a ); // 3

4)判断this

1. 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。

var bar = new foo()

2. 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。

var bar = foo.call(obj2)

3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。

var bar = obj1.foo()

4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。

var bar = foo()

四、绑定例外

1)被忽略的this

如果你把null或者undefined作为this的绑定对象传入call、apply或者bind。

这些值在调用时会被忽略,实际应用的是默认绑定规则。

function foo() {
console.log(this.a);
}
var a = 2;
foo.call(null); // 2

2)间接引用

你有可能(有意或者无意地)创建一个函数的“间接引用”。

在这种情况下,调用这个函数会应用默认绑定规则。

function foo() {
console.log(this.a);
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 }; o.foo(); // 3
(p.foo = o.foo)(); // 2

赋值表达式p.foo = o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo()。

3)软绑定

如果可以给默认绑定指定一个全局对象和undefined以外的值。

那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改this的能力。

可以通过一种被称为软绑定的方法来实现我们想要的效果。

Function.prototype.softBind = function(obj) {
var fn = this;
// 捕获所有 curried 参数
var curried = [].slice.call(arguments, 1);
var bound = function() {
return fn.apply((!this || this === (window || global)) ? obj : this, curried.concat.apply(curried, arguments));
};
bound.prototype = Object.create(fn.prototype);
return bound;
};
function foo() {
console.log("name: " + this.name);
}
var obj = { name: "obj" },
obj2 = { name: "obj2" },
obj3 = { name: "obj3" }; var fooOBJ = foo.softBind( obj ); fooOBJ(); // name: obj <---- 应用了软绑定 obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2 fooOBJ.call( obj3 ); // name: obj3 setTimeout( obj2.foo, 10 );// name: obj <---- 应用了软绑定

软绑定版本的foo()可以手动将this绑定到obj2或者obj3上。

但如果应用默认绑定,则会将this绑定到obj。

最新文章

  1. 【转】C#类似Jquery的html解析类HtmlAgilityPack基础类介绍及运用
  2. Python之路【第七篇续】:I/O多路复用
  3. Sass学习之路(2)——Sass环境安装(windows版)
  4. 关于verilog中if与case语句不完整产生锁存器的问题 分类: FPGA 2014-11-08 17:39 260人阅读 评论(0) 收藏
  5. Coreseek:indexer crashed不解之谜
  6. AngularJS应用开发思维之2:数据绑定
  7. Django中Q查询及Q()对象
  8. 算法竞赛之递归——输出1-n的所有排列
  9. 使用 Laragon 在 Windows 中快速搭建 Laravel 本地开发环境 (转)
  10. TP5调用微信JSSDK 教程 —— 之异步使用
  11. python流程控制之if、 while和for 循环
  12. Liferay7 BPM门户开发之10: 通用流程实现从Servlet到Portlet(Part1)
  13. bcrelay广播包转发器
  14. 【SPL标准库专题(6)】 Datastructures:SplPriorityQueue
  15. jedis、jedisPool、jedisCluster的使用方法
  16. codeforces 424D Biathlon Track
  17. Java按位取反运算符“~”的工作原理
  18. 英语的各种 n. adj. vt. vi. 等词性解释
  19. day-19 多种优化模型下的简单神经网络tensorflow示例
  20. Informatica 常用组件Source Qualifier之一 概述

热门文章

  1. swift 代码添加按钮
  2. Object转bigdecimal
  3. Android菜鸟成长记4-button点击事件
  4. MapGIS6.7安装图文教程(完美破解)
  5. push
  6. timingFunction
  7. 爬虫--scrapy--windows上安装
  8. linux中时间的更改
  9. java多线程-Java中的Copy-On-Write容器
  10. DB2不记录事务日志