前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端day19-JS高级(函数调用的上下文模式)学习笔记

前端day19-JS高级(函数调用的上下文模式)学习笔记

原创
作者头像
帅的一麻皮
修改2020-05-11 10:46:56
1.2K0
修改2020-05-11 10:46:56
举报
文章被收录于专栏:前端与Java学习前端与Java学习

01-函数的三种调用方式(this关键字)

1.1复习函数三种调用方式:普通函数 对象方法 构造函数(理解this关键字作用:谁调用这个函数,this指向谁)

  • a.全局函数:this指向window
  • b.对象方法:this指向对象
  • c.构造函数:this指向new创建的空对象

共同特点:this的指向无法动态修改

      //1.全局函数
        fn();
        function fn(){
            console.log(this);//window
        }

        //2.对象的方法
        var obj = {
            name:'老八',
            sayHi:function(){
                console.log('奥利给干了兄弟们');
                console.log(this);//指向对象{name: "老八", sayHi: ƒ}
            }
        }
        obj.sayHi();
        //将fn的地址值赋给sayHi
        obj.sayHi = fn;
        //此时this指向obj,this指向跟声明没有关系,取决于函数式如何调用的
        obj.sayHi();//{name: "班长", sayHi: ƒ}

        //3.构造函数
        function Person(name,age){
            //1.创建一个空对象 2.this指向这个对象 3.执行赋值代码  4.返回这个对象
            //this:指向new创建的那个对象
            console.log(this);
            this.name = name;
            this.age = age;
        }
        var p1 = new Person("萨斯给",19);
        var p1 = new Person("哪路拖",21);

        //没有加new ,以全局函数方式执行,此时this就是window,函数里面其实是给window添加属性(全局变量)
        Person("啊giao",35);
        console.log(name);//啊giao
        console.log(age);//35 

02-函数调用的上下文模式

2.1-函数执行的上下文模式

作用:可以动态修改函数中的this指向

函数上下文的三个方法:call()apply()bind()它们定义在Function构造函数的原型中

异同点

  • 相同之处:都可以修改函数中this指向
  • 不同点:传参方式不同
    • call()语法: 函数名.call(this修改后的指向,arg1,arg2…………)
    • apply()语法:函数名.apply(this修改之后的指向,伪数组或者数组)
    • bind()语法:函数名.bind(this修改后的指向,arg1,arg2....)
      • bind()语法并不会立即执行函数,而是返回一个修改指向后的新函数,常用于回调函数
<script>
        /* 
            2.函数上下文模式:
                2.1作用:可以动态修改函数中的this
                2.2语法:有三种写法,作用都是一样改this,应用场景不同
                    a.函数名.call(修改的this,arg1,arg2...);
                        *适用于函数原本形参 <= 1
                    b.函数名.apply(修改的this,[数组或者伪数组])
                        *适用于函数原本形参 >= 2
                    c.函数名.bind(修改的this,arg1,arg2...)
                        *特点:不会立即执行这个函数,而是返回修改this后的新函数
                        *适用于不会立即执行的函数:事件处理函数,定时器函数
 
            补充:call() apply() bind() 都是放在Function的prototype原型中
         */
         
        // function fn() {
        //     //三种执行模式 this无法动态修改
        //     //  this = {age:18};//报错
        //     console.log(this);
        // }
        // fn(); //window

        /* 上下文模式 */
        function fn(a,b){
            console.log(this);
            console.log(a+b);
        }
        fn(10,20);//this:window
        
        //a.函数名.call(修改的this,arg1,arg2...)
        //应用场景: 适用于函数原本形参 <= 1
        fn.call({age:18},10,200);//this:{age: 18} 

        //b.函数名.apply(修改的this,[数组或者伪数组])
        //应用场景:适用于函数原本形参 >= 2
        fn.apply({sayHi:function(){console.log('giao');}},[11,22]);//this:{sayHi: ƒ}
        
        //c.函数名.bind(修改的this,arg1,arg2...)
        //特点:这个函数不会立即执行,而是返回一个修改this之后的新函数
        //应用场景:事件处理函数,定时器
        /* 
            细节:一般用bind()修改this指向,不要修改原本参数
            一旦传了,就会变成函数的固定参数,无法修改
            一般不传:下一次调用就可以正常传参了
         */
        var newFn1= fn.bind({name:'奥利给'},77,3);
        newFn1(1,2);//this:{name: "奥利给"}  80
        var newFn2 = fn.bind({name:'萨苏给'});
        newFn2(22,0);//this:{name:'萨苏给'} 22
        

        //4.定时器中的this一定指向window
        //定时器:一段代码间隔时间执行setTimeout(一段代码,间隔时间)

        //4.1具名函数
        var test = function(){
            console.log('我是具名函数');
            console.log(this);
        };

        var newTest = test.bind({name:"张三"});
        setTimeout(newTest,1000);

        //4.2 匿名函数
        setTimeout(function(){
            console.log('我是定时器中的函数');
            console.log(this);
        }.bind({name:'李四'}),2000);

    </script>

2.2-函数调用上下文模式注意点

<script>
        /* 
        1. 函数上下问执行模式 : 动态修改this
            注意点 : 修改的this只能是引用类型
        2.如果写的是基本数据类型 
            string,number,boolean : 自定帮我们转成对应的基本装包类型 new String() Boolean() Number()
            undefined,null :
         */
        function fn(){
            console.log(this);
        };

        fn.call('str');//String
        fn.call(1);//Number
        fn.call(true);//Boolean

        //如果传的是undefined和null,或者不传。代码不会报错,也不会帮我们修改this,还是原来的window
        fn.call(undefined);
        fn.call(null);
        fn.call();
        fn.call(window);
    </script>

2.3-函数调用的上下文模式案例

案例01:伪数组转数组

  • 方式一:遍历伪数组添加到真数组中
  • 方式二:空数组.push.apply(arr,伪数组)(不修改this指向,利用了apply传参会自动遍历数组/伪数组每一个元素作为实参传递)
  • 方式三:通过数组.slice(0)默认返回数组自身,越过原型链调用slice:Array.prototype.slice.call(weiArr,0)
<script>
        /* 需求:伪数组转成真数组,希望伪数组也可以调用数组的api
         * 1.伪数组:只有数组的三要素(元素、下标、长度),没有数组的api
         * 2.转数组目的:让伪数组也可以调用数组的api
         * 3.方式很多种,掌握任何一种即可
         */
        var weiArr = {
            0: '林绿裙',
            1: '保健坤',
            2: '泰拳邹',
            3: '炮王周',
            length: 4
        };
        // console.log(weiArr);
        // console.log(weiArr[0]);

        //1.遍历伪数组添加到真数组中
        // var arr = [];
        // for(var i =0;i<weiArr.length;i++){
        //     // console.log(weiArr[i]);
        //     arr[i] = weiArr[i];
        // }
        // console.log(arr);

        // 2.arr.push.apply()   常用
        // arr.push(weiArr[0],weiArr[1]....):支持多个参数
        // var arr = [];
        // //这里使用apply不是为了修改this,所以arr还是不变,而是利用了apply传参会自动遍历数组/伪数组每一个元素作为实参传递
        // arr.push.apply(arr,weiArr);//调用push, 使用上下文方式 
        // console.log(arr);

        //3. slice
        /* 
        1.arr.slice(start,end 批量查询 start<=范围<end)
        2.如果传入的是0,或者什么都不传会默认返回数组自身
        3.如果伪数组可以调用slice,不传参,就会返回一个真数组
         */
        
        //思考:数组可以调用slice,因为数组中的slice方法 存在于原型中
        /* 
            谁可以访问愿心中的成员?
            a.每一个实例对象:直接访问 arr.slice() this指向:数组
            b.构造函数自身:Array.prototype.slice() this指向:原型
         */
        //解决方案:越过原型链,直接从构造函数原型中调用slice方法
        weiArr = Array.prototype.slice.call(weiArr,0);//这个0可以写也可以不写
        console.log(weiArr);
    </script>

案例02:伪数组排序(伪数组如何使用sort排序?)

方式一:先把伪数组转成真数组,再调用方法:sort

方式二:越过原型链,直接使用Array.prototype.sort()

    <script>
        var weiArr = {
            0:88,
            1:24,
            2:45,
            3:55,
            4:90,
            length:5
        };
        //复习数组排序方法sort
        // var arr  = [2,3,5,1];
        // arr.sort(function(a,b){return a-b});
        // console.log(arr);

        //伪数组如何使用sort排序?
        //方式一:先把伪数组转成真数组,再调用sort()

        //方式二:越过原型链,直接使用Array.prototype.sort()
        Array.prototype.sort.call(weiArr,function(a,b){return a-b});
        console.log(weiArr);
    </script>

案例03:求数组最大值

方式一:擂台思想

方式二:通过Math.max()

    <script>
        //求数组最大值
        var arr=[33,91,26,55,77];

        //方式一:擂台思想

        //方式二:Math.max()

        //这里不需要修改this,只是借用apply传参特点:自动遍历数组/伪数组,逐一传参
        var max  = Math.max.apply(Math,arr);
        console.log(max);//91
    </script>        

案例04:万能检测数据类型

之前我们都是通过typeof检测数据类型,但是我们会发现当我们去检测null或者数组时无法得到准确的结果

为什么数组调用toString和对象调用toString得结果不一样?

<script>
        /* 
            typeof null:能否加测null的数据类型,如果不行,请问如何检测
            typeof [10,20,30]:能否检测数组的数据类型,如果不行,请问如何检测
         */
        /* 
            1.检测数据类型:typeof 数据
                特点:两种数据类型无法检测 null与array
         */
         console.log(typeof null);//object
         console.log(typeof [10,20,30]);//object

        /* 
            2.检测数据类型需要使用Object原型Object.prototype中有一个方法toString():
            返回固定格式字符串:[object Object]
         */
         var obj = {name:"张三"};
         console.log(obj.toString());//[object Object]
         /* 问题:数组的toString()和Object的toString() 结果不一样
            原因:数组的原型也有自己的toString() : 调用数组的join方法返回一个字符串
                Object.prototype:toString() 才是返回数据类型
                Array.prototype:toString() 本质是调用数组的join()
            解决问题:越过原型链,将this指向改为要检测的数据,直接调用Object.prototype.toString();
          */
         var arr = [10,20,30];
         console.log(arr.toString());//10,20,30
         console.log(Object.prototype.toString.call(arr));//[object Array]

        /* 3.万能检测数据类型:直接调用Object.prototype中的toStirng() */
        console.log(Object.prototype.toString.call("123"));//[object String]
        console.log(Object.prototype.toString.call(123));//[object Number]
        console.log(Object.prototype.toString.call(true));//[object Boolean]
        console.log(Object.prototype.toString.call(undefined));//[object Undefined]
        console.log(Object.prototype.toString.call(null));//[object Null]
        console.log(Object.prototype.toString.call([1,2,3,4]));//[object Array]
        console.log(Object.prototype.toString.call(function(){}));//[object Function]
        console.log(Object.prototype.toString.call({}));//[object Object]
    </script>

案例05:借用构造函数继承(了解)

<script>
        /* 
            借用构造函数继承:一个构造函数 去借用 另一个构造函数的复制代码
                不常用: 只能继承 this.xxx 成员
         */

        //人构造函数
         function Person(name,age){
            this.name = name;
            this.age = age;
            // console.log(this);
         }
         Person.prototype.hobby = "学习";
         var p1 = new Person("萨斯给",20);
         console.log(p1.hobby);//学习
         //学生构造函数
         function Student(name,age,score){
            // this.name = name;
            // this.age = age;
            //Person(name,age)
            /* 
                默认情况下:Person(name,age)这个方法里面的this指向的是window
                解决方式:Person.call(this,name,age) 修改Person里面的this(window) 为 student函数
             */
            Person.call(this,name,age);
            this.score = score;
         }
    </script>   

03-原型对象补充知识点

3.1-静态成员与·实例成员

  • 实例成员:属于构造函数实例化对象的成员变量,称之为实例成员
  • 静态成员:属于构造函数对象自身的成员变量,称之为静态成员
         //构造函数
         function Person(name,age){
            this.name = name;
            this.age = age;
        };

        Person.aaa = '啊啊啊';
        console.log(Person.aaa);//静态成员
        
        //实例化对象
        var p1 = new Person('张三',20);

        console.log(p1.name);//实例成员
        console.log(p1.age);//实例成员

3.2-Object.prototype成员介绍

Object.prototyp原型中一些常用的属性:

  • 原型链最终都会指向Object.prototype,意味着所有的对象可以访问他的成员变量
  • hasOwnProperty():判断对象是否拥有某个成员
  • isPrototypeOf():判断一个对象是否是另一个对象的原型
  • propertyisEnumerable():判断对象是否可以枚举某个属性

枚举(同时满足两个条件):1.属性是对象自己的成员 2.属性可以使用for-in循环遍历

for-in循环可以遍历的属性有哪些?(1)自己的成员 (2)原型的成员

疑惑:为什么hasOwnProperty与propertyIsEnumerable看起来好像作用一样

数组的length属性属于数组对象的成员,但是for-in循环无法枚举

  • 数组.hasOwnProperty('length') = true
  • 数组.propertyIsEnumerable('length') = false
<script>
        /* 
        1.每一个对象的原型值 最终都会指向Object.prototype(万物皆对象);
        2.人以对象都可以通过原型链访问 Object.prototype 里面的成员
        3.了解Object.prototype 的常用的成员
         */
        console.log(Object.prototype);

        //构造函数
        function Person(name,age){
            this.name = name;
            this.age = age;
        }
        //原型对象
        Person.prototype.type = "人类"
        //实例对象
        var p1 = new Person("老八",45);
        
        //1.对象.hasOwnProperty('属性'):检测对象的属性 是不是自己的
        /* 应用场景:检测一个对新娘的属性 是自己的 还是原型的 */
        console.log(p1.hasOwnProperty('name'));//true
        console.log(p1.hasOwnProperty('sex'));//false
        console.log(p1.hasOwnProperty('type'));//false ,type属性不是p1自己的,是原型的
        
        //2.对象A.isPrototypeOf(对象B):检测对象A是不是对象B的原型
        //等价于Person.prototype === p1.__proto__
        console.log(Person.prototype.isPrototypeOf(p1));//true
        console.log(Object.prototype.isPrototypeOf(p1));//true
        console.log(Array.prototype.isPrototypeOf(p1));//false
        
        //3.对象.propertyIsEnumerable('属性'):检测对象是否可以枚举某个属性
        //枚举必须满足两个条件,缺一不可:(1)属性必须是自己的 (2)属性可以被for-in遍历
        console.log(p1.propertyIsEnumerable('name'));//true

        //数组的length属性:属于数组,但是无法for-in遍历
        var arr = [10,20,30];
        console.log(arr.length);
        console.log(arr.propertyIsEnumerable('length'));//false 因为无法for-in遍历
    
        for(var key in arr){
            console.log('下标'+key);
            console.log('元素'+arr[key]);
        }
    
    </script>

3.3-函数对象常用属性

本小节知识点

函数属于对象,它也会有一些默认属性:

  • 1.caller属性:获取调用当前函数的引用(谁调用了我)
    • a.如果函数A中调用了函数B,那么函数B的caller就是函数A
    • b.如果在全局调用函数A,相当于window.fn2(),那么函数A的caller就是null
  • 2.length属性:获取函数形参的个数
  • 3.name属性:获取函数名
  • 4.arguments属性:获取所有的实参

疑惑:arguments关键字 函数名.arguments 是否一样?

  • 1.常用:arguments关键字:可以理解为是函数中一个默认的形参,作用是存储所有实参,并且与形参一一对应,修改了arguments,形参也会跟着改变(本质是一个对象:伪数组)
  • 2.不常用:函数名.arguments:可以理解为是函数对象的一个属性,作用是获取所有实参,不与形参一一对应,修改了函数名.arguments,形参不会改
  • 函数名.arguments == arguments得到false

arguments对象

  • arguments是一个伪数组(拥有数组三要素,没有数组的api,本质是一个对象),有两个常用属性
    • callee:获取当前函数自身 应用场景:匿名函数递归调用
    • length:实参的个数 length属于数组的三要素之一
<script>
        /* 
            js经典送命题:请说出 call,caller,callee区别
                call:属于Function.prototype,作用是修改this指向
                caller:属于函数对象,作用是获取调用该函数的引用(我在哪儿被调用了)
                callee:属于arguments对象,作用是匿名函数递归调用
            函数对象的属性(静态成员)
         */
        function fn(a, b) {
            console.log(a + b);
        }
        console.log(fn); //打印函数体代码
        console.dir(fn); //打印函数对象的属性

        //1.函数名.caller:获取调用函数的引用(我在哪被调用了)
        /* 
            a.如果在函数B中调用函数A:则函数A的caller就是函数B
            b.如果在全局作用域中调用函数A,那么函数A的caller是null
         */
        function fn1() {
            console.log('哈哈');
            console.log(fn1.caller); //fn2 我被谁给调用了
        }

        function fn2() {
            console.log('嘻嘻');
            //在fn2中调用fn1,那么fn1的caller就是fn2
            fn1();
        }
        fn2();
        //在全局作用域中调用fn1,那么fn1的caller是null
        fn1();


        //2.函数名.lenght:获取函数形参的数量
        function fn3(a, b) {

        }
        console.log(fn3.length); //2

        //3.函数名.name:获取函数名
        function fn4() {

        }
        console.log(fn4.name); //'fn4'

        //4.函数名.arguments:获取函数所有的实参
        function fn5(a, b) {
            //函数的静态成员:函数名.arguments
            console.log(fn5.arguments);
            //关键字:arguments
            console.log(arguments);
            console.log(fn5.arguments == arguments); //false 数据一样,但是地址不同
            
            console.log();
            /* 
            fn5.arguments是aruments的克隆体 深拷贝
             */
            /* 
            开发中一般使用arguments
                arguments.length:实参数量
                arguments.callee:获取函数自身
             */
            console.log(arguments.length);//3
            //唯一应用场景:匿名函数的递归调用(自己调用自己)
            console.log(arguments.callee);//fn5
        }
        fn5(20, 33, 40);
        //匿名函数
        (function(){
            console.log('111');
            //自己调用自己
            console.log(arguments.callee);//自己
        })()
    </script>

3.4-给内置构造函数原型添加方法

1.给内置构造函数添加方法

  • 应用场景:内置构造函数方法不够用,我想添加一些自定义方法
  • 如:给Array构造函数原型添加求最大值方法,那么所有数组都可以使用
<script>
        //1.数组实例对象都是Array构造函数生成
        var arr1 = [100, 88, 90, 20, 66];
        var arr2 = [-5, -10, 20, 50, 88];
        console.log(arr1.__proto__ === arr2.__proto__); //true
        console.log(arr1.__proto__ === Array.prototype); //true

        //2.内置Arrary构造函数方法不够用,我想自己加入一些自定义方法
    
        //2.1给Array添加冒泡排序方法
        Array.prototype.maopao = function(){
            //this:调用这个方法的具体数组
            for(var i=0;i<this.length-1;i++){
                var isSort = true;
                for(var j=0;j<this.length-i-1;j++){
                    if(this[j]>this[j+1]){
                        isSort = false;
                        var temp = this[j]
                        this[j] = this[j+1];
                        this[j+1] = temp;
                    }
                }
                if(isSort){
                    break;
                }
            }
            return this;
        }
        
        console.log(arr1.maopao());// [20, 66, 88, 90, 100]
        console.log(arr2.maopao());//[-10, -5, 20, 50, 88]
    </script>

2.如何安全的给内置构造函数原型添加方法?

  • 给内置构造函数添加方法弊端:在多人开发中,如果每个人都修改了内置构造函数的原型,则会造成潜在的bug(属性名一致导致覆盖问题) 与资源浪费 (你写的别人用不上,别人写的你用不上)
  • 解决方案:使用替换原型继承(自定义构造函数,将原型指向内置对象)
        /*1.给内置构造函数原型添加方法弊端
            a.存在潜在bug : 多人开发中,每个人都给内置构造函数原型添加方法,就有可能导致变量名一致,产生覆盖的情况
            b.资源浪费:你加的方法别人用不上,别人加的方法你用不上
        */
        //程序员A
        Array.prototype.sayHi = function () {
            console.log("哈哈,很帅");
        }
        //程序员B
        Array.prototype.sayHi = function () {
            console.log("你帅nm啊...");
        }
        //程序员A
        var arr = [10, 20, 30];
        arr.sayHi(); //你帅nm啊...

        /*2.解决方案:使用替换原型继承(自定义构造函数,将原型指向内置对象)*/
        function myArr() {
            this.sayHi = function () {
                console.log('奥利给干了兄弟们!');
            }
            this.maopao = function () {
                //this:调用这个方法的具体数组
                for (var i = 0; i < this.length - 1; i++) {
                    var isSort = true;
                    for (var j = 0; j < this.length - i - 1; j++) {
                        if (this[j] > this[j + 1]) {
                            isSort = false;
                            var temp = this[j]
                            this[j] = this[j + 1];
                            this[j + 1] = temp;
                        }
                    }
                    if (isSort) {
                        break;
                    }
                }
                return this;
            }
        }
        /* 
        这里为什么是空数组而不是Array.prototype呢?
        Array.prototype:对象类型赋值的时候拷贝的是地址,修改了myArr的原型之后,Array.prototype也会修改
        []:由于空数组的原型会指向Array.prototype,根据原型链中成员访问规则,myArr实例对象可以访问数组成员的成员
        
        并且,修改myArr的原型对象,本质上是修改这个空数组,不会对Array.prototype造成影响
        */

        myArr.prototype = []; //此时MyArr的原型拥有数组对象所有的方法
        var arr = new myArr();
        arr.push(20, 10, 30,15);
        arr.sayHi();
        console.log(arr);
        console.log(arr.maopao());    

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 01-函数的三种调用方式(this关键字)
  • 02-函数调用的上下文模式
    • 2.1-函数执行的上下文模式
      • 2.2-函数调用上下文模式注意点
        • 2.3-函数调用的上下文模式案例
          • 案例01:伪数组转数组
          • 案例02:伪数组排序(伪数组如何使用sort排序?)
          • 案例03:求数组最大值
          • 案例04:万能检测数据类型
          • 案例05:借用构造函数继承(了解)
      • 03-原型对象补充知识点
        • 3.1-静态成员与·实例成员
          • 3.2-Object.prototype成员介绍
            • 3.3-函数对象常用属性
              • 3.4-给内置构造函数原型添加方法
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档