• 上下文模式


     

    概念: 上下文就是环境, 就是自定义this的含义

    语法:

    1. 函数名.apply( 对象, [参数]);
      • 这个参数可以是数组, 也可以是伪数组
    2. 函数名.call( 对象, 参数);
      • 多个参数可以通过,进行隔离

    描述:

    1. 函数名表示的是函数本身, 使用函数进行调用的时候,默认this指的是全局变量
    2. 函数名也可以是方法提供, 使用方法调用的时候, this指的是当前对象
    3. 使用 apply 或者 call 进行调用后, 无论是函数, 还是方法的 this 指向全部无效了, this 的指向由 apply 或者 call 的第一个参数决定

    注意:

    1. 如果函数或方法中没有this的操作, 那么无论是哪一种函数调用模式, 其实都一样
    2. 如果是函数调用 foo(), 其实和 foo.apply(window) 类似
    3. 如果是方法调用 o.method(), 其实和 o.method.apply(o)

    无论是 call 还是 apply 在没有后面参数的情况下(函数无参数, 方法无参数), 两者一致

        function foo(){
            console.log(this);  // this => window
        }
        var obj = {};
        foo.apply( obj );   // this => obj
        foo.call( obj );    // this => obj

    apply 和 call 第一个参数的使用规则

    1. 如果传入的是一个对象, 就相当于设置该函数中的this为参数
    2. 如果不传参数, 或者传入 null, undefined 等,那么this就默认是 window

      foo();
      foo.apply();
      foo.apply(null);
      foo.apply(undefined);
      foo.call();
      foo.call(null);
      foo.call(undefined);
      // 上面都this都指向window
    3. 如果传入的是基本类型, 那么this指向的就是基本类型的包装类型的引用
      • number => Number
      • boolean => Boolean
      • string => String

    除 this 外的其他参数

    再使用上下文调用的时候, 原函数(方法)可能会带有参数, 那么要让这些参数在上下文中调用, 就需要这个第二, ( n )个参数来表示

        function foo(num){
            console.log(num);
        }
        foo.apply(null, [123]);
        // 相当于
        foo(123);

    应用

    上下文调用只是修改this, 但是使用最多的地方是借用函数调用

    1. 将伪数组转换为数组
      • 传统方法

        var a = {};
        a[0] = 'a';
        a[1] = 'b';
        a.length = 2;
        // 使用数组自带方法 concat();
        // 不修改原数组
        var arr = [];
        var newArr = arr.concat(a);

        分析
        由于 a 是伪数组, 并不是真正的数组, 不能使用数组的方法, concat 会将 a 作为一个整体 Object 加入数组
        apply 方法有一个特性, 可以将数组或者伪数组作为参数

        foo.apply( obj, 伪数组 ); // IE8 不支持

        将 a 作为 apply 的第二个参数

        var arr = [];
        var newArr = Array.prototype.concat.apply( arr, a);

        由上面的数组转换, 我们可以得到结论, 应该涉及到数组操作的方法理论上都应该可以
        push, pop, unshift, shift
        slice
        splice

    2. 让伪数组使用 push 方法
      小技巧, 伪数组添加元素

          var a = {length : 0};   // 设置伪数组的长度
          a[ a.length++   ] = 'a';
          a[ a.length++   ] = 'b';
          // 在给伪数组的元素赋值时, 同时修改伪数组的 length 属性
          // a[0] = 'a'; a.length++;

      伪数组使用 push 方法

          var arr = [];
          arr.push.apply( arr, a );
          // 利用 apply 可以处理伪数组的特性, 进行传参
          // 相当于 arr.push(a[0], a[1])
    3. 让伪数组使用 slice 方法
      数组的 slice 语法
      • arr.slice( index, endIndex ), 不包含 endIndex
      • 如果第二个参数不传参, 那么截取从 index 一直到数组结尾
      • slice 方法不会修改原数组

        通过 apply 实现伪数组使用 slice 方法

        var a = { length : 0 };
        a[a.length++] = 'a';
        a[a.length++] = 'b';
        var arr =[];
        var newArr = arr.slice.apply(a ,[0])

    获取数组中的最大值

    传统方法

        var arr = [1,2,3,4,5,6,7,8,9];
        var max = arr[0];
        for(var i = 0;i < arr.length;i++){
            if(max < arr[i]){
                max = arr[i]
            }
        }

    使用 apply 借用 Math.max 获取数组中最大值
    利用 apply 可以传入参数可以是数组或是伪数组的特性

        var arr = [1,2,3,4,5,6,7,8,9];
        Math.max.apply(null, arr);

    创建对象的几种模式

    了解了四种函数调用模式, 我们可以深一步了解创建对象的几种方式, 对象是通过构造函数创建的

    1. 工厂模式
      特点:
      1. 大量重复执行的代码, 解决重复实例化的问题
      2. 函数创建对象并返回
      3. 最典型的工厂模式就是 document.createElement()
      4. 无法知道是谁创建了这个实例对象

        function createPerson(name, age, gender){
            var o = {};
            o.name = name;
            o.age = age;
            o.gender = gender;
            return o;
        }
    2. 构造方法
      特点:
      1. 解决了重复实例化问题
      2. 能够知道是谁创建了这个对象(constructor)
      3. 需要通过 new 运算符穿件对象

        function Person(name,age,gender){
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
    3. 寄生式构造函数创建对象
      特点:
      1. 外表看起来就是构造犯法, 但本质不是通过构造方法创建对象
      2. 工厂模式 + 构造函数模式
      3. 不能确定对象的关系, 不推荐使用

        function createPerson(name,age,gender){
            var o = {};
            o.name = name;
            o.age = age;
            o.gender = gender;
            return o;
        }
        var p = new createPerson('Bob',19,'male')
    4. 混合式创建
      1. 构造函数 + 原型
      2. 解决了构造函数传参和共享的问题
      3. 不共享的参数使用构造函数
      4. 共享的使用原型
      5. 这种混合模式很好的解决了传参引用共享的难题

        function createPerson(name,age,gender){
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
        createPerson.prototype = {
            constructor : createPerson,
            wife : '高圆圆'
        }
    5. 借用构造函数继承(对象冒充)
      特点:
      1. 借用构造函数(对象冒充)只能继承构造函数的成员, 无法继承原型的成员

        function Person(name,age,gender){
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
        function Studner(name,age,gender,course){
            // 借用构造函数Person, 创建 Student 对象
            Person.call(this,name,age,gender);
            this.course = course;
        }
        var boy = new Student('Bob',19,'male',;Math);
    6. 寄生式继承
      特点:
      1. 原型 + 工厂模式
      2. 通过临时中转

        // 临时中转
        function person(o){
            function Foo(){};
            F.prototype = o;
            return new F();
        }
        // 寄生函数
        function create(o){
            var fn = person(o);
            fn.move = function(){};
            fn.eat = function(){};
            fn.sleep = function(){};
            return fn;
        }
        var boy = {
            name : 'Bob',
            age : 19,
            famliy : ['father','mother','wife']
        }
        var boy1 = create(boy);
        console.log(boy1.name);
        console.log(boy1.family);
        // 此时 boy1 有了 boy 的成员

    经典例题

    例题 1

        function Foo(){
            getName = function(){ alert(1); };
            return this;
        }
        function getName(){
            alert(5);
        }
        Foo.getName = function(){ alert(2); };
        Foo.prototype.getName = function(){ alert(3); };
    
        getName = function(){ alert(4); };
    
        Foo.getName();              // 2
        getName();                  // 4
        Foo().getName();            // 4  1
    
        getName();                  // 4  1
        new Foo.getName();          // 2
        new Foo().getName();        // 3
    
        new new Foo().getName();    // 3

    分析

    1. 预解析
    • 函数名 Foo 和函数名 getName 声明提升,函数名和函数体绑定
    1. 执行代码
    • 执行 Foo.getName();
      • 输出 2
    • 执行 getName();
      • 此时 getName 是在全局中, 未被修改, 输出4
    • Foo().getName();
      • 此事 Foo() 只是一个函数, 执行完成后 getName 被重新赋值
      • getName 因为被重新赋值为 1, 输出1
    • getName()
      • 由于 getName 被重新赋值, 所以输出 1
    • new Foo.getName();
      • Foo.getName 并未被修改
      • new 没有起任何作用
      • 输出2
    • new Foo().getName();
      • 构造函数创建了实例对象
      • 对象中没有 getName 方法, 要从对象的构造函数中的原型中寻找
      • 在 Foo.prototype 中得到 getName 输出为 3
    • new new Foo().getName();
    • 构造函数创建了实例对象
    • 对象中没有 getName 方法, 要从对象的构造函数中的原型中寻找
    • new 没有起作用
    • 在 Foo.prototype 中得到 getName 输出为 3

    例题 2

    var length = 10;
    function fn() {
        console.log( this.length );
    }
    
    var obj = {
        length: 5,
        method: function ( fn ) {
            fn();
            arguments[ 0 ]();  // [ fn, 1, 2, 3, length: 4]
        }
    };
    obj.method( fn, 1, 2, 3 );

    分析

    1. 预解析
      • 变量名 length, obj 和 函数名fn 声明提升, 函数名和函数体绑定
    2. 执行代码
      • length = 10, 此时 length 可以看作是 window 下的属性
      • obj = {}, 进行赋值
      • 执行 obj 中的 method 方法
      • 将 函数体 fn 进行传参
      • 跳进函数 fn,执行函数 fn(), 函数中的 this 指的是 window 下的 length, 为10
      • 明确argument是一个对象, argument[0] 指的是 fn
      • 使用对象调用函数, 这里的 this 指的是 argument 这个对象
      • 得到 argument.length 为 4
      • 最后输出结果为 10 4

    例题 3

        var o = {
            method : function(){
                console.log(this);
            }
        };
        o.method();
        var f = o.method;
        f();
        (o.method)();
        var obj = {};
        (obj.fn = o.method)();
        (obj.fn)();

    分析

    1. 预解析
      • 变量名 o, f ,obj 声明提升
    2. 执行函数
      • o = {},进行赋值
      • 执行o.method方法, 这里的 this 指的是 o 这个对象
      • 将 o.method 函数体,赋值给f
      • 执行 f(), 这里的 this 指的是window
      • (o.method)是一个函数表达式, 和 o.method() 的结果一致
      • obj = {}, obj进行赋值
      • o.method 的函数体, 赋值给obj.fn, 执行之后, 这里的 this 指的是window
      • (obj.fn)是一个函数表达式, 和 obj.fn() 的结果一致, tshi 指向 obj
  • 相关阅读:
    Redis 笔记
    React Native
    docker run 命令
    vue 集成腾讯地图基础api Demo集合
    在uniapp H5项目中使用腾讯地图sdk
    基于UE4/Unity绘制地图
    腾讯位置服务打车乘客端小车平滑移动-安卓篇
    腾讯位置服务定位打卡功能实现
    腾讯位置服务GPS轨迹回放-安卓篇
    腾讯位置服务个性化图层创建及发布
  • 原文地址:https://www.cnblogs.com/huangshikun/p/6678189.html
Copyright © 2020-2023  润新知