函数进阶

158 阅读8分钟

目标

  • 能够说出函数的多种定义和调用方式
  • 能够说出和改变函数内部this的指向
  • 能够说出严格模式的特点
  • 能够把函数作为参数和返回值传递
  • 能够说出闭包的作用
  • 能够说出递归的两个条件
  • 能够说出深拷贝和浅拷贝的区别

目录

  • 函数的定义和调用
  • this
  • 严格模式
  • 高阶函数
  • 闭包
  • 递归

1. 函数的定义和调用

1.1 函数的定义方式

  1. 函数声明方式function关键字(命名函数)
  2. 函数表达式(匿名函数)
  3. new Function()
var fn = new Funciton('参数1','参数2'...,'函数体')
  • Funcion里面参数都必须是字符串格式
  • 第三种方式执行效率低,也不方便书写,因此较少使用
  • 所有函数都是Funciton的实例(对象)
  • 函数也属于对象

image.png

1.2 函数的调用方式

  1. 普通函数
  2. 对象的方法
  3. 构造函数
  4. 绑定事件函数
  5. 定时器函数
  6. 立即执行函数

2. this

2.1 函数内this的指向

这些this的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this的指向不同,一般指向我们的调用者。

  • 普通函数调用:window
  • 构造函数调用:实例对象 原型对象里面的方法也指向实例对象
  • 对象方法调用:该方法所属对象
  • 事件绑定方法:绑定事件对象
  • 定时器函数:window
  • 立即执行函数:window

2.2 改变函数内部this指向

JavaScript为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部this的指向问题,常用的有bind()、call()、apply()三种方法。

1. call方法

call()方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的this指向。

fun.call(thisArg,arg1,arg2,...)

2. apply方法

apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的this指向。

fun.apply(thisArg, [argsArray])
  • thisArg:在fun函数运行时指定的this值
  • argsArray:传递的值,必须包含在数组里面
  • 返回值就是函数的返回值,因为它就是调用函数

3. bind 方法

bind()方法不会调用函数。但是能改变函数内部this指向

fun.bind(thisArg,arg1,arg2,...)
  • thisArg:在fun函数运行时指定的this值
  • arg1,arg2:传递的其他参数
  • 返回由指定的this值和初始化参数改造的原函数拷贝

2.3 call apply bind 总结

相同点:

都可以改变函数内部的this指向。

区别点:

  1. call和apply 会调用函数,并且改变函数内部this指向。
  2. call 和 apply 传递的参数不一样,call传递参数aru1,aru2...形式,apply必须数组形式[arg]
  3. bind 不会调用函数,可以改变函数内部this指向

主要应用场景:

  1. call 经常做继承
  2. apply 经常跟数组有关系,比如借助于数学对象实现数组最大值最小值
  3. bind 不调用函数,但是还想改变this指向。比如改变定时器内部的this指向

3. 严格模式

3.1 什么是严格模式

JavaScript除了提供正常模式外,还提供了严格模式(strict mode)。ES5的严格模式是采用具有限制性JavaScript变体的一种方式,即在严格的条件下运行JS代码。

严格模式在IE10以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。

严格模式对正常的JavaScript语义化做了一些更改:

  1. 消除了Javascript语法的一些不合理、不严谨之处,减少了一些怪异行为。
  2. 消除代码运行的一些不安全之处3,保证代码运行的安全。
  3. 提高编译器效率,增加运行速度
  4. 禁用了在ECMAScipt的未来版本中可能会定义的一些语法,为未来新版本的Javascipt做好铺垫。比如一些保留字如:class,enum,export,extends,import,super不能做变量名

3.2 开启严格模式

严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式为函数开启严格模式两种情况。

1. 为脚本开启严格模式

为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句"use strict";(或'use strict';)

 <!-- 为整个脚本(script标签)开启严格模式 -->
    <script>
        'use strict';
        // 下面的js 代码就会按照严格模式执行代码
    </script>
    <script>
        (function () {
            'use strict';
        })();
    </script>

2. 为函数开启严格模式

要给某个函数开启严格模式,需要把"use strict";(或'use strict';)声明放在函数体所有语句之前。

<script>
        function fn() {
            'use strict';
            // 下面的代码按照严格模式执行
        }
        function fun() {
            // 里面的代码按照普通模式执行
        }
    </script>

3.4 严格模式中的变化

严格模式对Javascript的语法和行为,都做了一些改变。

1. 变量规定

  1. 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var命令声明,然后再使用。
  2. 严禁删除已经声明变量。例如,delete x;语法是错误的。

2. 严格模式下this指向问题

  1. 以前在全局作用域函数中的this指向window对象
  2. (重点)严格模式下全局作用域中函数中的this是undefined
  3. 以前构造函数时不加new也可以调用,当普通函数,this指向全局对象
  4. 严格模式下,如果构造函数不加new调用,this会报错
  5. new实例化的构造函数指向创建的对象实例
  6. 定时器里的this还是指向window
  7. 事件、对象还是指向调用者

3. 函数变化

  1. 函数不能有重名的参数。
  2. 函数必须声明在顶层新版本的Javascript会引入"块级作用域"(ES6中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数。

更多参考:

developer.mozilla.org/zh-CN/docs/…

4. 高阶函数

高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。

 <script>
        function fn(callback) {
            callback&&callback();
        }
        fn(function() {alert('hi')})
    </script>
<script>
        function fn() {
           return function() {}
        }
        fn();
    </script>

此时fn就是一个高阶函数。

函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。

5. 闭包

5.1 变量作用域

变量根据作用域的不同分为两种:全局变量和局部变量。

  1. 函数内部可以使用全局变量
  2. 函数外部不可以使用局部变量
  3. 当函数执行完毕,本作用域内的局部变量会销毁

5.2 什么是闭包

闭包(closure)指有权访问另一个函数作用域中变量的函数。——————Javascript高级程序设计

简单理解就是,一个作用域可以访问另外一个函数内部的局部变量。

(如果一个函数内有一个局部变量,别的作用域可以访问这个变量,此时就有闭包产生。这个变量产生的函数就叫闭包函数)

闭包的主要主要:延伸了变量的作用范围

5.5 闭包案例

  1. 循环注册点击事件
  2. 循环中的setTimeout()
  3. 计算打车价格

5.6 闭包总结

1. 闭包是什么?

闭包是一个函数(一个作用域可以访问另外一个函数的局部变量)

2. 闭包的作用是什么?

延伸了变量的作用范围

6. 递归

6.1 什么是递归?

如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。

简单理解:函数内部自己调用自己,这个函数就是递归函数

递归函数的作用和循环效果一样。

由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return。

6.3 利用递归求:根据id返回对应的数据对象

  var data = [{
      id:1,
      name:'家电',
      goods:[{
        id:11,
        gname:'冰箱',
        goods:[{
          id:111,
          gname:'海尔'
        },
        {
          id:112,
          gname:'美的'
        }]
      },{
        id:12,
        gname:'洗衣机'
      }]
    },{
      id:2,
      name:'服饰'
    }];
    // 我们想要做输入id号,就可以返回的数据对象
    //  1. 利用 forEach 去遍历里面的每一个对象
    function getID(json, id) {
      var o = {};
      json.forEach(function(item) {
        // console.log(item); // 2个数组元素
        if(item.id == id) {
          o = item;
          return item;
          // console.log(item);
          // 2. 我们想要得到里层的数据 11 12 可以利用递归函数
          // 里面应该有goods这个数组并且长度不为0
        } else if (item.goods && item.goods.length > 0) {
          o = getID(item.goods, id);
        }
      });
      return o;
    }
   console.log(getID(data, 1));
   console.log(getID(data, 2));
   console.log(getID(data, 11));
   console.log(getID(data, 111));

6.4 浅拷贝和深拷贝

  1. 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用
  2. 深拷贝拷贝多层,每一级别的数据都会拷贝
  3. Object.assign(target,...sources) es6新增方法可以浅拷贝

封装函数进行深拷贝

 // 深拷贝拷贝多层,每一级别的数据都会拷贝
    var obj = {
      id: 1,
      name: 'andy',
      msg: {
        age: 18
      },
      color: ['pink', 'red']
    };
    var o = {};
    // 封装函数
    function deepCopy(newobj, oldobj) {
      for (var k in oldobj) {
        // 判断我们的属性值属于哪种数据类型
        // 1. 获取属性值 oldobj[k]
        var item = oldobj[k];
        // 2. 判断这个值是否是数组
        if (item instanceof Array) {
          newobj[k] = [];
          deepCopy(newobj[k], item);
        } else if (item instanceof Object) {
          // 3. 判断这个值是否是对象
          newobj[k] = {};
          deepCopy(newobj[k], item)
        } else {
          // 4. 属于简单数据类型
          newobj[k] = item;
        }
      }
    }
    deepCopy(o, obj);
    console.log(o);