美文网首页JavaScript前端
【前端 JavaScript 高级】03 - 函数进阶 + 函数

【前端 JavaScript 高级】03 - 函数进阶 + 函数

作者: itlu | 来源:发表于2020-12-28 15:05 被阅读0次

1. 函数的定义和调用

1.1 函数的定义方式

  1. 方式1 使用 function 关键字 (命名函数)
function fn(){}
  1. 方式2 函数表达式(匿名函数)
var fn = function(){}
  1. 方式3 new Function()
ar f = new Function('a', 'b', 'console.log(a + b)');
f(1, 2);

var fn = new Function('参数1','参数2'..., '函数体')
注意
/*Function 里面参数都必须是字符串格式
第三种方式执行效率低,也不方便书写,因此较少使用
所有函数都是 Function 的实例(对象)  
函数也属于对象
*/

1.2 函数的调用

 // 1.对象的方法
  let obj = {
    sayHi: function () {
      console.log('hi');
    }
  }

  // 2.普通函数
  function f() {
    console.log('我是一个函数');
  }

  // 函数的调用方式  直接调用或者 使用call()调用
  f.call();

  // 3.构造函数
  function Star(name, age) {
    this.name = name;
    this.age = age;
  }

  // 4.绑定事件函数
  let btn = document.querySelector('button');
  btn.onclick = function () {
    console.log('你点击了我');
  }


  // 5. 定时器函数
  setInterval(() => {
    alert('我是函数');
  }, 1000);


  // 6. 立即执行函数
  (function () {
    console.log('我是立即执行函数');
  })()

2. this

2.1 函数内部的this指向

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

  2. 一般指向我们的调用者。

函数内部的this指向
// 1.对象的方法 this 指向的是实例对象
  let obj = {
    sayHi: function () {
      console.log('hi');
    }
  }

  // 2.普通函数 this指向的是window
  function f() {
    console.log('我是一个函数');
  }

  // 函数的调用方式  直接调用或者 使用 call() 调用
  f.call();

  // 3.构造函数 this 指向的是实例对象
  function Star(name, age) {
    this.name = name;
    this.age = age;
  }

  // 4.绑定事件函数 this 指向的是绑定事件的对象
  let btn = document.querySelector('button');
  btn.onclick = function () {
    console.log('你点击了我');
  };


  // 5. 定时器函数 里面的this 指向的是 window
  window.setInterval(() => {
    alert('我是函数');
  }, 1000);


  // 6. 立即执行函数 指向的是window
  (function () {
    console.log('我是立即执行函数');
  })();

2.2 改变函数内部 this 指向

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

  2. 应用场景: 经常做继承。

/**
   * bind()
   *
   * apply()
   *
   * call()
   */
  let obj = {
    name: 'andy'
  };


  function fun(a,b) {
    console.log(this);
    console.log(a + b);
  }
  // call 可以调用函数可以改变this的指向
  fun.call(obj, 123, 123);

  // call 的主要 作用可以实现继承
  /**
   *
   * @param name
   * @param age
   * @param sex
   * @constructor
   */
  function Father(name,age,sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
  }


  /**
   *
   * @param name
   * @param age
   * @param sex
   * @constructor
   */
  function Son(name, age, sex) {
    Father.call(this, name, age, sex);
  }
  1. 以上代码的运行结果:
call方法
apply方法
  1. apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。

  2. 应用场景: 经常跟数组有关系。

  let o = {
    name: 'andy'
  };

  function fn() {
    console.log(this);
  }

  fn.apply(o, ['pink']);
  // 1. apply也是调用函数,第二个可以改变函数内部的this指向

  // 2. 但是它的参数必须是数组

  // 3. apply的主要应用比如说我们可以利用apply借助于数学内置对象求最大值
  let arr = [1, 2, 3, 512, 456];
  // 使用数学内置对象求数组中的最值
  let max = Math.max.apply(Math, arr);
  let min = Math.min.apply(Math, arr);
  console.log(max);
  console.log(min);
apply方法
bind方法
  1. bind() 方法不会调用函数,但是能改变函数内部this指向,返回的是原函数改变this之后产生的新函数。

  2. 如果只是想改变this 指向,并且不想调用这个函数的时候,可以使用bind

  3. 应用场景:不调用函数,但是还想改变this指向。

  // bind 捆綁的意思

  let o = {
    name: 'andy'
  }

  function fn(a, b) {
    console.log(this);
    console.log(a + b);
  }

  // bind 不会调用原来的函数但是可以改变原来函数内部的 this 指向
  let f = fn.bind(o, 1, 2);
  f();
  1. 以上代码运行结果:
bind方法
bind 方法在实际开发中的使用:
  1. 让按钮在 3秒 后按钮 变得可用。
 // 1. 如果有的函数我们不需要立即调用,但是又想改变这个函数的内部this指向此时用bind

  // 2. 让按钮在 3秒后按钮 变得可用
  let btn = document.querySelector('button');


  /**
   *
   * 使用笨办法
   * */
 /* btn.onclick = function () {
    this.disabled = true;

    /!**
     * 2 秒之后按钮恢复
     *!/
    setTimeout(function () {
      // 原来 定时器里面的 this 是指向的 window
      // 现在改变了指向了
      btn.disabled = false;
    }, 2000)
  };*/

  /**
   *
   * 为按钮定义一个单击事件
   * */
  btn.onclick = function () {
    this.disabled = true;

    /**
     * 2 秒之后按钮恢复
     */
    setTimeout(function () {
      // 原来 定时器里面的 this 是指向的 window
      // 现在改变了指向了
      this.disabled = false;
    }.bind(this), 2000)
  };
bind 处理多个对象中this的指向问题
  // 获取所有的按钮
  let btns = document.querySelectorAll('button');
  for (let i = 0; i < btns.length; i++) {
    btns[i].onclick = function () {
      this.disabled = true;
      setTimeout(function () {
        // btns[i].disabled = false;
        // bind改变了this的指向
        this.disabled = false;
        // 此时的this指向的时btns[i]
      }.bind(this), 2000);
    };
  }

2.3 call、apply、bind三者的异同

共同点 : 都可以改变this指向。
不同点:
  1. callapply 会调用函数, 并且改变函数内部this指向。

  2. callapply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递。

  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. 提高编译器效率,增加运行速度。

  4. 禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class,enum,export, extends, import, super 不能做变量名。

3.2 开启严格模式

严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。
  1. 情况一 :为脚本开启严格模式:有的script 脚本是严格模式,有的 script脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他script脚本文件。
 // 开启严格模式 下面的js代码就会按照严格模式执行代码
  'use strict';
  // 但是严格模式只会在IE10以上才会被支持

  1. 情况二: 为函数开启严格模式:要给某个函数开启严格模式,需要把“use strict”; (或 'use strict'; ) 声明放在函数体所有语句之前。
function fn(){
  "use strict";
  return "123";
} 
//当前fn函数开启了严格模式

3.4 严格模式中的变化

  1. 严格模式对 Javascript 的语法和行为,都做了一些改变。
 'use strict'
    num = 10 
    console.log(num)//严格模式后使用未声明的变量
    --------------------------------------------------------------------------------
    var num2 = 1;
    delete num2;//严格模式不允许删除变量
    --------------------------------------------------------------------------------
    function fn() {
     console.log(this); // 严格模式下全局作用域中函数中的 this 是 undefined
    }
    fn();  
    ---------------------------------------------------------------------------------
    function Star() {
         this.sex = '男';
    }
    // Star();严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错.
    var ldh = new Star();
    console.log(ldh.sex);
    ----------------------------------------------------------------------------------
    setTimeout(function() {
      console.log(this); //严格模式下,定时器 this 还是指向 window
    }, 2000);  

  1. 更多严格模式要求参考

4. 高阶函数

  1. 高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。此时fn 就是一个高阶函数:

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

  3. 同理函数也可以作为返回值传递回来。

  // 高阶函数是对其他函数进行操作的函数,它是接收函数作为参数或者将函数作为返回值输出
  function fn(a, b, callback) {
    console.log(a + b);
    callback && callback();
  }

  fn(1, 2, function () {
    console.log('我是回调函数,在最后调用');
  });

5. 闭包

5.1 变量的作用域复习

变量根据作用域的不同分为两种:全局变量和局部变量。
  1. 函数内部可以使用全局变量。

  2. 函数外部不可以使用局部变量。

  3. 当函数执行完毕,本作用域内的局部变量会销毁。

5.2 什么是闭包

  1. 闭包(closure)指有权访问另一个函数作用域中变量的函数。简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。
  /**
   * 闭包(closure) :指的是有权访问另一个函数作用域中变量的函数
   * 简单理解:一个作用域 fn 可以访问另一个函数 fun 内部的局部变量
   */
  function fn() {
    // fn 就是一个闭包函数
    let num = 10;

    function fun() {
      // fun 访问了 fn 内部的 一个局部变量 所以 fn 就是一个局部变量
      console.log(num);
      // fun 作用域中访问了另一个作用域的num num 变量所在的作用域就是闭包 闭包是一种现象
    }

    fun();

  }

  fn();

5.3 闭包的作用

  1. 作用:延伸变量的作用范围。
  /**
   * 我们fn外边的作用域可以访问fn内部的局部变量
   *
   * 闭包的主要作用 : 延伸了变量的作用范围
  */
  function fn() {

    let num = 10;

    function fun() {
      console.log(num);
    }

    return fun;
  }

  let f = fn();

  /**
   * 执行之后就会产生闭包
   * */
  f();
  /*
    // 类似于
    let f = function fun() {
      console.log(num);
    }
  */

5.4 闭包的案例

  1. 利用闭包的方式得到当前 li 的索引号;
for (var i = 0; i < lis.length; i++) {
// 利用for循环创建了4个立即执行函数
// 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
(function(i) {
    lis[i].onclick = function() {
      console.log(i);
    }
 })(i);
}
  1. 闭包应用- 3秒钟之后,打印所有 li 元素的内容;
 // 闭包案例:点击你输出当前li的索引号

  var lis = document.querySelectorAll('li');

  // 1. 利用我们动态添加属性的方式
  /* var lis = document.querySelectorAll('li');
   for (var i = 0; i < lis.length; i++) {
     lis[i].onclick = function () {
       /!**
        * 点击事件是异步执行的
        *!/
       console.log(i); // 6 这里始终都是 6
     }
   }*/

  // 解决:使用动态添加属性的方式

  /*for (var i = 0; i < lis.length; i++) {
    lis[i].index = i; // 先将index 存一份到每一个li项中
    lis[i].onclick = function () {
      /!**
       * 点击事件是异步执行的
       *!/
      console.log(this.index); // 6 这里始终都是 6
    }
  }*/

  // 2. 利用闭包的方式得到当前小li的索引号
  for (let i = 0; i < lis.length; i++) {
    (function (i) {
      lis[i].onclick = function () {
        console.log(i);
      }
    })(i)
  }
  1. 闭包应用-计算打车价格
/*需求分析
打车起步价13(3公里内),  之后每多一公里增加 5块钱.  用户输入公里数就可以计算打车价格
如果有拥堵情况,总价格多收取10块钱拥堵费*/

 var car = (function() {
     var start = 13; // 起步价  局部变量
     var total = 0; // 总价  局部变量
     return {
       // 正常的总价
       price: function(n) {
         if (n <= 3) {
           total = start;
         } else {
           total = start + (n - 3) * 5
         }
         return total;
       },
       // 拥堵之后的费用
       yd: function(flag) {
         return flag ? total + 10 : total;
       }
    }
 })();
console.log(car.price(5)); // 23
console.log(car.yd(true)); // 33

5.5 案例:关于闭包的思考题

 var name = "The Window";
   var object = {
     name: "My Object",
     getNameFunc: function() {
     return function() {
     return this.name;
     };
   }
 };
console.log(object.getNameFunc()())
-----------------------------------------------------------------------------------
var name = "The Window";  
  var object = {    
    name: "My Object",
    getNameFunc: function() {
    var that = this;
    return function() {
    return that.name;
    };
  }
};
console.log(object.getNameFunc()())

6. 递归

6.1 什么是递归

递归:如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。简单理解:函数内部自己调用自己, 这个函数就是递归函数。
注意:递归函数的作用和循环效果一样,由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return

6.2 利用递归求1~n的阶乘

//利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
 function fn(n) {
     if (n == 1) { //结束条件
       return 1;
     }
     return n * fn(n - 1);
 }
 console.log(fn(3)); // 6

6.3 利用递归求斐波那契数列

// 利用递归函数求斐波那契数列(兔子序列)  1、1、2、3、5、8、13、21...
// 用户输入一个数字 n 就可以求出 这个数字对应的兔子序列值
// 我们只需要知道用户输入的n 的前面两项(n-1 n-2)就可以计算出n 对应的序列值
function fb(n) {
  if (n === 1 || n === 2) {
        return 1;
  }
  return fb(n - 1) + fb(n - 2);
}
console.log(fb(3));

6.4 利用递归遍历数据(多维数组)

var data = [{
    id: 1,
    name: '家电',
    goods: [{
      id: 11,
      gname: '冰箱',
      goods: [{
        id: 111,
        gname: '海尔'
      }, {
        id: 112,
        gname: '美的'
      },]
    }, {
      id: 12,
      gname: '洗衣机'
    }]
  }, {
    id: 2,
    name: '服饰'
  }];

  // 当我们输入想要输入的id好,就可以返回对应的数据对象 
  function getId(data, id) {
    var object = {};
    // 使用forEach遍历数组
    data.forEach(function (item) {
      if (item.id === id) {
        object = item;
        return item;
        /**
         * 使用递归遍历多维数组
         */
      } else if (item.goods && item.goods.length > 0) {
        object = getId(item.goods, id);
      }
    });
    return object;
  }

  console.log(getId(data, 11));
  console.log(getId(data, 1));
  console.log(getId(data, 111));
  console.log(getId(data, 666));

7. 浅拷贝

  1. 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用。 拷贝对象的引用会导致修改拷贝的对象导致原来的对象也被修改。
  // 1. 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用

  // 2. 深拷贝拷贝的是多层,每一级的数据都会拷贝

  let obj = {
    id: '1',
    name: 'andy',
    msg: {
      age: 18
    }
  };

  // 浅拷贝遇到对象级别的只会拷贝对象的引用
  let target = {};
  // es6 中实现浅拷贝的语法糖
  Object.assign(target, obj);

  // 修改对象级别的属性
  target.msg.age = 100;
  // 打印拷贝之后的对象
  console.log(target);
  // 打印源对象
  console.log(obj);
  1. 运行结果:
浅拷贝测试运行

8. 深拷贝

  1. 深拷贝拷贝的是多层,每一级的数据都会拷贝。
let obj = {
    id: '1',
    name: '张三',
    msg: {
      title: '今天真开心',
      content: '666',
      format: {
        date: '2020',
        birthday: '1998'
      }
    },
    size: ['big', 'small', 'mini']
  };

  let target = {};

  /**
   * 实现对对象的深拷贝
   * @param targetObj
   * @param oldObj
   */
  function deepCopy(targetObj, oldObj) {
    // 1. 循环遍历对象
    for (let key in oldObj) {
      // 判断我们的属性属于哪种数据类型

      // 1. 获取属性值oldObj[key]
      let item = oldObj[key];
      if (item instanceof Array) {
        // 如果属性是一个数组
        targetObj[key] = [];
        // 递归进行拷贝
        deepCopy(targetObj[key], item);
      } else if (item instanceof Object) {
        // 3. 判断当前的属性是否为一个对象 Object
        targetObj[key] = {};
        deepCopy(targetObj[key], item);
      } else {
        // 如果属性是一个普通的属性
        targetObj[key] = item;
      }
    }

  }

  deepCopy(target, obj);
  console.log(target);


  // 修改赋值之后的对象
  target.msg.title = '李四';
  console.log(target); // 原来对象的属性被修改了
  console.log(obj); // 拷贝的对象的属性未被修改
深拷贝测试

相关文章

  • 【前端 JavaScript 高级】03 - 函数进阶 + 函数

    1. 函数的定义和调用 1.1 函数的定义方式 方式1 使用 function 关键字 (命名函数) 方式2 函...

  • 【JavaScript】技术参考资料

    JS基础、高级、进阶 MDN·JavaScript 函数式编程 阮一峰老师的入门简介: 函数式编程初探、函数式编程...

  • js高级(三)

    JavaScript高级第03天笔记 1.函数的定义和调用 1.1函数的定义方式 方式1 函数声明方式 funct...

  • JS高级函数

    高级函数 在 JavaScript 中使用函数的高级方法。 数据类型的安全检测 构造函数的安全作用域 惰性载入函数...

  • JavaScript

    JavaScript高级函数 惰性函数 函数柯里化 级联函数(链式函数) 设计模式 单例模式 构造函数模式 工厂模...

  • 03JavaScript-函数进阶

    函数的定义方式 方式1 函数声明方式 function 关键字 (命名函数) 方式2 函数表达式(匿名函数) 方式...

  • JavaScript函数进阶

    函数定义 区别 用函数声明方式定义一个函数时 1.函数会被前置2.只有最后一次定义是有效的 用函数实例化方式定义函...

  • JavaScript函数高级

    一、函数的递归(1) 什么是递归函数递归recursion,描述了函数在自己的内部代码中调用自身的过程(2) 递归...

  • 好程序员web前端培训分享js常用函数

    好程序员web前端培训分享js常用函数 1.常规函数 javascript常规函数包括以下9个函数: (1)ale...

  • Javascript 基础

    1、函数防抖和函数节流 【《javascript高级程序设计》里,函数节流是这里讲的函数防抖。】函数防抖: 在事件...

网友评论

    本文标题:【前端 JavaScript 高级】03 - 函数进阶 + 函数

    本文链接:https://www.haomeiwen.com/subject/qwrbnktx.html