目录

1.全局的this(浏览器window):

2.一般函数的this(浏览器window):

3.作为对象方法的函数的this(调用该函数的对象):

4.对象原型链上的this:

5.构造函数中的this(有return则指向return的对象,无则默认返回this):

6.bind方法和this

7.call/apply方法中的this

8.隐式绑定的函数会丢失绑定对象。

9.this优先级

10. this在箭头函数的作用:

11. 总结:this绑定对象的判断流程


在JavaScript中This总是指向调用它所在方法的对象。在 JavaScript 中,被称为 this 的事物,指的是“拥有”当前代码的对象。this 的值,在函数中使用时,是“拥有”该函数的对象。请注意 this 并不是变量。它属于关键词。您无法改变 this 的值。当不带拥有者对象调用对象时,this 的值成为全局对象。在 web 浏览器中,全局对象就是浏览器对象。

1.全局的this(浏览器window):

console.log(this.document === document) //true
console.log(this === window) //true
this.a = 37;
console.log(window.a); //37

2.一般函数的this(浏览器window):

一般函数
function f1(){return this;
}
f1()===window; //true,global object

3.作为对象方法的函数的this(调用该函数的对象):

var o = {prop:37,f:function(){return this.prop;}
};
console.log(o.f()); //37
f作为一个对象的方法,那么作为对象的方法去调用的时候,比如o.f调用的时候,这种情况,这个this,一般会指向对象var o = {prop:37
};
function indepedent(){return this.prop;
}
o.f = indepedent;
console.log(o.f()); //37
这里并不是去看函数是再怎么样创建的,而是只要将这个函数作为对象的方法,这个o.f去调用的话,那么这个this就会指向这个o 关于this,一般来说,谁调用了方法,该方法的this就指向谁,如:
function foo(){console.log(this.a)
}
var a = 3;
var obj = {a: 2,foo: foo
};
obj.foo(); // 输出2,因为是obj调用的foo,所以foo的this指向了obj,而obj.a = 2如果存在多次调用,对象属性引用链只有上一层或者说最后一层在调用位置中起作用,如:
function foo() {console.log( this.a )
}
var obj2 = {a: 42,foo: foo
}
var obj1 = {a: 2,obj2: obj2
}
obj1.obj2.foo(); // 42

4.对象原型链上的this:

var o = {f:function(){return this.a + this.b;}
}
var p = Object.create(o);
p.a = 2;
p.b = 6;
console.log(p.f()); //8
这里p的原型是o,那么p.f的时候调用的是对象o上面的函数属性f,this也指向p

5.构造函数中的this(有return则指向return的对象,无则默认返回this):

new一个MyClass空对象,如果没有返回值,默认会返回this,有返回值,那么o就指向返回的对象了
function MyClass(){this.a = 37;
}
var o = new MyClass();
console.log(o.a); //37
function C2(){this.a = 37;return{a:38}
}
o=new C2();
console.log(o.a); //38
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:创建(或者说构造)一个全新的对象这个新对象会被执行[[Prototype]]连接这个新对象会绑定到函数调用的this如果函数没有返回其他对象,那么new表达式中的函数会自动返回这个新对象
new是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定。

6.bind方法和this

function f(){return this.a;
}
var g = f.bind({a:'test'
});
console.log(g()); //test
var o = {a:37,f:f,    g:g
};
console.log(o.f(),o.g()); //37,test

7.call/apply方法中的this

function add(c,d){return this.a + this.b+c+d;
}
var o = {a:1,b:3
};
add.call(o,5,7); //1+3+5+7 = 16 add(5,7)
add.apply(o,[10,20]); //1+3+10+20 = 34
function bar(){console.log(Object.prototype.toString.call(this));
}
bar.call(7); //[Objedt Number]// example 2
function foo( something ) {console.log( this.a, something)return this.a + something
}var obj = {a: 2
}var bar = function() {return foo.apply( obj, arguments)
}var b = bar(3); // 2 3
console.log(b); // 5
在bar函数中,foo使用apply函数绑定了obj,也就是说foo中的this将指向obj,
与此同时,使用arguments(不限制传入参数的数量)作为参数传入foo函数中;
所以在运行bar(3)的时候,首先输出obj.a也就是2和传入的3,然后foo返回了两者的相加值,所以b的值为5
也可使用bind
function foo( something ) {console.log( this.a, something)return this.a + something
}var obj = {a: 2
}var bar = foo.bind(obj)var b = bar(3); // 2 3
console.log(b); // 5

8.隐式绑定的函数会丢失绑定对象。

/*一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,
这个时候的应用默认绑定,从而把this绑定到全局对象或者undefined上,
取决于是否是严格模式。
example 1: 最常见的就是作为参数传递以及变量赋值。*/
var a = "oops, global";
function foo() {console.log( this.a )
}
var obj = {a: 2,foo: foo
}
function fn(param) {param();
};
var bar = obj.foo; // 变量赋值。虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身。
bar();  // 因此此时的bar()其实是一个普通的函数调用,因此应用了默认绑定。fn(obj.foo); /* 参数传递, 单纯传递了一个函数而已,this并没有跟函数绑在一起,所以this丢失,
应用了默认绑定这里指向了window。*/// example 2:隐式绑定丢失并不是都会指向全局对象
var name = 'tobey';
let obj = {name: 'jupy',fn: function () {console.log(this.name);}
};
let obj1 = {name: 'cindy'
}
obj1.fn = obj.fn;
obj1.fn(); //输出cindy
// 虽然丢失了 obj 的隐式绑定,但是在赋值的过程中,又建立了新的隐式绑定,这里this就指向了对象 obj1。/*example 2:this指针只在age方法的函数内指向xiaoming,在函数内部定义的函数,
this又指向undefined了!(在非strict模式下,它重新指向全局对象window!)。*/
var xiaoming = {name: '小明',birth: 1990,age: function () {function getAgeFromBirth() {var y = new Date().getFullYear();return y - this.birth;  // this处在函数里面的函数里面,那么指向的是默认的全局对象,由于使用strict模式所以是指向了undefined}return getAgeFromBirth();}
};xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined
/*
修复的办法也不是没有,(1)我们用一个that变量首先捕获this:用var that = this;
就可以放心地在方法内部定义其他函数,而不是把所有语句都堆到一个方法中。*/
var obj = {birth: 1990,getAge: function () {var b = this.birth; // 1990var that = this   // 捕捉到了this关键字所指的对象var fn = function () {return new Date().getFullYear() - that.birth; // that指向obj对象};return fn();  }
};// (2)完全修复了this的指向问题,不再需要that。
var obj = {birth: 1990,getAge: function () {var b = this.birth; // 1990var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象return fn();}
};
obj.getAge(); // 25

9.this优先级

(1)隐式绑定:如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上

# 隐式绑定
function fn() {console.log(this.name);
};
let obj = {name: 'tobey',func: fn
};
obj.func()   # tobey    '''(1)如果函数调用前存在多个对象,this指向距离调用自己最近的对象,比如这样:'''
function fn() {console.log(this.name);
};
let obj = {name: 'tobey',func: fn,
};
let obj1 = {name: 'jupy',o: obj
};
obj1.o.func()  # tobey   最近的对象是o即obj''' (2) 如果将obj对象的name删除,那么输出undefined,注意不要将作用域链和原型链弄混淆了,
obj对象虽然obj1的属性,但它两原型链并不相同,并不是父子关系,由于obj未提供name属性,
所以是undefined。'''
function fn() {console.log(this.name);
};
let obj = {func: fn,
};
let obj1 = {name: 'jupy',o: obj
};
obj1.o.func()   # 输出undefined'''(3)原型链:虽然obj对象并没有name属性,但顺着原型链,找到了产生自己的构造函数Fn,
由于Fn原型链存在name属性,所以输出时间跳跃了。'''
function Fn() {};
Fn.prototype.name = 'tobey';function fn() {console.log(this.name);
};let obj = new Fn();
obj.func = fn;let obj1 = {name: 'jupy',o: obj
};
obj1.o.func()   # 输出tobey

作用域链与原型链的区别:

  • 当访问一个变量时,解释器会先在当前作用域查找标识符,如果没有找到就去父作用域找,作用域链顶端是全局对象window,如果window都没有这个变量则报错。
  • 当在对象上访问某属性时,首选i会查找当前对象,如果没有就顺着原型链往上找,原型链顶端是null,如果全程都没找到则返一个undefined,而不是报错。

(2)显式绑定:指我们通过call、apply以及bind方法改变this的行为,相比隐式绑定,我们能清楚的感知 this 指向变化过程。

let obj1 = {name: 'tobey'
};
let obj2 = {name: 'jupy'
};
let obj3 = {name: 'echo'
}
var name = 'cindy';function fn() {console.log(this.name);
};
fn(); //cindy
// 分别通过call、apply、bind改变了函数fn的this指向
fn.call(obj1); // tobey
fn.apply(obj2); // jupy
fn.bind(obj3)(); // echo

当我们调用一个函数时,我们习惯称之为函数调用函数处于一个被动的状态;而call与apply让函数从被动变主动,函数能主动选择自己的上下文,所以这种写法我们又称之为函数应用注意,如果在使用call之类的方法改变this指向时,指向参数提供的是null或者undefined,那么 this 将指向全局对象。

call、apply与bind有什么区别?

  • 1.call、apply与bind都用于改变this绑定,但call、apply在改变this指向的同时还会执行函数,而bind在改变this后是返回一个全新的boundFcuntion绑定函数,这也是为什么上方例子中bind后还加了一对括号 ()的原因。
  • 2.bind属于硬绑定,返回的 boundFunction 的 this 指向无法再次通过bind、apply或 call 修改;call与apply的绑定只适用当前调用,调用完就没了,下次要用还得再次绑。
  • 3.call与apply功能完全相同,唯一不同的是call方法传递函数调用形参是以散列形式,而apply方法的形参是一个数组。在传参的情况下,call的性能要高于apply,因为apply在执行时还要多一步解析数组。
  • let obj1 = {name: 'tobey'
    };
    let obj2 = {name: 'jupy'
    };
    var name = 'cindy';function fn() {console.log(this.name);
    };
    // 描述一:
    fn.call(undefined); //cindy
    fn.apply(null); //cindy
    fn.bind(undefined)(); //cindy
    // 描述二
    fn.call(obj1); //tobey
    fn(); //cindy
    fn.apply(obj2); //jupyt
    fn(); //cindy
    let boundFn = fn.bind(obj1);//tobey
    boundFn.call(obj2);//tobey
    boundFn.apply(obj2);//tobey
    boundFn.bind(obj2)();//tobey
    // 描述三
    let obj = {name: 'jupy'
    };function fn(age,describe) {console.log(`我是${this.name},我的年龄是${age},我非常${describe}!`);
    };
    fn.call(obj,'26','帅');//我是jupy,我的年龄是26,我非常帅!
    fn.apply(obj,['26','帅']);//我是jupy,我的年龄是26,我非常帅!

(3)new绑定:准确来说,js中的构造函数只是使用new 调用的普通函数,它并不是一个类,最终返回的对象也不是一个实例,只是为了便于理解习惯这么说罢了。

那么new一个函数究竟发生了什么呢,大致分为三步:

  • 1.以构造器的prototype属性为原型,创建新对象
  • 2.将this(可以理解为上句创建的新对象)和调用参数传给构造器,执行;
  • 3.如果构造器没有手动返回对象,则返回第一步创建的对象

这个过程我们称之为构造调用。

 this 绑定优先级:

显式绑定 > 隐式绑定 > 默认绑定

new绑定 > 隐式绑定 > 默认绑定

默认绑定的优先级是四条规则中最低的,所以可以先不考虑它。

  • (1)隐式绑定和显式绑定哪个优先级更高?显式绑定优先级更高,也就是说在判断时应当先考虑是否可以存在显式绑定。
  • (2)new绑定隐式绑定的优先级谁更高?new绑定隐式绑定优先级高。
  • (3)new绑定显式绑定谁的优先级更高呢?注意不存在这种绑定同时生效的情景,如果写这两种代码会直接报错。
// 比较一 显式>隐式
function foo(a){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// 比较二  new>隐式
function foo(something){this.a = something
}
var obj1 = {foo: foo
}
var obj2 = {}// 隐式
obj1.foo(2);
console.log(obj1.a); // 2// 显式
obj1.foo.call(obj2,3);
console.log(obj2.a); // 3// new绑定
var bar = new obj1.foo(4)
console.log(obj1.a); // 2
console.log(bar.a); // 4// 比较三  new和显示同时出现会报错
function Fn(){this.name = '听风是风';
};
let obj = {name:'行星飞行'
}
let echo = new Fn().call(obj);//报错 Uncaught TypeError: (new Fn()).call is not a function//比较三,如果不是同时出现,可以看到,new绑定修改了硬绑定中的this,所以new绑定的优先级比显式绑定更高。
function foo(something){this.a = something
}
var obj1 = {}
var bar = foo.bind(obj1); // 显式绑定,bind方法不执行函数,返回函数
bar(2);
console.log(obj1.a); // 2var baz = new bar(3);  // new绑定
console.log(obj1.a); // 2
console.log(baz.a); // 3

10. this在箭头函数的作用:

准确来说,箭头函数中没有this,箭头函数的this指向取决于外层作用域中的this(意味着箭头函数的this无法主动修改),外层作用域或函数的this指向谁,箭头函数中的this便指向谁。

function foo() {// 返回一个箭头函数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。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new也不行!)

11. 总结:this绑定对象的判断流程

如果要判断一个运行中的函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断this的绑定对象。

  1. 由new调用?绑定到新创建的对象。
  2. 由call或者apply(或者bind)调用?绑定到指定的对象。
  3. 由上下文对象调用?绑定到那个上下文对象。
  4. 默认:在严格模式下绑定到undefined,否则绑定到全局对象。

this关键字全面剖析相关推荐

  1. 关键字深度剖析,集齐所有关键字可召唤神龙?【完】

    关键字深度剖析,集齐所有关键字可召唤神龙?[完] 1. union关键字 1.1 联合体union 1.2 union和内存布局 1.3 大小端对于union的影响 2. enum 关键字 2.1 枚 ...

  2. 关键字深度剖析,集齐所有关键字可召唤神龙?【二】

    关键字深度剖析,集齐所有关键字可召唤神龙?[二] 1. if.else 组合 1.1 if 和 else 1.1.1 结论1 1.1.2 结论2 1.1.3 结论3 1.2 bool 变量与" ...

  3. 关键字深度剖析,集齐所有关键字可召唤神龙?【三】

    关键字深度剖析,集齐所有关键字可召唤神龙?[三] 1. return关键字 1.0 before return 1.1 熟悉的问题,函数调用开辟栈帧 1.2 返回值临时变量接收的本质 1.3 retu ...

  4. 【C语言你真的学会了吗】C语言深度剖析(1)【关键字深度剖析】

    目标: 初步了解关键字分类 深刻理解变量 深刻理解定义与声明 auto关键字的理解 站在存储结构角度,理解register关键字 目录 1.关键字分类 2.第一个C程序(补充内容) 3.变量的定义和声 ...

  5. 内部类详解————匿名内部类

    内部类三连击: <内部类详解----匿名内部类> <内部类详解----局部内部类> <内部类详解----静态嵌套类> 应用场景 由于匿名内部类不利于代码的重用,因此 ...

  6. 匿名内部类属于局部内部类吗_内部类详解————匿名内部类

    应用场景 由于匿名内部类不利于代码的重用,因此,一般在确定此内部类只会使用一次时,才会使用匿名内部类. 形式 public class OutterClass { public Runnable ta ...

  7. brpc学习笔记(1)

    文章目录 一.简介及各个文档学习 1.1 bvar 1.2 bthread 1.3 client 1.4 server 1.5 内置服务 1.6 工具 参考 一.简介及各个文档学习 brpc是一款RP ...

  8. volatile关键字之全面深度剖析

    引言 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字 ...

  9. 第三节:深度剖析各类数据结构(Array、List、Queue、Stack)及线程安全问题和yeild关键字

    一. 各类数据结构比较及其线程安全问题 1. Array(数组): 分配在连续内存中,不能随意扩展,数组中数值类型必须是一致的.数组的声明有两种形式:直接定义长度,然后赋值:直接赋值. 缺点:插入数据 ...

最新文章

  1. windows server 2008 开机进度条闪过后重启_Windows系统损坏 | 无法进入系统如何正常备份数据?...
  2. loadrunner-2-12日志解析
  3. opencv 图像雾检测_OpenCV图像处理-基于OpenPose的关键点检测
  4. 开学综合症有救了!17篇最新AI论文不容错过
  5. 大白技术控 | Windows10X 模拟器简单上手体验
  6. 多维柔性作业调用_摆脱困境:从预定作业中调用安全方法
  7. python boxplot orient_Python 可视化 | Seaborn5 分钟入门 (三)——boxplot 和 violinplot
  8. 在网页中嵌入任意字体的解决方案 (insert any font)
  9. ubuntu安装ipfs
  10. JAVA 使用类的继承和接口实现多态
  11. 我的第一个MPI程序:利用矩形规则计算pi
  12. 【linux】linux 查找 或者 搜索 文件 find
  13. EasyUI 验证框使用方法
  14. 无法解析 org.apache.commons:commons-pool2:2.4.2
  15. gradle 不支持多级子模块_Apache NetBeans 11.0 正式发布 支持Java 12
  16. 用Linux编写C语言程序
  17. 网络安全-终端安全检测和防御技术
  18. 银行从业中级系列课程之——银行管理(二)监管概述,监管指标和监测指标
  19. 微信客户端如何发只有纯文字 不带图片的朋友圈动态
  20. 谷歌play支付_Google Play的新功能

热门文章

  1. iOS 测试在应用发布前后的痛点探索以及解决方案
  2. css字体样式,选择器,外观属性
  3. 南京航天航空大学计算机推免,南京航空航天大学计算机学院2016研究生推免办法...
  4. 怪异盒模型and弹性盒模型
  5. 对话北邮张平院士:不建议高校盲目设元宇宙系,但元宇宙不能不做
  6. 刘韧:我对股票最大的迷思是相信物极必反
  7. javascript-几秒后页面自动跳转
  8. Java面试之异常处理
  9. 公司停电,程序员去网吧写代码;iPhone 14将于北京时间9月8日发布;GitLab修复一个关键远程代码执行漏洞|极客头条
  10. 钉钉企业内部应用授权免登+鉴权