JavaScript中的this,刚接触JavaScript时大家都在大肆渲染说其多么多么的灵巧重要,然而自己并不关心;随着自己对JavaScript一步步深入了解,突然恍然大悟,原来它真的很重要!所以,自己花费了大约2周的时间去查贴、翻阅之前读的书籍,将this的全貌展示如下。

先出几道题:

var a = {
value: 'a',
fn:function() {
alert(this.value);
}
};
var b = a.fn;
b();

//undefined

var a = {
value: 'a',
fn:function() {
alert(this.value);
}
};
setTimeout(a.fn);
//undefined

可以认为是作为“函数”调用。换种形式写呢:

var a = {
value: 'a',
fn:function() {
alert(this.value);
}
};
var b;
(b = a.fn)();

//undefined

没问题吧?再来个:

var a = {
value: 'a',
fn:function() {
alert(this.value);
}
};
var b = {
value: 'b'
};
(b.fn = a.fn)();

//undefined

至于这个为啥this指向window,可以认为进行表达式求值时,返回的是个函数,因此作为函数调用,再看

var a = {
value: 'a',
fn:function() {
alert(this.value);
}
};
(a.fn = a.fn)();
(a.fn, a.fn, a.fn)();

// undefined

// undefined

var a = {
value: 'a',
fn:function() {
alert(this.value);
}
};
(a.fn)();

//"a"

bind绑定问题

var a = {
value: 'a',
fn:function() {
alert(this.value);
}
};
var b = {
value: 'b'
};
a.fn = a.fn.bind(b);
a.fn();

//"b"

绑定过了,就不能改了?

var a = {
value: 'a',
fn:function() {
alert(this.value);
}
};
var b = {
value: 'b'
};
a.fn = a.fn.bind(b);
new a.fn();

//undefined

ES6的箭头函数
我们知道如下的this指向window

var fn = function() {
setTimeout(function() {
alert(this.value)
});
};
fn.call({value:'a'});
//undefined

但对应的箭头函数呢?

var fn = function() {
setTimeout(() => {
alert(this.value)
});
};
fn.call({value:'a'});

//

以上总总有感于《你不知道的JavaScript》,
书中关于this的介绍还是很详细的。
还起了几个名字呢?
默认绑定(函数调用),
隐式绑定(方法调用),
显式绑定(call或apply),
new绑定(new构造函数)
还像模像样的给出了调用优先级(上面四种逆序)。。。
此书有英文版(共六本),可以去啃啃。

一、this是什么--基于调用位置的上下文;调用位置不同,this值不同。

大家都JavaScript中this存在两个误解:
(1)this指向函数自身
(2)this指向函数的作用域

作用域无法通过JavaScript代码访问,它存在于JavaScript引擎内部。每当把this和词法作用域的查找混合使用时,一定要提醒自己,这是无法实现的!

this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用位置(也就是函数的调用方式)!

示例:

  1. var foo = "golbal foo";
  2. var myObj = {foo : 'myObj foo'};
  3. var say = function(){
  4. console.log(this.foo);
  5. }
  6. myObj.say = say;
  7. myObj.say();    //结果:myObj foo
  8. say();      //结果:golbal foo

二、为什么使用this

  1. var me = {
  2. name: "fenfei"
  3. };
  4. //不使用this,调用
  5. function speak(name){
  6. console.log("Hello, I'm "+ name);
  7. }
  8. speak(me.name);     //Hello, I'm fenfei
  9. //使用this,调用
  10. function speak(){
  11. console.log("Hello, I'm "+ this.name);
  12. }
  13. speak.call(me);     //Hello, I'm fenfei

this提供了一种更优雅的方式来隐式“传递”对象引用,因此可以将API设计得更加简洁并易于复用。

三. this的四大绑定规则

1. 默认绑定--函数调用类型:独立函数调用,this指向全局对象。

  1. var a = "foo";
  2. function foo(){
  3. console.log(this.a);
  4. }
  5. foo();  // "foo"

在严格模式下,全局对象将无法使用默认绑定,因此this会绑定到undefined。

  1. var a = "foo";
  2. function foo(){
  3. "use strict";
  4. console.log(this.a);
  5. }
  6. foo();  // TypeError:this is undefined

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

  1. function foo(){
  2. console.log(this.a);
  3. }
  4. var obj1 = {
  5. a : 2,
  6. foo : foo
  7. }
  8. var obj2 = {
  9. a : 1,
  10. obj1 : obj1
  11. }
  12. obj2.obj1.foo();    //结果:2

当foo()被调用时,它的落脚点指向obj1对象,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
对象属性引用链中只有最顶层或者说最后一次层会影响调用位置。

注意:隐式丢失

常见的this绑定问题就是“隐式绑定”的函数会丢失绑定对象,也就是“默认绑定”,从而把this绑定到全局对象(严格模式下为undefined)。

  1. var a = "foo";
  2. function foo(){
  3. console.log(this.a);
  4. }
  5. var obj = {
  6. a : 2,
  7. foo : foo
  8. }
  9. var bar = obj.foo;
  10. bar();  //"foo"

虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是不带任何修饰的函数调用,因此应用了默认绑定。

  1. var a = "foo";
  2. function foo(){
  3. console.log(this.a);
  4. }
  5. function doFoo(fn){     //var fn = obj.foo
  6. fn();
  7. }
  8. var obj = {
  9. a : 2,
  10. foo : foo
  11. }
  12. doFoo(obj.foo); //"foo"
  13. setTimeout(obj.foo, 100);   //"foo"

参数传递其实就是一种隐式赋值,因此传入函数式会被隐式赋值(LHS)

3. 显示绑定

(1)call、apply
(2)硬绑定

  1. function foo(){
  2. console.log(this.a);
  3. }
  4. var obj = { a:2 };
  5. var bar = function(){
  6. foo.call(obj);
  7. };
  8. bar(); //2
  9. setTimeout( bar, 1000); //2
  10. bar.call( window ); //2

创建了函数bar(),在其内部手动调用了foo.call(obj),因此强制把foo的this绑定到了obj上。无论之后如何调用函数bar,总会手动在obj上调用foo。这种显示的强制绑定,称为“硬绑定”。

4. new绑定

new调用函数会自动执行下面操作:
(1)创建(或者说构造)一个全新的对象;
(2)这个新对象会被执行[[原型]]连接;
(3)这个新对象会绑定到函数调用的this;
(4)如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

四、优先级

了解了函数调用中this绑定的四条规则,需要做的就是找到函数的调用位置并判断对应哪条规则。

1. 函数是否是new绑定?如果是,this绑定的是新创建的对象。

  1. var bar = new Foo();

2. 函数是否通过call、apply显示绑定或硬绑定?如果是,this绑定的是指定的对象。

  1. var bar = foo.call(obj);

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

  1. var bar = obj.foo();

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

  1. var bar = foo();

五、绑定this注意点

1. 忽略this

把null或undefined作为this的绑定对象传入call、apply、bind,调用时会被忽略,实际应用的是默认绑定规则!

  1. function foo(){
  2. console.log(this.a);
  3. }
  4. var a = 1;
  5. foo.call(null, 2);          //1
  6. foo.apply(undefined, [3]);  //1

2. 间接引用

  1. function foo(){
  2. console.log(this.a);
  3. }
  4. var a = 2;
  5. var o = { a : 3,foo : foo};
  6. var p = { a : 4};
  7. o.foo();            //3
  8. (p.foo = o.foo)();  //2 间接引用
  9. var pfoo = o.foo;
  10. pfoo();         //2 隐式丢失

注意:同上述“隐式丢失”结果一样,但是过程不太一样,区分!!

3. ES6箭头函数

箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。
箭头函数的绑定无法被修改。常用于回调函数中,如事件处理器或定时器。和ES6之前代码中的this = self机制一样。

  1. function foo(){
  2. setTimeout(()=>{
  3. console.log(this.a);
  4. },100);
  5. }
  6. var obj = { a : 2};
  7. foo.call(obj);

等价于:

  1. function foo(){
  2. var self = this;
  3. setTimeout(function(){
  4. console.log(self.a);
  5. },100);
  6. }
  7. var obj = { a : 2};
  8. foo.call(obj);

六、详解如何确定this

1.(隐式绑定)如果某个对象中某个成员是个function,当从这个对象上调用这个方法时this指向当前对象。

  1. var FenFei = {
  2. firstname:"li",
  3. lastname:"gang",
  4. timeTravel:function(year){
  5. console.log(this.firstname + " " + this.lastname + " is time traveling to " + year);
  6. }
  7. }
  8. FenFei.timeTravel(2014);    //li gang is time traveling to 2014(父/拥有者对象:FenFei)

2.(隐私绑定)可以通过创建一个新的对象,来引用FenFei对象上的timeTravel方法。

  1. var Camile = {
  2. firstname:"li",
  3. lastname:"yunxia"
  4. }
  5. Camile.timeTravel = FenFei.timeTravel;
  6. Camile.timeTravel(2014);    //li yunxia is time traveling to 2014(父/拥有者对象:Camile)

注意:此示例同上述“隐式丢失”、“间接引用”区分!!!

3.(隐式丢失)使用变量保存FenFei.timeTravel方法的引用

  1. var getTimeTravel = FenFei.timeTravel;
  2. getTimeTravel(2014);    //undefined undefined is time traveling to 2014(父/拥有者对象:Window;window对象里并没有firstName和lastName属性)

PS:谨记方法中的this将指向调用它的那个父/拥有者对象!无论何时,当一个函数被调用,我们必须看方括号或者是圆括号左边紧邻的位置,如果我们看到一个引用(reference),那么传到function里面的this值就是指向这个方法所属于的那个对象,如若不然,那它就是指向全局对象的。

4. 异步调用的方法内部的this

  1. <button id="async">点击我</button>
  1. var btnDom = document.getElementById("async");
  2. btnDom.addEventListener('click',FenFei.timeTravel);//undefined undefined is time traveling to [object MouseEvent](父/拥有者对象:button)
  3. btnDom.addEventListener('click',function(e){
  4. FenFei.timeTravel(2014);//li gang is time traveling to 2014(父/拥有者对象:FenFei)
  5. });

5.(new绑定)构造函数里的this

当使用构造函数创建一个对象的实例时,构造函数里的this就是新建的实例。

  1. var TimeTravel = function(fName, lName){
  2. this.firstname = fName;
  3. this.lastname = lName;
  4. }
  5. var FenFei = new TimeTravel("li", "gang");
  6. console.log(FenFei.firstname + " " + FenFei.lastname);      //li gang

6.(显示绑定)call和apply方法设定this值

  1. var Camile = {
  2. firstname:"li",
  3. lastname:"yunxia"
  4. }
  5. FenFei.timeTravel.call(Camile,2014);        //li yunxia is time traveling to 2014(指定this对象为Camile)
  6. FenFei.timeTravel.apply(Camile,[2014]); //li yunxia is time traveling to 2014(指定this对象为Camile)

PS:注意和上述“2”处做对比

7.(显示绑定)bind将函数绑定至某个对象(ES5)

  1. function f(y){
  2. return this.x + y;
  3. }
  4. var o = {x:1};
  5. /* f.bind(o)返回一个新函数,调用g(2)会把原始的函数f()当作o的方法来调用 */
  6. var g = f.bind(o);
  7. g(2);   // 3

补充:ES5之前方法模拟bind()方法

  1. function myBind(f, o){
  2. if(f.bind){
  3. return f.bind(o);
  4. }else{
  5. return function(){
  6. return f.apply(o, arguments);
  7. }
  8. }
  9. }

bind的作用和apply,call类似都是改变函数的execute context,也就是runtime时this关键字的指向。但是使用方法略有不同。一个函数进行bind后可稍后执行。

七、补充

1. 使用new为函数创建多个实例的时候,这些实例会共享prototype。

当在一个实例里直接给this添加属性的时,会隐藏prototype中与之同名的属性。
如果想访问prototype中的属性值而不是自己的设定的属性值:
(1)删除实例自己添加的属性: delete 实例名.属性名
(2)直接访问prototype中的属性:Thing.prototype.name

  1. function Thing(){}
  2. Thing.prototype.name = "leegang";
  3. Thing.prototype.getName = function(){
  4. console.log(this.name);
  5. }
  6. Thing.prototype.setName = function(newName){
  7. this.name = newName;
  8. }
  9. Thing.prototype.deleteName = function(){
  10. delete this.name;
  11. }
  12. var thing = new Thing();    /*thing:Thing {}*/
  13. thing.setName("liyunxia");  /*thing:Thing {name: "liyunxia"}*/
  14. thing.getName();        //结果:liyunxia
  15. thing.deleteName();     /*thing:Thing {}*/
  16. thing.getName();        //结果:leegang    thing.__proto__为Thing {name: "leegang"}
  17. thing.name = "liyunxia";    /*Thing {name: "liyunxia"}*/
  18. thing.getName();        //结果:liyunxia
  19. delete thing.name;      /*thing:Thing {}*/
  20. thing.getName();        //结果:leegang thing.__proto__为Thing {name: "leegang"}

2. 柯里化

把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,如果其他的参数是必要的,返回接受余下的参数且返回结果的新函数

    1. var sum = function(x, y){
    2. return x + y;
    3. }
    4. var succ = sum.bind(null, 1);
    5. succ(2);    //3
    6. function f(y, z){
    7. return this.x + y +z;
    8. }
    9. var g = f.bind({x:1}, 2);
    10. g(3);   //6

JavaScript中的this(你不知道的JavaScript)的更多相关文章

  1. JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)

    this是什么? this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件.this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式.当一个函数被调用时,会 ...

  2. JavaScript作用域闭包(你不知道的JavaScript)

    JavaScript闭包.是JS开发project师必须深入了解的知识. 3月份自己曾撰写博客<JavaScript闭包>.博客中仅仅是简单阐述了闭包的工作过程和列举了几个演示样例,并没有 ...

  3. javaScript高级程序设计.pdf && 你不知道的JavaScript

    转: JavaScript高级程序设计 链接:https://pan.baidu.com/s/1iRQ8FjpJqutZJzgdJ8ZBKA 提取码:ndnp 你不知道的JavaScript 链接:h ...

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

    最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,这次研究了一下“this”. 当一个函数被调用时,会创建一个活动记录(执行上下文). 这个记录会包含函 ...

  5. 《你不知道的JavaScript》整理(一)——作用域、提升与闭包

    最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,里面分析了很多基础性的概念. 可以更全面深入的理解JavaScript深层面的知识点. 一.函数作用域 ...

  6. 【总结】浅谈JavaScript中的接口

    一.什么是接口 接口是面向对象JavaScript程序员的工具箱中最有用的工具之一.在设计模式中提出的可重用的面向对象设计的原则之一就是“针对接口编程而不是实现编程”,即我们所说的面向接口编程,这个概 ...

  7. JavaScript中的百变大咖~this

    原文链接:http://www.jeffjade.com/2015/08/03/2015-08-03-javascript-this/ JavaScript作为一种脚本语言身份的存在,因此被很多人认为 ...

  8. JavaScript中的正则表达式(终结篇)

    JavaScript中的正则表达式(终结篇) 在之前的几篇文章中,我们了解了正则表达式的基本语法,但那些语法不是针对于某一个特定语言的.这篇博文我们将通过下面几个部分来了解正则表达式在JavaScri ...

  9. javascript中原型(prototype)与原型链

    javascript是一门动态语言(动态语言Dynamic Programming Language:动态类型语言,意思就是类型的检查是在运行时做的,也就是常说的“弱类型”语言),没有类的概念,有cl ...

  10. JavaScript 中的对象

    JavaScript 中的对象 在 JavaScript 中,对象是数据(变量),拥有属性和方法. JavaScript 中的所有事物都是对象:字符串.数字.数组.日期,等等.   访问对象的属性 访 ...

随机推荐

  1. 【java】泛型的作用是在编译阶段防止错误输入,绕过编译就绕过泛型,可用反射验证

    package com.tn.collect; import java.lang.reflect.Method; import java.util.ArrayList; public class Fa ...

  2. Mybatis-Oralce批量插入方法

    mybatis-Oralce 中批量插入方法一:<insert id="insertBatchSelective" parameterType="java.util ...

  3. ArcGIS 网络分析[2] 在ArcMap中使用网络数据集进行五大网络分析[最短路径/服务区/最近设施点/OD成本矩阵/车辆分配]

    上一章花了大篇幅介绍网络数据集的创建,也简单说了下点线的连通性问题. 那么可以试试刀锋不锋利啦! 网络分析呢,ArcGIS提供了5个基本分析类型: 最短路径求解 服务区(服务覆盖范围) 事故突发地的最 ...

  4. Beautiful Paintings

    There are n pictures delivered for the new exhibition. The i-th painting has beauty ai. We know that ...

  5. [知了堂学习笔记]_用JS制作《飞机大作战》游戏_第1讲(素材查找和界面框架搭建)

    一.查找素材: 二.分析游戏界面框架: 登录界面.游戏界面.暂停游戏界面.玩家死亡后弹出界面:并对应的界面包含什么元素: 三.分别搭建以上四个界面: 1.登录界面与游戏界面框架(隐藏游戏界面,四个界面 ...

  6. Cleaner, more elegant, and harder to recognize (msdn blog)

    It appears that some people interpreted the title of one of my rants from many months ago, "Cle ...

  7. vue链接传参与路由传参

    1.链接传参: 例如:链接是:http://localhost:3333/#/index?id=001 我们要获取参数:console.log(this.$route.query.id):即可 2.路 ...

  8. datatable使用笔记

    这是一个使用datatable的jsp文件实例,实现了点击单元格后编辑单元格所在行的内容. <%@ page pageEncoding="UTF-8"%> <!D ...

  9. 阿里云ECS连接阿里云Redis问题

    描述 项目之前的服务器使用Windows,Redis使用阿里云的云数据库Redis版,一切正常. 后来了更换了Linux,也配置好了Redis,但连接阿里云的Redis时却怎么也连接不上 原因 ECS ...

  10. Python学习_10__python2到python3

    同样作为动态语言,python的面相对像和ruby有很多类似的地方,这里还是推荐<Ruby元编程>一书来参考学习python的面向对象.然而python并不是纯面向对象设计,所以很多rub ...