zoukankan      html  css  js  c++  java
  • 函数的属性和方法之call、apply 及bind

    一、前言

      ECMAScript中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:length和prototype。每个函数也包含两个非继承来的方法:apply()和call(),还有一些继承而来的方法,比如valueOf()等。

    二、函数属性:length、prototype

    2.1、length

    函数希望接收的命名参数的个数,如:

    function sum(a,b){
       return a+b;
    }
    sum.length ; //2
    
    function add(){
    
    }
    add.length; //0

    2.2、prototype

      它是保存所有实例方法的真正所在。比如toString()和valueOf()等方法都是保存在prototype名下,只不过是通过各自的对象的实例访问而已。在创建自定义引用类型和以及实现继承的时候,这个属性尤为重要。且这个属性是不可枚举的所以for in 访问不到,这个暂且不讲这个属性。

    三、函数的方法: apply() 和call()

      每个函数都包含这两个非继承而来的方法,都是Function.prototype的方法。用途都是在特定的作用域中调用函数。  

    都是用来改变函数的运行时的上下文对象(执行上下文),或者说是改变函数内部的this指向。

    3.1、两者区别:

    apply()方法接收两个参数,一个是在其中运行函数的作用域,一个是参数数组,(这个参数数组可以是Array的实例,也可以是类数组arguments;)

    call()方法和apply()的作用相同,区别仅在于接收的参数的方式不一样,使用call时候参数必须逐个列举出来传给函数。

    function sum(num1, num2){
      return num1 + num2;
    }
    
    function callsum1(num1, num2){
         return sum.apply(this,arguments);   //return sum.apply(this, [sum1,sum2]);
    }
    
    
    function callsum2(num1, num2){
      return sum.call(this,num1, num2);
    }

    当参数明确的时候,建议使用call,但传入的参数不明确的时候,或者是数组的时候,建议使用apply。

    3.2、apply()和call()真正强大的地方:

    扩充函数赖以运行的作用域,这就使得对象不需要和方法有任何耦合的关系。看如下demo.

    var animal = {
        words:'......',
        speak:function(say){
            console.log(say + ' ' + this.words);
        }
    }
    
    var dog ={
      words: 'wang'  
    }
    //dog没有say这个方法,但是可以通过call来调用aniaml的say方法。
    
    animal.speak.call(dog, 'speak'); //dog就是call()这个方法的上下文,this指向了dog这个对象。

    3.3、原生实现

    Function.prototype.myCall = function (content) {
      console.log(typeof this);
    
      context = context || window;
      context.fn = this;
      const args = [...arguments].silce(1);
      const result = context.fn(...args);
      delete context.fn;
    
      return result;
    }
    

      

    3.4、说明:

    非严格条件下,call和apply的第一个参数为null、undefined、空 ,函数内的this指向windown(浏览器 ) 或global。
    严格模式下,null是null,undefined和空 为undefined

     

    四、bind()

      ECMAScript 5 中定义了这个方法,这个方法会创建一个函数的实例,其中this值会被绑定到传给bind()函数的值。

    因为在IE9以下不支持原生的bind函数,但是对apply是支持的,所以可以利用apply自定义bind函数,如下:

    Function.prototype.myBind = function () {
      const _this = this; //保存调用bind的原函数
      const context = [].shift.call(arguments);//保存需要绑定的this上下文,就是第一个参数
      const args = [].slice.call(arguments);//剩余参数转为数组
    
      return function () {
        //我们将 bind 的其余参数和调用bind后返回的函数在执行的过程中接收的参数进行拼接,
        //作为一个数组传入apply的第二个参数中去。
        _this.apply(context, [].concat.call(args, [].slice.call(arguments)]);
      }
    }
    

      

    在bind()中创建了一个闭包,闭包中使用apply()调用传入的函数,并给apply传递context和arguments参数(arguments对象是内部函数的,不是bind函数的),

    当调用返回函数的时候,它会在给定的环境中执行被传入的函数,并给出所有的参数。这也是函数绑定的常用方法。

    看看vue源码中是怎么自定义的bind的,感慨~~~ 一个优秀的框架真的要考虑很多啊:

    五、bind vs call、apply

    bind返回的是对应的函数,不会立即调用,而call、apply都是立即调用的。

    下面的只是一个简单的例子,用来说明bind的用处,特别注意的是,定时器中的this如果不在执行函数中bind(this),则里面的this就会指向window,而不是当前创建的对象中。可以这么理解,setTimeout是window的,那么在执行函数的this也会指向Windows,除非bind(this)前置把里面函数的执行上下文改成当前对象。

    var slider={
    	LWIDTH:0,
    	DURATION: 1000,
    	WAIT:3000, //自动轮播之间的等待时间
    	timer:null,    //保存一次性定时器序号
    	canAuto:true,
    	LHeight:60,
    	init:function(){
    		this.updateView();
    		$(".slider").hover(
    			function(){this.canAuto=false;}.bind(this),
    			function(){this.canAuto=true;}.bind(this)
    		)
    		this.autoMove();//判断之后,调用自动执行函数
    	},
    	autoMove:function(){
    		this.timer=setTimeout(
    			function(){
    				if(this.canAuto){
    					this.move(1);
    				}else{
    					this.autoMove();
    				}
    			}.bind(this),
    		);
    	}
    }
    

      

    六、应用场景

    是不是经常遇到这样的用法?

    Array.prototype.slice.call ( arguments);

    或者是:
    [].prototype.slice.apply(arguments);

    或者是:

    function   isArray(obj){ 
        return Object.prototype.toString.call(obj) === '[object Array]' ;

    }

    或者是:

    function(){XXXXXXx}.bind (this);

    jQuey中不也有bind方法么?

    $('.XXX').bind('click',function(){});

    6.1、利用apply传参数机制,在某些本来需要写成遍历数组变量的任务中使用内建的函数

    定义一个 log 方法,让它可以代理 console.log 方法

    function log(){
      console.log.apply(console, arguments);
    };
    log(1);    //1
    log(1,2);    //1 2

    6.2、Math.max/Math.min来找出一个数组中的最大/最小值。

    var numbers = [2, 8, 4, 3, 1];
    var max = Math.max.apply(null, numbers); //等价于:Math,max(2, 8, 4, 3, 1);
    var min = Math.min.apply(null, numbers);  //同上



    //es6中可以用数组的扩展运算符替代apply
    Math.max(...numbers)

    6.3、方便实现继承

    function Animal(words){
        this.words = words;
        this.speak = function(){
           console.log(this.words);
        }
    }    
    
    function Dog(words){
        Anaimal.call(this, words);//或者Anmal.apply(this,, arguments)
         
    }
    
    var dog  = new Dog('wang');//这样实例的dog对象就有了Animal的speak方法了。
    
    dog.speak();
    

      

  • 相关阅读:
    Java正式day_06——数组排序
    别只知道策略模式+简单工厂,试试更香的策略模式+抽象工厂!
    图解连接阿里云(一)创建阿里云物联网平台产品和设备,使用MQTT.fx快速体验
    嵌入式交叉编译GDB,结合vscode图形化调试C和C++代码 coredump定位段错误
    内核链表之list_for_eacy_entry手绘图解
    makefile实验三 理解make工作的基本原则
    玩转Libmodbus(一) 搭建开发环境
    RT-Thread的C语言多态风格展示
    C++函数默认参数 详解
    杂类-边学边记
  • 原文地址:https://www.cnblogs.com/leaf930814/p/6661854.html
Copyright © 2011-2022 走看看