JavaScript函数

三、函数

1、函数定义有哪几种实现方式

在使用函数前,先需要对函数进行定义。关于函数的定义总体上可以分为三类。

第一类是函数声明。

第二类是函数表达式

第三类是通过Function构造函数来完成函数的定义。

首先来看一下函数的声明

函数声明是直接通过function关键字接一个函数名,同时可以接收参数。

function sum(num1, num2){
     return num1 + num2
}

函数表达式

函数表达式的形式类似于普通变量的初始化,只不过这个变量初始化的值是一个函数。如下代码所示:

var sum =  function (num1,num2){
    return num1 + num2
}

这个函数表达式没有名称,属于匿名函数表达式。

Function( )构造函数

使用new操作符,调用Function( )构造函数,传入参数,也可以定义一个函数。

var sum = new Function('num1','num2', 'return a+b ')

其中的参数,除了最后一个参数是要执行的函数体,其它的参数都是函数的形参。

2、Function( )构造函数定义函数的问题

但是,我们在实际的应用中很少使用Function( )构造函数来实现对函数的定义。

原因是:

第一:Function( ) 构造函数每次执行时,都会解析函数体,并创建一个新的函数对象,所以当在一个循环或者是一个频繁执行的函数中去使用Function( )构造函数的时候,相对来说性能是比较低的。

第二:通过Function( ) 构造函数创建的函数,并不遵循典型的作用域。

如下代码所示:

    var a = "12";
      function fun() {
        var a = "11";
        return new Function("return a");
      }
      console.log(fun()());

3、函数表达式的应用场景

关于函数表达式非常典型的应用就是实现了块级作用域

  var person = (function () {
        var _name = "";
        return {
          getName: function () {
            return _name;
          },
          setName: function (userName) {
            _name = userName;
          },
        };
      })();
      person.setName("zhangsan");
      console.log(person.getName());

4、函数声明与函数表达式有什么区别

函数声明与函数表达式虽然是两种定义函数的方式,但是两者之间还是有区别的。

第一点就是:函数名称

// 函数声明,函数名称sum是必须的
function sum (num1,num2){
	return num1 + num2 
}
// 没有函数名称的匿名函数表达式
var sum = function (num1,num2){
    return num1 + num2
}

第二点就是关于:函数提升

      console.log(add(1, 2)); // 3
      console.log(sum(3, 6)); // Uncaught TypeError: sum is not a function
      // 函数声明
      function add(num1, num2) {
        return num1 + num2;
      }
      // 函数表达式
      var sum = function (num1, num2) {
        return num1 + num2;
      };

5、函数常见的调用模式有哪些

函数调用模式

  function add(num1, num2) {
        return num1 + num2;
      }
      // 函数表达式
      var sum = function (num1, num2) {
        return num1 + num2;
      };

  console.log(add(1, 2)); 
 console.log(sum(3, 6));

方法调用模式

     var obj = {
        userName: "zhangsan",
        getUserName: function () {
          return this.userName;
        },
      };
      console.log(obj.getUserName());
 var obj = {
        userName: "zhangsan",
        getUserName: function () {
          return this.userName;
        },
      };
      // console.log(obj.getUserName());
      console.log(obj["getUserName"]());
   var obj = {
        userName: "zhangsan",
        getUserName: function () {
          return this.userName;
        },
        setUserName: function (name) {
          this.userName = name;
          return this;
        },
      };
      console.log(obj.setUserName("lisi").getUserName());// lisi

构造器(构造函数)调用模式

  //定义构造函数
      function Person(name) {
        this.userName = name; //定义属性
      }
      // 在原型上定义函数
      Person.prototype.getUserName = function () {
        return this.userName;
      };
      // 通过new来创建实例
      var p = new Person("zhangsan");
      // 调用原型上的方法
      console.log(p.getUserName());
   function sum(num1, num2) {
        return num1 + num2;
      }
      //定义一个对象
      var obj = {};
      //通过call()和apply( )函数调用sum( )函数
      console.log(sum.call(obj, 2, 6));
      console.log(sum.apply(obj, [3, 6]));

匿名函数调用模式

所谓的匿名函数,就是没有函数名称的函数。匿名函数的调用有两种方式,一种是通过函数表达式定义函数,并赋值给变量,通过变量进行调用。如下所示:

//通过函数表达式定义匿名函数,并赋值给变量sum
var sum =funciton (num1,num2){
    return num1 + num2
}
// 通过sum来进行调用
sum(2,6)

另外一种是使用小括号()将匿名函数括起来,然后在后面使用小括号( ),传递对应的参数从而完成对应的调用。

 (function (num1, num2) {
        console.log(num1 + num2);
      })(2, 6);

6、实参与形参有哪些区别

第一:在函数的调用过程中,数据传递是单向的,也就是只能把实参的值传递给形参,而不能把形参的值反向传递给实参

第二:当实参是基本数据类型的值的时候,在向形参传递的时候,实际上是将实参的值复制一份传递给形参,在函数运行结束以后

形参释放,而实参中的值不会发生变化。当实参是引用类型的值的时候,实际是将实参的内存地址传递给形参,即实参与形参都指向了

相同的内存地址,此时形参可以修改实参的值。

 var person = { age: 21 };
      function fn(obj) {
        obj.age = 22;
      }
      fn(person);
      console.log(person.age);

第三:函数可以不用定义形参,在函数体中可以通过arguments对象获取传递过来的实参的值,并进行处理。

第四:在函数定义形参时,形参的个数并一定要和实参的个数相同,实参与形参会按照从前向后的顺序进行匹配,没有匹配到的形参被当作undefined来处理。

第五:实参并不需要与形参的数据类型一致,因为形参的数据类型只能在执行的时候才能够被确定,因为会通过隐式数据类型的转换。

7、介绍一下arguments对象

arguments对象是所有函数都具有的一个内置的局部变量,表示的是函数实际接收到的参数,是一个类似数组的结构。

下面我们说一下arguments对象都具有哪些性质。

第一:arguments对象只能在函数内部使用,无法在函数的外部访问到arguments对象。同时arguments对象存在于函数级的作用域中。

 console.log(arguments); //Uncaught ReferenceError: arguments is not defined
      function fn() {
        console.log(arguments.length);
      }
      fn(1, 2, 3);

第二:可以通过索引来访问arguments对象中的内容,因为arguments对象类似数组结构。

 function fn() {
        console.log(arguments[0]); // 1
        console.log(arguments[1]); // 2
        console.log(arguments[2]); // undefined
      }
      fn(1, 2);

第三:arguments 对象的值由实参决定,不是有形参决定。

  function fn(num1, num2, num3) {
        console.log(arguments.length); // 2
      }
      fn(1, 2);

因为arguments对象的length属性是由实际传递的实参的个数决定的,所以这里输出的是2.

    function fn(num1, num2, num3) {
        arguments[0] = 23;
        console.log("num1=", num1); //23
        num2 = 33;
        console.log(arguments[1]); // 33
      }
      fn(1, 2);
      function fn(num1, num2, num3) {
        // arguments[0] = 23;
        // console.log("num1=", num1); //23
        // num2 = 33;
        // console.log(arguments[1]); // 33

        arguments[2] = 19;
        console.log(num3); //undefined
        num3 = 10;
        console.log(arguments[2]); // 19
      }
      fn(1, 2);
 function fn(num1, num2, num3) {
        // arguments[0] = 23;
        // console.log("num1=", num1); //23
        // num2 = 33;
        // console.log(arguments[1]); // 33

        arguments[2] = 19;
        console.log(num3); //undefined
        num3 = 10;
        console.log(arguments[2]); // 19

        console.log(arguments.length); // 2 长度还是2
      }
      fn(1, 2);

8、arguments对象有哪些应用场景

第一:进行参数个数的判断。

  function fn(num1, num2, num3) {
        // 判断传递的参数个数是否正确
        if (arguments.length !== 3) {
          throw new Error(
            "希望传递3个参数,实际传递的参数个数为:" + arguments.length
          );
        }
      }
      fn(1, 3);

第二:对任意个数参数的处理,也就是说只会对函数中前几个参数做特定处理,后面的参数不论传递多少个都会统一进行处理,这种情况我们可以使用arguments对象来完成。

  function fn(sep) {
        
        var arr = Array.prototype.slice.call(arguments, 1);
        // console.log(arr); // ["a", "b", "c"]
        return arr.join(sep);
      }
      console.log(fn("-", "a", "b", "c"));

第三:模拟函数的重载

什么是函数的重载呢?

函数的重载指的是在函数名称相同的情况下,函数的形参的类型不同或者是个数不同。

但是在JavaScript中没有函数的重载。

      function fn(num1, num2) {
        return num1 + num2;
      }
      function fn(num1, num2, num3) {
        return num1 + num2 + num3;
      }
      console.log(fn(1, 2)); // NaN
      console.log(fn(1, 2, 3)); // 6
   function fn() {
        //将arguments对象转换成数组
        var arr = Array.prototype.slice.call(arguments);
        // console.log(arr);  // [1,2]
       //调用数组中的reduce方法完成数据的计算
        return arr.reduce(function (pre, currentValue) {
          return pre + currentValue;
        });
      }
      console.log(fn(1, 2));
      console.log(fn(1, 2, 3));
      console.log(fn(1, 2, 3, 4, 5));

9、说一下普通函数与构造函数的区别

JavaScript的函数中,有一类比较特殊的函数:‘构造函数’。当我们创建对象的时候,经常会使用构造函数。

构造函数与普通函数的区别:

第一:构造函数的函数名的第一字母通常会大写。

第二:在构造函数的函数体内可以使用this关键字,表示创生成的对象实例。

      function Person(userName) {
        this.userName = userName;
      }
      var person = new Person("zhangsan");
      console.log(person);

第三:在使用构造函数的时候,必须与new操作符配合使用。

第四:构造函数的执行过程与普通函数也是不一样的。

代码如下:

     function Person(userName) {
        this.userName = userName;
        this.sayHi = function () {
          console.log(this.username);
        };
      }
      var p1 = new Person("zhangsan");
      var p2 = new Person("lisi");
      console.log(p1.sayHi === p2.sayHi); // false
    function Person(userName) {
        this.userName = userName;
        // this.sayHi = function () {
        //   console.log(this.username);
        // };
      }
      Person.prototype.sayHi = function () {
        console.log(this.username);
      };
      var p1 = new Person("zhangsan");
      var p2 = new Person("lisi");
      console.log(p1.sayHi === p2.sayHi); // true

10、什么是变量提升,什么是函数提升

javascript中存在一些比较奇怪的现象。在一个函数体内,变量在定义之前就可以被访问到,而不会抛出异常。

如下所示:

     function fn() {
        console.log(num); // undefined
        var num = 2;
      }
      fn();

同样函数在定义之前也可以被调用,而不会抛出异常。

如下代码所示:

  fn();
      function fn() {
        console.log("hello");
      }

导致出现以上情况的原因是,在javascript中存在变量提升与函数提升的机制。

在讲解变量提升之前,先来说以作用域的问题。

作用域

JavaScript中,一个变量的定义与调用都是在一个固定的范围内的,这个范围我们称之为作用域。

作用域可以分为全局的作用域,局部作用域(函数作用域)和块级作用域。

如下程序:

 function fn() {
        var userName = "zhangsan";
        console.log(userName);
      }
      fn(); //zhangsan

下面,再看如下代码:

  var userName = "zhangsan";
      function fn() {
        console.log(userName);
      }
      fn(); //zhangsan

综上两个案例,我们可以总结出,作用域本质就是一套规则,用于确定在何处以及如何查找变量的规则。

下面,我们再来看一个比较复杂的结构图,来体验一下作用域

请添加图片描述

  • 作用域链

下面,我们再来看一下前面的代码:

  var userName = "zhangsan";
      function fn() {
        console.log(userName);
      }
      fn(); //zhangsan

我们在查找userName这个变量的时候,现在函数的作用域中进行查找,没有找到,再去全局作用域中查找。你会注意到,这是一个往外层查找的过程,即顺着一条链条从下往上查找变量。这个链条,我们就称之为作用域链。

如下图所示:

请添加图片描述

对应的代码如下:

请添加图片描述

面试中关于作用域与作用域链的问题

第一题:以下代码的执行结果是:

   var a = 1;
      function fn1() {
        function fn2() {
          console.log(a);
        }
        function fn3() {
          var a = 4;
          fn2();
        }
        var a = 2;
        return fn3;
      }
      var fn = fn1();
      fn(); // 2

第二题:以下代码的执行结果是:

    var a = 1;
      function fn1() {
        function fn3() {
          var a = 4;
          fn2();
        }
        var a = 2;
        return fn3;
      }

      function fn2() {
        console.log(a);
      }
      var fn = fn1();
      fn(); // 1

第三题:以下代码的输出结果为

  var a = 1;
      function fn1() {
        function fn3() {
          function fn2() {
            console.log(a);
          }
          var a;
          fn2();
          a = 4;
        }
        var a = 2;
        return fn3;
      }
      var fn = fn1();
      fn(); //undefined

第四题:以下代码的输出结果为:

 var x = 10;
      bar(); //10
      function foo() {
        console.log(x);
      }
      function bar() {
        var x = 30;
        foo();
      }

第五题: 以下代码的输出结果为:

  var x = 10;
      bar(); //30
      function bar() {
        var x = 30;
        function foo() {
          console.log(x);
        }
        foo();
      }

第六题:以下代码的输出结果为:

   var x = 10;
      bar(); //30
      function bar() {
        var x = 30;
        (function () {
          console.log(x);
        })();
      }

变量提升

所谓变量提升,是将变量的声明提升到函数顶部的位置,也就是将变量声明提升到变量所在的作用域的顶端,而变量的赋值并不会被提升。

  var str = "hello world";
      (function () {
        console.log(str);
        var str = "hello vue";
      })(); // undefined
      var str = "hello world";
      (function () {
        var str; //变量的声明得到提升
        console.log(str);
        str = "hello vue"; // 变量的赋值没有得到提升
      })();

如下代码所示:

    (function () {
        console.log(str);
        str = "hello vue";
      })(); // str is not defined

以下代码的执行结果是:

  function foo() {
        var a = 1;
        console.log(a); //1
        console.log(b); //undefined
        var b = 2;
      }
      foo();

上面的代码等价于

function foo() {
  var a;
  var b;
  a = 1;
  console.log(a); // 1
  console.log(b); // undefined
  b = 2;
}
foo();

函数提升

不仅通过var定义的变量会出现提升的情况,使用函数声明方式定义的函数也会出现提升。

如下代码:

   foo(); // 函数提升
      function foo() {
        console.log("hello");
      }
function foo(){
    console.log("hello");
}
foo() //'hello'
   foo(); // foo is not a function
      var foo = function () {
        console.log("hello");
      };

看一下如下程序的执行结果:

 function foo() {
        function bar() {
          return 3;
        }
        return bar();
        function bar() {
          return 9;
        }
      }
      console.log(foo()); // 9

如下程序的执行结果:

  var a = true;
      foo();
      function foo() {
        if (a) {
          var a = 20;
        }
        console.log(a); // undefined
      }

以上的代码的执行过程如下:

var a;
a = true;
function foo(){
    var a;
    if(a){
        a=20
    }
    console.log(a)
}
foo()

如下程序的执行结果:

   function v() {
        var a = 1;
        function a() {}
        console.log(a);
      }
      v(); // 1

下面我们再来看一段代码:

 function fn() {
        console.log(typeof foo); // function
        var foo = "hello";
        function foo() {
          return "abc";
        }
        console.log(typeof foo); // string
      }
      fn();

执行上面的代码,首先打印的是function,然后是string.

上面的代码实际上可以修改成如下的代码段。

function fn1() {
        // 变量提升到函数的顶部
        var foo;
        // 函数提升,但是优先级低,所以出现在变量声明的后面。
        function foo() {
          return "abc";
        }
        console.log(typeof foo); //function
        foo = "hello";
        console.log(typeof foo); //string
      }

下面,我们再来看一段代码,看一下对应的输出结果是:

   function foo() {
          var a = 1;
          function b() {
            a = 10;
            return;
            function a() {}
          }
          b();
          console.log(a);
        }
        foo(); //1

上面的代码可以修改成如下的代码。

 function foo() {
     //变量a提升
        var a;
     //函数声明b的提升
        function b() {
            //内部的函数声明a的提升
          function a() {}
            //全局变量
          a = 10;
          return;
        }
        a = 1;
        b();
        console.log(a);//在当前的作用域中,可以找到变量a,不需要获取全局变量a,所以其值为1,所以打印结果为1,
      }
      foo();

11、闭包

在正常的情况下,如果定义了一个函数,就会产生一个函数作用域,在函数体中的局部变量会在这个函数的作用域中使用。

一旦函数执行完毕后,函数所占用的空间就会被回收,存在于函数体中的局部变量同样也会被回收,回收后将不能被访问。

如果我们期望在函数执行完毕以后,函数中的局部变量仍然可以被访问到,应该怎样实现呢?

这里我们可以通过闭包来实现。

在讲解闭包的问题之前,我们先说一个概念,执行上下文环境。

执行上下文环境

JavaScript的每段代码的执行都会存在于一个执行上下文环境中。

执行上下文有且只有三类,全局执行上下文,函数上下文,与eval上下文;由于eval一般不会使用,这里不做讨论

function f1() {
    f2();
    console.log(1);
};

function f2() {
    f3();
    console.log(2);
};

function f3() {
    console.log(3);
};

f1();//3 2 1

为了方便理解,我们假设执行栈是一个数组,在代码执行初期一定会创建全局执行上下文并压入栈,因此过程大致如下:

//代码执行前创建全局执行上下文
ECStack = [globalContext];
// f1调用
ECStack.push('f1 functionContext');
// f1又调用了f2,f2执行完毕之前无法console 1
ECStack.push('f2 functionContext');
// f2又调用了f3,f3执行完毕之前无法console 2
ECStack.push('f3 functionContext');
// f3执行完毕,输出3并出栈
ECStack.pop();
// f2执行完毕,输出2并出栈
ECStack.pop();
// f1执行完毕,输出1并出栈
ECStack.pop();
// 此时执行栈中只剩下一个全局执行上下文

什么是闭包

关于闭包的官方概念:一个拥有许多变量和绑定了这些变量执行上下文环境的表达式,通常是一个函数。

简单的理解就是:闭包就是能够读取其它函数内部变量的函数。由于在JavaScript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。

function outer () {
    ...
    function inner () {
        ...
    }
}

所以,本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包有两个比较显著的特点:

第一:函数拥有的外部变量的引用,在函数返回时,该变量仍然处于活跃状态。

第二:闭包作为一个函数返回时,其执行上下文环境不会销毁,仍然处于执行上下文环境中。

JavaScript中存在一种内部函数,即函数声明和函数表达式可以位于另一个函数的函数体内,在内部函数中可以访问外部函数声明的变量,当这个内部函数在包含它们外部函数之外被调用时,就会形成闭包。

function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();

下面,我们再来看另外一段代码:

    function fn() {
        var max = 10;
        return function bar(x) {
          if (x > max) {
            console.log(x);
          }
        };
      }
      var f1 = fn();
      f1(11); // 11

闭包的应用场景

应用缓存

   var cacheApp = (function () {
        var cache = {};
        return {
          getResult: function (id) {
            // 如果在内存中,则直接返回
            if (id in cache) {
              return "得到的结果为:" + cache[id];
            }
            //经过耗时函数的处理
            var result = timeFn(id);
            //更新缓存
            cache[id] = result;
            //返回计算的结果
            return "得到的结果为:" + result;
          },
        };
      })();
      //耗时函数
      function timeFn(id) {
        console.log("这是一个非常耗时的任务");
        return id;
      }
      console.log(cacheApp.getResult(23));
      console.log(cacheApp.getResult(23));

代码封装

在编程的时候,我们提倡将一定特征的代码封装到一起,只需要对外暴露对应的方法就可以,从而不用关心内部逻辑的实现。

 <script>
      var stack = (function () {
        //使用数组模拟栈
        var arr = [];
        return {
          push: function (value) {
            arr.push(value);
          },
          pop: function () {
            return arr.pop();
          },
          size: function () {
            return arr.length;
          },
        };
      })();
      stack.push("abc");
      stack.push("def");
      console.log(stack.size()); // 2
      console.log(stack.pop()); // def
	  console.log(stack.size()); // 1
    </script>

闭包常见面试题

第一:如下程序执行的结果为:

获取所单击的li元素的索引值

  <ul>
      <li>a</li>
      <li>b</li>
      <li>c</li>
      <li>d</li>
      <li>e</li>
    </ul>

对应的js代码如下:

  // 获取所单击的`li`元素的索引值
      var list = document.getElementsByTagName("ul")[0].children;
      for (var i = 0; i < list.length; i++) {
        list[i].onclick = function () {
          console.log(i);
        };
      }

可以采用闭包解决这个问题:

  var list = document.getElementsByTagName("ul")[0].children;
      for (var i = 0; i < list.length; i++) {
        (function (index) {
          list[index].onclick = function () {
            console.log(index);
          };
        })(i);
      }

第二:如下程序输出结果是:

  var arr = ["a", "b", "c"];
      for (var i = 0; i < arr.length; i++) {
        setTimeout(function () {
          console.log(arr[i]);
        }, 1000);
      }

代码修改后的内容为:

      var arr = ["a", "b", "c"];
      for (var i = 0; i < arr.length; i++) {
        (function (index) {
          setTimeout(function () {
            console.log(arr[index]);
          }, 1000);
        })(i);
      }

第三:以下程序打印结果是:

      var userName = "zhangsan";
      var person = {
        userName: "lisi",
        method: function () {
          return function () {
            return this.userName;
          };
        },
      };
      console.log(person.method()()); //zhangsan
  var userName = "zhangsan";
      var person = {
        userName: "lisi",
        method: function () {
          var that = this; //用that保存person的this
          return function () {
            return that.userName;
          };
        },
      };
      console.log(person.method()());

第四:以下程序的输出结果

    function create() {
        var a = 100;
        return function () {
          console.log(a);
        };
      }
      var fn = create();
      var a = 200;
      fn(); // 100

第五:以下程序的输出结果:

   function print(fn) {
        var a = 200;
        fn();
      }
      var a = 100;
      function fn() {
        console.log(a); // 100
      }
      print(fn);

闭包优缺点

闭包的优点:

第一:保护函数内变量的安全,实现封装,防止变量流入其它环境发生命名冲突,造成环境污染。

第二:在适当的时候,可以在内存中维护变量并缓存,提高执行效率

闭包的缺点:

消耗内存:通常来说,函数的活动对象会随着执行上下文环境一起被销毁,但是由于闭包引用的是外部函数的活动对象,因此这个活动对象无法被销毁,所以说,闭包比一般的函数需要消耗更多的内存。

12、this指向

常见面试题

我们知道,当我们创建一个构造函数的实例的时候,需要通过new操作符来完成创建,当创建完成后,函数体中的this指向了这个实例。

如下代码所示:

  function Person(userName) {
        this.userName = userName;
      }
      var person = new Person("zhangsan");
      console.log(person.userName);

如果,我们将上面的Person函数当作一个普通函数来调用执行,那么对应的this会指向谁呢?

 function Person(userName) {
        this.userName = userName;
      }
      Person("lisi");
      console.log(window.userName);

通过上面的程序,我们可以总结出,this指向的永远是函数的调用者。

第一:如下程序的输出结果:

    var a = 10;
      var obj = {
        a: 120,
        method: function () {
          var bar = function () {
            console.log(this.a); // 10
          };
          bar();//这里是通过window对象完成bar方法的调用
          return this.a;
        },
      };
      console.log(obj.method()); // 120

第二:如下程序的输出结果是:

   var num = 10;
      function Person() {
        //给全局变量重新赋值
        num = 20;
        // 实例变量
        this.num = 30;
      }
      Person.prototype.getNum = function () {
        return this.num;
      };
      var person = new Person();
      console.log(person.getNum()); // 30

第三:如下程序的输出结果是:

    function fn() {
        console.log(this);
      }
      let obj = {
        fn: fn,
      };
      fn(); //window
      obj.fn(); //obj

第四:如下程序的输出结果是:

 var fullName = "language";
      var obj = {
        fullName: "javascript",
        prop: {
          getFullName: function () {
            return this.fullName;
          },
        },
      };
      console.log(obj.prop.getFullName()); // undefined
      var test = obj.prop.getFullName; // language
      console.log(test());

第五:如下程序的输出结果是:

      var val = 1;
      var json = {
        val: 10,
        dbl: function () {
          val *= 2; //这里由于前面没有添加this,也就是没有写成this.val,所以这里的val指向了全局变量
        },
      };
      json.dbl();
      console.log(json.val + val); // 12

如果将上面的题目修改成如下的形式:

var val = 1
var json = {
  val: 10,
  dbl: function () {
    this.val *= 2 //20
  }
}
json.dbl() 
console.log(json.val + val)//21  20+1=21

第六,如下程序的输出结果是:

  var num = 10;
      var obj = { num: 20 };
      obj.fn = (function (num) {
        this.num = num * 3;
        num++;
        return function (n) {
          this.num += n;
          num++;
          console.log(num);
        };
      })(obj.num);
      var fn = obj.fn;
      fn(5);
      obj.fn(10);
      console.log(num, obj.num);

第七:this 指向call()函数,apply()函数,bind()函数调用后重新绑定的对象。

我们知道通过call()函数,apply()函数,bind()函数可以改变函数执行的主体,如果函数中存在this关键字,则this指向call()函数,apply()函数,bind()函数处理后的对象。

代码如下:

 //全局变量
      var value = 10;
      var obj = {
        value: 20,
      };
      // 全局函数
      var method = function () {
        console.log(this.value);
      };
      method(); // 10
      method.call(obj); // 20
      method.apply(obj); // 20
      var newMethod = method.bind(obj);
      newMethod(); // 20

下面我们再来看一段代码,看一下对应的执行结果:

<body>
    <button id="btn">获取用户信息</button>
    <script>
      var userInfo = {
        data: [
          { userName: "zhangsan", age: 20 },
          { userName: "lisi", age: 21 },
        ],
        getUserInfo: function () {
          var index = 1;
          console.log(this.data[index].userName + " " + this.data[index].age);
        },
      };
      var btn = document.getElementById("btn");
      btn.onclick = userInfo.getUserInfo;
    </script>
  </body>

修改后的代码:

   var btn = document.getElementById("btn");
      //   btn.onclick = userInfo.getUserInfo;
      btn.onclick = userInfo.getUserInfo.bind(userInfo);

第八、如下程序的输出结果是:

    <button id="btn">获取用户信息</button>
    <script>
      var userInfo = {
        data: [
          { userName: "zhangsan", age: 20 },
          { userName: "lisi", age: 21 },
        ],
        getUserInfo: function () {
          this.data.forEach(function (p) {
            console.log(this);
          });
        },
      };
      var btn = document.getElementById("btn");
      //   btn.onclick = userInfo.getUserInfo;
      btn.onclick = userInfo.getUserInfo.bind(userInfo);
    </script>

修改后的代码:

<script>
      var userInfo = {
        data: [
          { userName: "zhangsan", age: 20 },
          { userName: "lisi", age: 21 },
        ],
        getUserInfo: function () {
          var that = this;//保存this
          this.data.forEach(function (p) {
            console.log(that);//这里的that 指的就是当前的userInfo对象。
          });
        },
      };
      var btn = document.getElementById("btn");
      //   btn.onclick = userInfo.getUserInfo;
      btn.onclick = userInfo.getUserInfo.bind(userInfo);
    </script>

或者是修改成箭头函数

     var userInfo = {
        data: [
          { userName: "zhangsan", age: 20 },
          { userName: "lisi", age: 21 },
        ],
        getUserInfo: function () {
          //   var that = this;
          this.data.forEach((p) => {
            console.log(this);
          });
        },
      };
      var btn = document.getElementById("btn");
      //   btn.onclick = userInfo.getUserInfo;
      btn.onclick = userInfo.getUserInfo.bind(userInfo);

13、call()函数,apply( )函数,bind( )函数的使用与区别

在前面我们简单的说过call( )函数,apply( )函数,bind( )函数,的作用。

call( )函数,apply( )函数,bind( )函数,的作用都是改变this的指向,但是在使用方式上是有一定的区别的。

下面我们分别来看一下它们各自的使用方式:

call( )函数的基本使用

基本语法如下:

function.call(thisObj,arg1,arg2,...)

function表示的是:需要调用的函数。

thisObj表示:this指向的对象,也就是this将指向thisObj这个参数,如果thisObj的值为null或者是undefined,则this指向的是全局对象。

arg1,arg2,..表示:调用的函数需要的参数。

   function add(a, b) {
        console.log(this);
        console.log(a + b);
      }
      function sub(a, b) {
        console.log(a - b);
      }

      add.call(sub, 3, 1);// 调用add方法,但是add方法中的this指向的是sub,最终的输出结果是4

apply( )函数的基本使用

apply()函数的作用与call()函数的作用是一样的,不同的是在传递参数的时候有一定的差别

语法格式如下:

function.apply(thisObj,[argsArray])

function表示的是:需要调用的函数。

thisObj:this指向的对象,也就是this将指向thisObj这个参数,如果thisObj的值为null或者是undefined,则this指向的是全局对象。

[argsArray]:表示的是函数需要的参数会通过数组的形式进行传递,如果传递的不是数组或者是arguments对象,会抛出异常。

  function add(a, b) {
        console.log(this); // 这里指向的是sub
        console.log(a + b);
      }
      function sub(a, b) {
        console.log(a - b);
      }

      add.apply(sub, [3, 1]); 

bind函数的基本使用

function.bind(thisObj,arg1,arg2,...)

通过上面语法格式,可以看出bind函数与call函数的参数是一样的。

不同 的是bind函数会返回一个新的函数,可以在任何时候进行调用。

      function add(a, b) {
        console.log(this); // 这里指向的是sub
        console.log(a + b);
      }
      function sub(a, b) {
        console.log(a - b);
      }

      var newFun = add.bind(sub, 3, 1); //bind 返回的是一个新的函数。
      newFun();//完成对add函数的调用,同时this指向了sub

三个函数的比较

通过前面对三个函数的基本使用,可以看出,它们共同点就是改变this的指向。

不同点:

call()函数与apply()函数,会立即执行函数的调用,而bind返回的是一个新的函数,可以在任何时候进行调用。

call()函数与bind函数的参数是一样的,而apply函数第二个参数是一个数组或者是arguments对象。

应用场景

这里,我们重点看一下,关于call()函数,bind()函数,apply()函数的应用场景。

求数组中的最大值与最小值

 var arr = [3, 6, 7, 1, 9];
      console.log(Math.max.apply(null, arr));
      console.log(Math.min.apply(null, arr));

arguments转换成数组

   function fn() {
        var arr = Array.prototype.slice.call(arguments);
        arr.push(6);
        return arr;
      }
      console.log(fn(1, 2));

继承的实现

function Person(userName, userAge) {
        this.userName = userName;
        this.userAge = userAge;
      }
      function Student(name, age, gender) {
        Person.call(this, name, age);
        this.gender = gender;
      }
      var student = new Student("zhangsan", 20, "男");
      console.log(
        "userName=" +
          student.userName +
          ",userAge=" +
          student.userAge +
          ",gender=" +
          student.gender
      );

改变匿名函数的this指向

首先看一下如下程序的执行结果:

  var person = [
        { id: 1, userName: "zhangsan" },
        { id: 2, userName: "lisi" },
      ];
      for (var i = 0; i < person.length; i++) {
        (function (i) {
          this.print = function () {
            console.log(this.id);
          };
          this.print();
        })(i);
      }

具体的实现方式如下:

    var person = [
        { id: 1, userName: "zhangsan" },
        { id: 2, userName: "lisi" },
      ];
      for (var i = 0; i < person.length; i++) {
        (function (i) {
          this.print = function () {
            console.log(this.id);
          };
          this.print();
        }.call(person[i], i));
      }

手写call、apply及bind函数

call方法的实现

      Function.prototype.myCall = function (context) {
       
        var args = [...arguments].slice(1);
       
        context = context || window;
        
        context.fn = this;
     
        var result = context.fn(...args);
        return result;
      };
      function Add(num1, num2) {
        console.log(this);
        console.log(num1 + num2);
      }
      function Sub(num1, num2) {
        console.log(num1 - num2);
      }
      Add.myCall(Sub, 6, 3);

apply函数的实现

 Function.prototype.myApply = function (context) {
        var result = null;
        context = context || window;
        context.fn = this;
        if (arguments[1]) {
          // console.log("arguments=", arguments[1]);// arguments= (2) [6, 3]
          result = context.fn(...arguments[1]);
        } else {
          result = context.fn();
        }
        return result;
      };
      function Add(num1, num2) {
        console.log(this);
        console.log(num1 + num2);
      }
      function Sub(num1, num2) {
        console.log(num1 - num2);
      }
      Add.myApply(Sub, [6, 3]);

bind函数的实现

   Function.prototype.myBind = function (context) {
        // 获取参数
        var args = [...arguments].slice(1), // [1,5]
          fn = this;
        // console.log(this);//Add
        return function Fn() {
          // console.log(this); //Window
          return fn.apply(context, args);
        };
      };
      function Add(num1, num2) {
        console.log(this);
        console.log(num1 + num2);
      }
      function Sub(num1, num2) {
        console.log(num1 - num2);
      }
      var newFun = Add.myBind(Sub, 1, 5);
      newFun();
  <script>
      function add(a, b) {
        console.log(this); // 这里指向的是sub
        console.log(a + b);
      }
      function sub(a, b) {
        console.log(a - b);
      }

      var newFun = add.bind(sub, 3); //bind 返回的是一个新的函数。
      newFun(2); //完成对add函数的调用,同时this指向了sub
    </script>

下面,我们就实现一下关于myBind方法参数的模拟。

    Function.prototype.myBind = function (context) {
        // 获取参数
        var args = [...arguments].slice(1),
          fn = this;
        // console.log(this);//Add
        return function Fn() {
          // console.log(this); //Window
          //这里是调用bind函数的时候传递的参数,将其转换成数组
          var bindArgs = Array.prototype.slice.call(arguments);
          //下面完成参数的拼接
          return fn.apply(context, args.concat(bindArgs));
        };
      };
      function Add(num1, num2) {
        console.log(this);
        console.log(num1 + num2);
        return 10;
      }
      function Sub(num1, num2) {
        console.log(num1 - num2);
      }
      var newFun = Add.myBind(Sub, 1);
      console.log(newFun(8));

14、回调函数有什么缺点

JavaScript编程过程中,我们经常会写回调函数。

我们知道在JavaScript中函数也是一种对象,对象可以作为参数传递给函数,因此函数也可以作为参数传递给另外一个函数,这个作为参数的函数就是回调函数。

例如,如下的代码示例:

const btn=document.getElementById('btn');
btn.addEventListener('click',function(event){
    
})

回调函数有一个比较严重的问题,就是很容易出现回调地狱的问题。也就是实现了回调函数不断的嵌套。

setTimeout(() => {
    console.log(1)
    setTimeout(() => {
        console.log(2)
        setTimeout(() => {
            console.log(3)
    
        },3000)
    
    },2000)
},1000)

以上的代码就是典型的回调地狱的问题,这样的代码是非常不利于阅读和维护的。

所以在ES6中提供了Promise以及async/await来解决地狱回调的问题。关于这块内容

15、 为什么函数被称为一等公民?

JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回

同时函数还可以作为类的构造函数,完成对象实例的创建。所以说,这种多重身份让JavaScript中的函数变得非常重要,所以说函数被称为一等公民。

nsole.log(this); // 这里指向的是sub
console.log(a + b);
}
function sub(a, b) {
console.log(a - b);
}

  var newFun = add.bind(sub, 3); //bind 返回的是一个新的函数。
  newFun(2); //完成对add函数的调用,同时this指向了sub
</script>

下面,我们就实现一下关于`myBind`方法参数的模拟。

```js
    Function.prototype.myBind = function (context) {
        // 获取参数
        var args = [...arguments].slice(1),
          fn = this;
        // console.log(this);//Add
        return function Fn() {
          // console.log(this); //Window
          //这里是调用bind函数的时候传递的参数,将其转换成数组
          var bindArgs = Array.prototype.slice.call(arguments);
          //下面完成参数的拼接
          return fn.apply(context, args.concat(bindArgs));
        };
      };
      function Add(num1, num2) {
        console.log(this);
        console.log(num1 + num2);
        return 10;
      }
      function Sub(num1, num2) {
        console.log(num1 - num2);
      }
      var newFun = Add.myBind(Sub, 1);
      console.log(newFun(8));

14、回调函数有什么缺点

JavaScript编程过程中,我们经常会写回调函数。

我们知道在JavaScript中函数也是一种对象,对象可以作为参数传递给函数,因此函数也可以作为参数传递给另外一个函数,这个作为参数的函数就是回调函数。

例如,如下的代码示例:

const btn=document.getElementById('btn');
btn.addEventListener('click',function(event){
    
})

回调函数有一个比较严重的问题,就是很容易出现回调地狱的问题。也就是实现了回调函数不断的嵌套。

setTimeout(() => {
    console.log(1)
    setTimeout(() => {
        console.log(2)
        setTimeout(() => {
            console.log(3)
    
        },3000)
    
    },2000)
},1000)

以上的代码就是典型的回调地狱的问题,这样的代码是非常不利于阅读和维护的。

所以在ES6中提供了Promise以及async/await来解决地狱回调的问题。关于这块内容

15、 为什么函数被称为一等公民?

JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回

同时函数还可以作为类的构造函数,完成对象实例的创建。所以说,这种多重身份让JavaScript中的函数变得非常重要,所以说函数被称为一等公民。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值