IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> this指向问题全面解析 -> 正文阅读

[JavaScript知识库]this指向问题全面解析

1.1 为什么要用this

1.2 误解

1.2.1 指向自身

function foo(num) { 
 console.log( "foo: " + num ); 
 // 记录 foo 被调用的次数
 this.count++; 
} 
foo.count = 0; 
var i; 
for (i=0; i<10; i++) { 
 if (i > 5) { 
 foo( i ); 
 } 
} 
// foo: 6 
// foo: 7 
// foo: 8 
// foo: 9 
// foo 被调用了多少次?
console.log( foo.count ); // 0 -- 什么?!
console.log(count); // NAN  没报错

foo函数调用了4次,但foo.count仍然为0, 实际上我们是在全局作用域下创建了count这个变量,但并未赋值,所以是NAN,即此时foo里面的this不是指向自身,而是指向全局window

1.2.2 指向它的作用域

function foo() { 
 var a = 2; 
 this.bar(); 
} 
function bar() { 
 console.log( this.a ); 
} 
foo(); // ReferenceError: a is not defined

报错,说明this不指向它的作用域,所以不能通过作用域来查找或者将this跟作用域联系起来使用

1.3 this到底指向什么

  • this是在运行时才被绑定的
  • this的绑定和函数声明的位置没有关系,只取决于函数的调用方式
  • 当一个函数被调用时,会创建一个活动记录(执行上下文),this就是这个记录的一个属性,会在函数执行过程中用到

第二章 this全面解析

2.1 绑定规则

2.1.1 默认绑定

当函数是直接使用且不带任何修饰时(独立函数调用),会使用默认绑定规则

function foo() { 
 console.log( this.a ); 
} 

var a = 2; 
foo(); // 2

如果使用严格模式,则不能将全局对象用于默认绑定,因此this会绑定到undefined

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

这里有一个比较重要的细节,虽然this的绑定规则完全取决于调用位置,但是只有foo()运行在非严格模式下时,默认绑定才能绑定到全局对象,在严格模式下调用foo()则不影响默认绑定

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

注意:对于默认绑定来说,决定 this 绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式,this 会被绑定到 undefined,否则this 会被绑定到全局对象。

2.1.2 隐式绑定

这条规则则是需要考虑函数调用位置是否有上下文对象,

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

调用位置会是用obj的上下文来引用函数,当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象中

对象属性链中只有上一层或者最后一层在调用位置中起作用,如

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

隐式丢失

即被绑定的函数会丢失绑定对象,就会应用默认绑定

function foo() { 
 console.log( this.a ); 
} 
var obj = { 
 a: 2, 
 foo: foo 
}; 
var bar = obj.foo; // 函数别名!
 
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global"

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

一种更微妙、更常见并且更出乎意料的情况发生在传入回调函数时:

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"

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一

个例子一样

2.1.3 显示绑定

直接指定this的绑定对象,称为显示绑定,例如可以将函数用call或apply指定this的绑定对象

call和apply的使用

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

通过 foo.call(…),我们可以在调用 foo 时强制把它的 this 绑定到 obj 上。

如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作 this 的绑定对象,这个原始值会被转换成它的对象形式(也就是 new String(…)、new Boolean(…) 或者new Number(…))。这通常被称为“装箱”

硬绑定

硬绑定可以解决隐式丢失的问题,例子如下:

function foo() {  
    console.log( this.a ); 
}
var obj = {  
    a:2 
};
var bar = function() {  
    foo.call( obj );  // 强制把foo的this绑定在obj上
                     }; 
bar(); // 2 setTimeout( bar, 100 ); // 2 // 硬绑定的 bar 不可能再修改它的 this 
 bar.call( window ); // 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

另一种使用方法是创建一个可以重复使用的辅助函数:

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

2.1.4 new绑定

使用new来调用函数时,会自动执行下面的操作:

  • 创建或者说构造一个新对象
  • 这个新对象会执行[[prototype]]连接
  • 这个新对象会绑定到函数调用的this
  • 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
function foo(a) {  
    this.a = a; 
} 
var bar = new foo(2); 
console.log( bar.a ); // 2

使用 new 来调用 foo(…) 时,我们会构造一个新对象并把它绑定到 foo(…) 调用中的 this上。new 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定。

2.2 优先级

现在可以通过判断函数的调用位置来判断应用哪一条规则,但如果同时可以应用多条规则的话,就需要知道他们的优先级

隐式绑定和显式绑定比较

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

可以看到,显式绑定优先级更高,也就是说在判断时应当先考虑是否可以存在显式绑定。

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 );  // 即判断此时的this指向谁,
console.log( obj1.a ); // 2 
console.log( bar.a ); // 4

可以看到 new 绑定比隐式绑定优先级高

new绑定与显示绑定的比较

new foo.call(obj1)  // TypeError: foo.call is not a constructor

new 和 call/apply 无法一起使用,因此无法通过 new foo.call(obj1) 来直接进行测试。但是我们可以使用硬绑定来测试它俩的优先级。

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

bar 被硬绑定到 obj1 上,但是 new bar(3) 并没有像我们预计的那样把 obj1.a 修改为 3。相反,new 修改了硬绑定(到 obj1 的)调用 bar(…) 中的 this。因为使用了new 绑定,我们得到了一个名字为 baz 的新对象,并且 baz.a 的值是 3。因此,new绑定的优先级高于显示绑定

总结

  • 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。var bar = new foo()

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

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

  • 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。var bar = foo()

软绑定

if (!Function.prototype.softBind) {  
    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;  }; }

2.3 箭头函数

箭头函数不使用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 !
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-19 01:03:52  更:2022-02-19 01:05:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:10:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码