this解析 记录分享

2021-4-22    前端达人

this指向的问题也是JavaScript中的难点之一了,也是面试常问的问题,很多时候对this指向的问题就很懵逼,明明应该是指向他,为什么又指向他了…所以我就学习了一下这方面的知识,整理了一下,希望能够帮助大家

为什么要用this?

首先看一段代码

function identify(){
    return this.name.toUpperCase()
}
function speak(){
    var greeting = '你好,我是'+identify.call(this)
    console.log(greeting)
}

var me ={
    name:'kyle'
}

var you ={
    name:"reader"
}
identify.call(me) 
identify.call(you) 

speak.call(me) //?  你好,我是KYLE
speak.call(you) //? 你好,我是READER 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

上面的这段代码中可以从不同的上下文对象 me 和 you 中重复的使用identify函数和speak函数
如果你不使用this的话 你就要显式的将上下文对象作为参数传递进去,比如这样:

function identify(context){
    return context.name.toUpperCase()
}
function speak(context){
    var greeting = '你好,我是'+identify(context)
    console.log(greeting)
}

var me ={
    name:'kyle'
}

var you ={
    name:"reader"
}
identify(me)
identify(you) 

speak(me) 
speak(you) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

就像这样,这样看起来就不想上面那样简洁了,你要把一个对象传来传去的

认识this

刚见到this的时候 觉得this指向是这个函数自身,或者是函数的作用域,后来发现其实不是这样的的,不过也不能说错了,因为有些情况确实是这样的,比如这样:

function foo(num){
    console.log('foo'+ num)
    this.count ++ 
}
foo.count = 0

var i;
for(i = 0;i<10;i++){
    if(i>5){
        foo.call(foo,i)
    }
}
console.log(foo.count) //4 这样的话 this指向了foo本身  foo上面的count属性++ 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

无法指向函数作用域

var a = 3
function foo() {
    var a = 2;
    bar.call(foo);
}
function bar() {
    console.log( this.a );
}
foo(); // undefined 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

我们要记住非常重要的一点:this是在运行的时候进行绑定的,而不是在定义的时候绑定,this的绑定跟函数声明的位置没有关系,主要是取决于函数的调用方式,想要找到this指向谁,我们就要看懂函数是怎么调用的。

绑定规则

1.默认绑定

当一个独立函数正常调用的时候,不带任何修饰的调用

// 非严格模式下
var a = 3
function foo(){
    console.log(this.a) //a
}
foo() 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这种情况下 this.a被解析成了了 全局变量a,this指向是全局对象

// 严格模式下
var a = 3
function foo(){
    "use strict" 
    console.log(this.a) //TypeError
}
foo() 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

严格模式下 this不会指向全局对象 this绑定的是undefined

2.隐式绑定

调用位置上是否有上下文对象

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

调用位置会使用obj上下文对象来引用函数,foo被调用的时候 他的落脚点指向是obj对象,隐式绑定的规则就会把this指向这个上下文对象。所以this.a就跟 obj.a是一样的

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

当多层调用的时候 只有最后一层才会影响函数的调用位置 比如上面这个 this绑定的还是 obj 而不是obj2

注意

隐式绑定会出现隐式丢失的问题,会失去绑定对象,最后应用默认绑定

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

bar 是 obj.foo的一个引用 他引用的是foo函数本身,此时bar就是一个不带任何修饰的函数调用 应用默认绑定

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

参数传递也是一种隐式赋值,回调函数丢失this是非常常见的…

3.显式绑定

隐式绑定的时候我们必须在一个对象内部包含一个指向函数的属性,然后通过属性间接引用函数,把这个this间接隐式的绑定到这个对象上
如果我们不想在对象内部包含函数的引用 ,而想在某个对象上强制调用函数
我们可以把这个函数绑定到对象的原型上,也算是不用再对象内部包含函数了吧…
更好的办法是我们可以使用函数的 call() apply() bind() 这种方法

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

var obj = {
    a:2
}
foo.call(obj) //2
foo.apply(obj) //2 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如果你第一个参数传入的是一个原始类型 比如字符串 布尔 数字作为绑定对象 这些原始类型会被转换为 对象的形式 new String() new Number()…

硬绑定

Function.prototype.bind()
function foo(){
    console.log(this.a)
}

var obj = {
    a:2
}
var obj2 = {
    a:3
}
var bar = foo.bind(obj)  //会返回一个硬编码的新函数 他会把参数设置为this的上下文
bar.call(obj2) //2  返回的新函数 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

有些api 方法 会提供一个可选参数 context 其作用跟bind一样 确保你的回调函数使用指定的this 比如 array.forEach(fn,context)…

4.new绑定

使用new 来调用函数的时候会执行以下操作
1.创建一个全新的对象
2.这个新对象会被执行原型的链接
3.新对象会绑定到函数调用的this
4.如果没有返回其他的对象,那么函数会自动返回这个对象

function Foo(a){
    this.a = a
}
var bar = new Foo(2)
console.log(bar.a) //2 
  • 1
  • 2
  • 3
  • 4
  • 5

使用new 来调用Foo函数 会构造一个新对象并把它绑定到Foo调用中的this上 然后返回了

优先级

函数不带任何修饰的时候单独调用才会触发默认绑定 所以说默认绑定是优先级最低的了

那剩下三个规则哪个的优先级最高?

显示绑定跟隐式绑定比较

function foo(){
    console.log(this.a)
}
var obj1 = {
    a:1,
    foo:foo
}

var obj2 = {
    a:2,
    foo:foo
}
obj1.foo() //1
obj2.foo() //2

obj1.foo.call(obj2) //2
obj2.foo.call(obj1) //1 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

可以看到 显示绑定的优先级还是更高一点

new 绑定跟隐式绑定比较

function foo(arg){
    this.a = arg
}

var obj1 ={
    foo:foo
}
var obj2 ={}

obj1.foo(2)
console.log(obj1.a) //2

var bar = new obj1.foo(4)
console.log(obj1.a) //2
console.log(bar.a) //4 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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

new 绑定跟显示绑定比较

new跟call apply无法一起使用 无法通过new foo.call(obj),试一下硬绑定

在这里插入代码片 
  • 1
function foo(arg){
    this.a = arg
}
var obj1 ={}
var bar = foo.bind(obj1)
bar(3)
console.log(obj1.a) //3

var baz = new bar(4)
console.log(baz.a) //4
console.log(obj1.a) //3 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

new 调用bar修改了硬绑定时候的 函数的this new的优先级高一点

所以我们可以根据下面的优先级规则进行判断了

1.函数是否在new中调用 是的话this绑定新创建的对象 var bar = new Foo()
2.函数是否通过call apply 显示绑定或者是 bind硬绑定 如果是的话this指向指定的对象 foo.call(obj)
3.函数是否在某个上下文中调用 隐式绑定,如果是 this绑定那个上下文对象 注意绑定丢失的问题
4.如果都不是 就是默认绑定非严格模式下绑定的是全局对象 严格模式下绑定的是undefined

绑定例外

1.将null和undefined作为call apply参数 作为this绑定对象的时候 这些值会被忽略 应用的是默认绑定

var a =3
function foo(){
    console.log(this.a) //3
}
foo.call(null) 
  • 1
  • 2
  • 3
  • 4
  • 5

2.箭头函数

function foo(){
    return ()=>{
        console.log(this.a)
    }
}
var obj1 = {
    a:3
}
var obj2 = {
    a:4
}
var bar = foo.call(obj1)
bar.call(obj2) //3  this绑定的是obj1 而不是obj2!!! 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在看一个

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

箭头函数不使用this绑定的四种规则,而是根据外层作用域来决定this的,外层作用域的this绑定的是什么 他的this就是什么





转自:csdn 论坛 作者:Selfimpr欧

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服

分享本文至:

日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档