一、this指向绑定规则

在js中,this是函数当前的运行环境,它表示当前对象的一个引用;这个当****前对象
很迷,它的具体值会随着执行时所在环境的改变而改变。但是,有一个不会变的点:this永远指向它所在函数的实际调用者
,如果没有调用者,就指向全局对象【浏览器环境:window;node环境:global;ES5之后严格模式下值为undefined】

严格模式只是让函数内部的this值为undefined,并不会影响window对象本身的值

为什么会有this问题?

ECMAScript中定义了对象属性的(js中一切皆对象)的4种数据属性和2种访问器属性(getter,setter函数),这4种数据属性分别是分别是:

configurable:是否可重新配置定义,能否删除;

enumberable:是否可枚举,是否可用for…in遍历对象属性;

writable:是否可写;

value:属性值(默认undefined),声明变量不赋值的时候默认用的就是这个默认值undefined。

其他这里不赘述,我们的主角是value属性。

熟悉基本类型和引用类型的同学都知道,下面代码段中,JS引擎在内存生成一个对象,并将该对象的内存地址赋值给变量obj。要读取obj.a的值时,JS引擎先从obj拿到对象的内存地址,然后找到原始对象并返回属性a的值。属性a的值保存在a的属性描述对象的value属性中,则a的数据属性value是1;
var obj = { a: 1, foo: function() {     //do sth } }

同样,foo属性中保留的也是函数的地址,函数是被单独保存在内存中的,foo保存了函数的地址[即foo的数据属性value中保存的是该函数的地址];让我们换一种写法:
function foo(){ //do sth } var obj = { a: 1,   foo }
函数foo是一个独立的值,我们可以通过不同的方式访问它从而使它在不同的环境执行
foo(); //全局环境 obj.foo(); //obj环境
JS允许我们在函数体中引用当前环境的其他变量。下列代码中,函数foo中引用了变量a,a由foo的运行环境提供。
function foo() { console.log(a) } var a = 'window'; var obj = {   a: 'obj',
  foo:foo }
我们已经知道函数是可以运行在不同上下文(执行环境),我们需要一种机制获取函数内部当前上下文(执行环境),而this的出现,就是为了指示函数的当前运行的环境。
function foo() { console.log(this.a) } var a = 'window'; var obj = { a: 'obj',
foo:foo } foo(); //全局环境中,this.a指向全局的a obj.foo(); //obj环境中,this.a指向obj环境的a
那么如何区分出函数的执行环境呢?这个this到底是谁呢?且看看this的绑定规则。

在非箭头函数中this的指向存在4种绑定规则,下面对这些种规则分别进行介绍:

* 默认绑定规则: 默认指向window, 函数独立调用时指向全局对象 // 默认指向全局对象
console.log("1",this);//Window or global(node) console.log(this ===
window);//tru
下面列举一些函数被独立调用的场景:
//直接调用function声明的函数,this指向Window对象 function foo(){console.log("2",this);}
foo(); window.foo();
将匿名函数赋值给某个变量,再通过该变量调用,也属于独立调用的情况。此时this指向Window对象,严格模式下为undefined
var foo = function() { // "use strict"; console.log("3",this); } foo();//window
//IIFE,【(function(){})(); or (function(){}())】其实也可以拆解成上面类似的场景,
//声明一个匿名函数,立即调它,匿名函数的调用具有全局性,this指向Window对象 (function() {
console.log("4",this); })() //闭包【当函数执行的时候,导致函数被定义并被抛出就会产生闭包】中: varobj = { a:
'aaa', b: function() { function c() { console.log('c---',this); //window }
        return c; } } obj.b()();
//函数c是在这里才被调用的,【obj.b()返回函数c】相当于独立调用c,所以this指向window
2. 隐式绑定规则:谁调用就指向谁(通过对象.函数属性() 方式调用,函数中的this指向该对象)this是在执行函数时才确定的。

通过obj.funAttr()调用,函数funAttr()中this指向obj
//对象的方法调用,this指向对象本身 var obj = { aa: this, //对象属性中,this指向window func:
function() {       console.log(this); //obj         function a(){
console.log(this);//window } a(); //因为在这里函数是独立调用的,内部this指向window } } obj.func();
当对象链式调用时,this指向最靠近函数的对象
var a='a'; function foo(){ console.log(this.a); } var obj1 = { a: 'obj1',
obj2: { a: 'obj2', foo } } obj1.obj2.foo(); //obj2
共享实例prototype中的this指向调用它的实例对象
String.prototype.logthis = function () { return "我是"+this+", "+this+"真棒hahaha"
} console.log("溪森堡".logthis()) //我是溪森堡, 溪森堡真棒hahaha Function.prototype.logthis2
= function () { return this; //指向add函数 } var add = function(x,y) { return x+y;
} add.logthis2();
事件绑定和监听函数(非箭头函数)中,this指向被绑定/监听的元素对象
var btn = document.getElementById("btn"); btn.onclick = function() {
console.log(this); //<button id="btn">点我</button> }
btn.addEventListener('click', function() { console.log(this); //<button
id="btn">点我</button> })
内联事件中this指向:

a. 内联处理函数调用代码时,this指向监听器对应的DOM元素;

b. 代码被包含在函数内部执行(直接调用)时,非严格模式指向全局对象window,严格模式指向undefined
<button onclick="console.log(this)">click here</button> <button
onclick="(function(){console.log(this)})()">click here</button> <button
onclick="(function(){'use strict';console.log(this)})()">click here</button>

回调函数中(定时函数内部的回调函数,forEach,sort等)this默认指向全局对象window;

函数foo在定时器中是作为回调函数来执行的,回到主线程执行时在是全局执行环境中执行,涉及Event
Loop这里暂且不细说,setTimeout内部可拆解成下面这样,这时候this指向window
setTimeout(callback) { if (回调条件满足) ( callback() ) }
隐式绑定中还有一个比较特殊的例子:
var length = 10; function fn() { console.log(this.length); } var obj = {
a:'a', length: 5,    method: function(fn) {        fn(); //10
      arguments[0](); //5 } }; obj.method(fn, 1, 2, 3, 'a');
fn()独立调用函数,没有疑问this指向window,输出10;

arguments[0]()这个有点奇怪,arguments[0]不就是fn么,那不也是相当与fn()吗?4哪来的?

我们先回顾一下什么是arguments。

MDN中的定义:arguments 是一个对应于传递给函数的参数的类数组对象。

说白了就是实际参数所组成的类数组对象(要拿到形参个数可用函数名.length);而类数组,并不是真正的数组,它可以通过length属性拿到实参个数,并且这些参数是以0开始为key,实参值为value的键值对的形式呈现的。它与真正的数组不同,它没有数组内置函数如forEach()、map()等。

我们回到上面arguments[0]()的问题,obj.method(fn, 1, 2, 3, ‘a’);
函数method内部得到的arguments对象结构如下:

{

0: fn,

1: 1,

2: 2,

3: 3,

4: ‘a’

}

注意,我们说这是一个类数组对象,我们知道访问对象属性的方法有:obj.attr 和 obj[attr]

把这里的arguments看成obj,是不是就明晰了,这里其实就是通过obj[‘attr’]的方式调用fn,发生了隐式绑定,fn中的this指向了arguments对象,它同时是一个类数组,具有length属性,所以this.length为实际参数个数,也就是5。

再看一个关于隐式绑定的例子:
var a='a'; function foo(){ console.log(this.a); } var obj1 = { a: 'obj1', foo:
function(){ setTimeout(function(){ console.log(this.a); },0) } } var obj2 = {
a: 'obj2', foo } var obj3 = { a: 'obj3', foo } obj1.foo(); //a,回调函数中
setTimeout(obj2.foo, 0); //a,或掉函数中,且是独立调用的 setTimeout(function(){
    obj3.foo(); //obj3,虽然在回调函数中,但是是通过对象调用,隐式绑定 },0);
如果我们需要this指向对应的objXXX对象,可以这样做:

a. 提前保留this的值
var obj1 = { a: 'obj1', foo: function(){ let that = this;
setTimeout(function(){ console.log(that.a); },0) } } obj1.foo(); //obj1
b. 强制改变this指向(后面会提到,显式绑定)
setTimeout(obj2.foo.bind(obj2), 0);  //obj2
像forEach等高阶函数最后一个可选参数可指定this指向

在高阶函数中,父函数是可以决定子函数中this指向的
new bar(fn) { fn(); new fn(); fn.bind(obj)(); }
在隐式绑定规则中,有两种情况会导致隐式绑定失败,此时this绑定规则变成默认绑定:

a. 变量赋值的情况【隐式丢失】
function foo() { console.log('foo---',this); //window } var obj = { a: 'aaa',
b: foo } obj.b(); //obj //隐式丢失(变量赋值情况) var bar = obj.b; //这里只是引用了函数,拿到存放函数的地址而已
bar(); //window, 在这里才调用的,相当于函数独立调用
b.参数赋值的情况, 在预编译过程中,实参被赋值为形参是一个值拷贝的过程(浅拷贝,这里指拷贝函数的引用地址)
function foo() { console.log('foo---',this); //window } function bar(fn) {
fn(); } var obj = { a: 'aaa', foo } bar(obj.foo);
3. 显式绑定规则: 用call/bind/apply显式地为一个函数指定其this指向,这里不赘述用法以及区别 后面专门写一篇文章介绍。
function foo() {   console.log(this); } var obj1 = { attr: 1 } var obj2 = {
attr:2 } foo(); //window foo.call(obj1); //obj1 foo.call(obj2); //obj2
* new绑定【在用构造函数实例化对象的时候】
了解过new内部实现原理的同学都知道,new操作符构造函数其实内部干了这几件事情:

a. 创建一个空的js对象{};

b. 为在步骤a创建的对象添加__proto__属性并将该属性指向构造函数的protptype;

c. 改变this指向并传递参数(给构造函数绑定新构造的对象为this,给新对象添加对应的属性和方法);

d. 如果构造函数返回了对象,直接返回该对象,否则返回上述步骤构造的新的实例对象。
function Foo(a) { this.a = a; console.log('Foo---',this);//new绑定中this指向实例对象 p
return this; //当构造函数中返回基本类型的值,this仍然是指向实例对象 // return 1;
//当构造函数中返回基本引用值,this被改变成指向该引用值 // return {}; } var p = new Foo('aa')
new绑定规则中,this指向实例对象p。 如果构造函数返回的不是一个对象,则p为一个新构造出来的对象;如果返回的是一个对象,p指向构造函数返回的对象。

function P (name, age) { this.name = name; this.age = age; this.foo = function
() { console.log(this.name) } this.bar = function () { return function () {
console.log(this.age) } } } var name = 'xsb'; var age = 18; var p = new P('xx',
100) p.foo() //new绑定 构造函数中this指向p,输出xx console.log(p.age); //100
p.bar()() //独立调用返回的函数,this指向window, 输出18
二、上述规则的优先级

上面讲述了this指向的四种绑定规则,当两种或多种规则存在的情况下,谁的优先级更高?接下来通过几个例子验证一下上述四种规则的优先级:

1. 显式绑定优先级大于隐式绑定大于默认绑定
var obj1 = { attr: 'aaa', func: test } var obj2 = { attr: 'bbb', func: test }
function test() { console.log(this); } test() //默认,window obj1.func() //隐式,obj1
obj1.func.call(obj2) //显式,obj2
2. new绑定优先级比显式绑定高
function foo(attr) { this.attr = attr; } var obj = {};
//显式绑定,bind拷贝原函数并指定了this指向,bar和foo不是同一个函数了 var bar = foo.bind(obj); //bar
是通过foo.bind返回的一个函数 this指向obj     bar(2);          console.log(obj.attr); //2
    //以bar为构造函数构造一个实例对象,是一个新的对象,跟obj没有任何关系     var baz = new
bar(3); //指向实例对象baz;     console.log(obj.attr); //2     console.log(baz.attr);
//3
第6行代码等同于 var baz = new (foo.bind(obj))(3);
最后baz.attr输出3说明this并非绑定obj而是绑定了new操作符构造出的新的实例对象;

综上得出结论:

new绑定规则 > 显式绑定规则 > 隐式绑定规则 > 默认绑定规则

三、箭头函数中this指向

在es6的箭头函数出现以前,上述绑定规则是普适的,但是在你看到箭头函数时,请将上面的每一句话都忘掉!

箭头函数中的this指向取决于其父环境中的this指向【箭头函数内部是没有this的,它直接拿定义时的上下文(this指向)作为当前this指向,而不是使用时所在的作用域指向的对象】

独立调用非箭头函数,函数嵌套this不继承父作用域上下文,指向全局对象window;
functionfoo() {     console.log(this); // obj     function test() {        
console.log(this); //window     } test(); } var obj = { a: 1, foo } obj.foo();
箭头函数中this只由父作用域决定,哪怕这里test()是独立调用的。
functionfoo() {     console.log(this); // obj var test = () => {
console.log(this); //obj }     test();//独立调用箭头函数 } var obj = { a: 1, foo }
obj.foo();
foo函数执行完成,返回函数test
function foo(){ console.log(this); //window function bar(){ console.log(this);
//obj2 }   return bar; } var obj = { attr: '111', foo } var obj2 = { attr:
'222', foo } var baz = foo().call(obj2);
显式绑定不能对箭头函数起作用:
function foo(){ console.log(this); //window
  var bar = () => console.log(this);  //window return bar; } var obj = { attr:
'111', foo } var obj2 = { attr: '222', foo } var baz = foo().call(obj2);
new绑定无效,甚至会直接报错,箭头函数不能作为构造函数
var Foo = () => console.log(this); new Foo();

var A = { name: 'A', say: function() { var s = () => console.log(this.name);
return s; } } var sayHello = A.say(); //隐式绑定,say方法中this指向A;
//执行say方法返回的一个箭头函数s,this由say方法中作用域对象this决定,故输出A sayHello(); //A var B = { name:
'B' } //显式绑定对箭头函数无效,故只要say方法中this不变,下列输出也一直是A sayHello.call(B); //A
sayHello.call(); //A
最后做一道练习综合回顾下,

运行一下,看看你做对了几个,下面是解析:

*
通过对象obj1调用一个普通函数,this指向obj1,属于隐式绑定,输出1;

*
为方法fn1显式地绑定this指向obj2,输出2;

*
fn2是一个箭头函数,其this指向定义位置的父作用于this即window,输出window;

*
同3,显式绑定对箭头函数不起作用,输出window;

*
函数fn3执行之后返回一个普通函数,相当于独立调用fn3,this指向window;

*
为fn3执行之后返回的函数显式地绑定this指向obj2,shuchu 2;

*
为fn3绑定this指向obj2,但是独立调用了其返回的函数,该函数this窒息那个window;

*

fn4是一个普通函数,执行之后返回箭头函数。通过对象方法调用,fn4中this窒息那个obj1,再执行其返回的箭头函数,this由其父作用于fn4中this决定,输出1;

*
fn4中this指向obj1(隐式绑定),显式绑定对其返回的箭头函数无效,故还是输出1;

*
fn4的this被显式绑定成obj2,其返回的箭头函数中this由父级作用域决定。输出2。

由此可见,要改变箭头函数中this的值,只能通过更改其父作用域指向的对象—this来实现。

再贴几道题加深印象:
var name = 'global'; var obj = { name: 'local', foo: function(){ this.name =
'foo';     }.bind(window) // 显式绑定 };
var bar = new obj.foo(); // new绑定,优先级比显式高,this指向bar,构造函数中给bar对象增加了name属性,值为foo,输出foo
setTimeout(function() { console.log(window.name);// global
    console.log(this.name); // 回调函数中this指向window,输出global }, 0);
console.log(bar.name); // foo var bar3 = bar2 = bar; bar2.name = 'foo2';
console.log(bar3.name);// foo2, bar3,bar2,bar保存的都只是对象的地址,改变了实际对象的 var num = 10
var obj = {num: 20} obj.fn = (function (num) { this.num = num * 3
//IIFE,this指向window,60   console.log('num---',num) //20 num++ //20++ return
function (n) { this.num += n console.log('num===',num) num++ console.log(num) }
})(obj.num) // IIFE,定义的时候就会执行 var fn = obj.fn //函数别名,保存函数的引用 fn(5)  obj.fn(10)
// this指向obj console.log(num, obj.num)
这里将一个立即执行函数赋值给obj.fn,将 IIFE 分配给一个变量,不是存储 IIFE 本身,而是存储 IIFE
执行后返回的结果。obj.fn和fn的数据属性value其实都是指向下列函数的地址。
ƒ (n) {     this.num += n     console.log('num===',num)     num++
    console.log(num) }
fn(5) 独立调用函数,this指向window,this.num即window.num,window.num在IIFE中被修改成60。
故this.num = 60 + 5 即65

num++, IIFE中被修改成21,则是21++; 再次访问num的时候值已经被修改成22,故输出22;

obj.fn(10) 隐式绑定,上面代码块中this指向obj; this.num = 20 + 10  即30;

num的值在上一次函数调用值被修改成22, num++即22++,输出num为23;

console.log(num, obj.num) 直接访问全局的num, 在执行fn(5)的时候window.num被修改成65,故第一项输出65;
obj.num 参考上一步解析,输出30

相关资料:

<>《2w字大章 38道面试题》彻底理清JS中this指向问题

JavaScript的this原理-阮一峰

<>JS中return一个函数与直接return一个函数变量的区别

<>IIFE(立即调用函数表达式)

技术
©2019-2020 Toolsou All rights reserved,
在算法研究过程中如何进行算法创新七大排序算法(java代码)MYSQL中的索引与事务———javaweb(8)(面试必考)2022蓝桥杯JavaB组省赛试题网络安全-wifi攻防网络层协议——ICMP协议MySQL查询表中指定条件下的最新记录JavaSE笔记(一)Java基础语法mysql 查询条件之外的数据_mysql 查询符合条件的数据qt使用数据库sqlite