zoukankan      html  css  js  c++  java
  • 关于JavaScript中的This-面试必备知识

    关于this

    this既不是指向函数自身也不是指向函数的词法作用域,this实际上是在函数被调用时发生的绑定,
    它指向什么完全取决于函数在哪里被调用。

    this的调用位置

    所谓的调用位置就是函数在代码中被调用的位置,可以通过分析调用栈来找到它的调用位置。

    this的绑定规则

    默认绑定

    默认的this是指向全局对象的

    function foo(){ 
        console.log(this.a)    
    }
    var a=2;
    foo() //输出2
    

    上面的函数foo是在全局作用域声明的,变量a也是,foo()的调用位置很明显是在全局作用域里。
    这里的this指向的实际是全局对象。
    注意:严格模式下,全局对象无法被默认绑定,所以this会绑定到undefined。

    隐式绑定

    当函数引用有上下文对象的时候,隐式规则会把函数调用中的this绑定到上下文对象。
    应用该规则首先要考虑的是调用位置是否存在上下文对象。简单说是否包含在某个对象里。

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

    这里foo被当作引用属性添加到了obj对象里,但是严格意义上来说这个函数不属于obj对象,即使它变为了obj的一个属性值,
    但是后面调用的时候使用了obj这个上下文来引用foo函数,所以可以理解成,函数被调用时obj对象包含它。(强调调用时)
    因为是通过obj对象调用foo的,所以这里的this指向是obj(上下文对象)。
    如果存在多层对象属性引用链,根据最后一层对象确定this的指向。

    function foo(){
        console.log(this.x)    
    }
    var b = {
        x:3
        foo:foo
    }
    var a= {
       x:5
       b:b
    }
    a.b.foo() //3
    

    在这个调用链的最外层是b,所以this指向的是b,进而得知this.x就是b.x等于3。

    还有一种情况就是把函数作为参数作为传递

    function foo(){
        console.log(this.a)
    }
    function doFoo(fn){
        // a =4  
        //接收参数等价于对fn赋值
    
        fn(); // 调用位置
    }
    var obj = {
        a:2,
        foo:foo
    };
    var a = "oops,gloal";//a是全局对象的属性,nodejs下去掉var就行。
    
    doFoo(obj.foo); //输出oops,gloal
    

    为什么输出的内容不是2呢,这里就设置的隐式赋值的问题,函数doFoo被调用的时候
    传入参数,相当于给它的参数fn做了一个赋值操作(RHS查询),foo函数调用位置是在doFoo内
    所以它会先找这里面有没有a,没有继续往上找也就是全局作用域了,然后它找到了a,最后得到了我们的输出结果。
    如果我们把上面a=4的放开就会发现this.a的结果此时变为了4。
    需要注意的是这段代码需要在浏览器环境下执行,node会出现undefined,去掉声明var就行了。

    此外我们还可以得知foo就是一个回调函数,这里回调函数丢失了this的绑定,我们实际上要使用的是
    obj里面的a,但是结果显示并非如此,那么我们如何通过this获取obj里面a的值呢,这就是下一部分要说的了。

    显式绑定

    在解决前面问题之前呢,这里引入显示绑定的概念。
    在JavaScript中我们可以通过call和apply这两个方法,初步将this绑定到指定的对象上去。
    因为我们可以直接指定this绑定的对象,所以叫它显式绑定。
    下面就说下这两个函数是如何工作的

    call和apply的使用

    它的语法是这样的

    func.call([thisArg[, arg1, arg2, ...argN]])
    

    它的第一参数代表是一个对象,这个对象会绑定到this上,接着在调用函数时指定这个this
    第二个参数开始后面是都是要传入的参数,通过逗号间隔开。举个简单的例子

    function foo(x,y,z){
        console.log(this.a,x,y,z)
    }
    var obj = {
        a:2
        }
    foo.call(obj,1,2,3) //输出2 1 2 3
    

    通过foo.call,在调用foo时我们强制把this绑定到了obj上去。
    apply的使用方法和call是一样的,区别就在于参数部分。
    apply的语法是

    func.apply(thisArg, [ argsArray])
    

    它和call唯一的区别就是参数上的区别,第一个参数是this要绑定的对象,第二个参数成了一个数组的形式。
    这个数组在传入函数的时候,实际上会进行类型python语言的拆包操作(不知道这么说对不对,意思大概是对的)。
    一个简单的例子

    function foo(something) {
        console.log(this.a, something);
        return this.a + something;
    }
    
    var obj = {
        a: 2
    };
    var bar = function () {
        return foo.apply(obj, arguments); //this绑定到obj对象
    };
    var b = bar(3); // 输出:2 3
    

    因为我们使用apply进行显式绑定,所以后面无论怎么调用bar
    它都会去将this绑定到obj之后调用foo函数。这种绑定方式是一种显式的强制性的,所以我们称之为硬绑定。
    因为硬绑定这种模式经常使用,所以ES5提供了内置方法Function.prototype.bind,来简化这个操作。
    在使用之前先了解下它的语法

    let boundFunc = func.bind(thisArg[, arg1[, arg2[, ...argN]]])
    

    bind()方法主要就是将函数绑定到某个对象,bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()中的第一个参数的值,例如:func.bind(obj),实际上可以理解为obj.func(),这时func函数体内的this自然指向的是obj;

    我们可以通过下面的代码实现bind的功能

    if (!Function.prototype.bind) {
        Function.prototype.bind = function () {
            var self = this,                        // 保存原函数
            context = [].shift.call(arguments), // 保存需要绑定的this上下文
            args = [].slice.call(arguments);    // 剩余的参数转为数组
            return function () {                    // 返回一个新函数
                // 执行新函数时,将传入的上下文context作为新函数的this
                // 并且组合两次分别传入的参数,作为新函数的参数
                self.apply(context,[].concat.call(args, [].slice.call(arguments)));
            }
        }
    }
    

    本质上bind就是apply的一个封装,方便我们使用罢了。

    通过使用bind我们就解决了上面提到的回调函数丢失了this绑定的问题。

    JavaScript内置函数实现this的绑定

    第三方库的许多函数,以及 JavaScript 语言和宿主环境中许多新的内置函数,都提供了一 个可选的参数,通常被称为“上下文”(context),其作用和 bind(..) 一样,确保你的回调 函数使用指定的 this。
    举例来说:

    function foo(el) {
        console.log( el, this.id );
    }
    var obj = {
        id: "awesome"
    };
    // 调用 foo(..) 时把 this 绑定到 obj [1, 2, 3].forEach( foo, obj );
    // 1 awesome 2 awesome 3 awesome
    

    这些函数实际上就是通过 call(..) 或者 apply(..) 实现了显式绑定,这样你可以少些一些 代码

    new绑定

    首先要说明的是JavaScript中new的机制实际上和面向类的语言完全不同。
    JavaScript中的"构造函数"并不属于某个类,也不会实例化一个类,它只不过是一个能被new调
    用的普通函数而已。
    看一段代码

    
    function foo(a) { this.a = a;
    }
    var bar = new foo(2); console.log( bar.a ); // 2
    
    

    使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

    1. 创建(或者说构造)一个全新的对象。
    2. 这个新对象会被执行[[原型]]连接。
    3. 这个新对象会绑定到函数调用的this。
    4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
      使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。new 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定。

    这几种this绑定的优先级

    我们先介绍前四种this绑定规则,那么问题来了,如果一个函数调用存在多种绑定方法,this最终指向谁呢?这里我们直接先上答案,this绑定优先级为:

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

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

    为什么显式绑定不和new绑定比较呢?因为不存在这种绑定同时生效的情景,如果同时写这两种代码会直接抛错,所以大家只用记住上面的规律即可。

    function foo(){
        this.a = 3;
    };
    var obj = {
        a:2
    }
    var f=new foo().call(obj);//报错 call is not a function
    

    箭头函数的this

    后续。。

    参考资料

    YouDontKnowJS
    https://github.com/lin-xin/blog/issues/7

  • 相关阅读:
    一个JAVA题引发的思考
    eclipse好玩的插件集(一) CKEditor插件
    Log4J使用实例---日志进行邮件发送或是存入数据库
    log4j输出到数据库(输出自定义参数、分级保存)
    String和StringBuffer的一点研究
    String、StringBuffer、StringBuilder区分和性能比较
    最新eclipse安装SVN插件
    jsoup select 选择器
    网页导出excel文件
    Dom4j完整教程
  • 原文地址:https://www.cnblogs.com/c-x-a/p/14265974.html
Copyright © 2011-2022 走看看