1 函数的定义方式

  1. // 函数声明方式 function 关键字 (命名函数)
  2. function fn(){}
  3. // 函数表达式(匿名函数)
  4. var fn = function(){}
  5. // new Function()
  6. var f = new Function('a', 'b', 'console.log(a + b)');
  7. var fn = new Function('参数1','参数2'..., '函数体')

Function 里面参数都必须是字符串格式,函数也属于对象,所有函数都是 Function 的实例(对象)

  1. var a = function (){
  2. console.log('test');
  3. }
  4. console.log(typeof a); // function
  5. console.log(a instanceof Function); // true
  6. console.log(a instanceof Object); // true

2 函数内 this 的指向

这些 this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this 的指向不同
image.png
箭头函数的this指向的是函数定义所在对象

3 改变this指向

3.1 call

**call()** 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

该方法的语法和作用与 [apply()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组

返回值:使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined

应用场景:经常做继承.

  1. var o = {
  2. name: 'andy'
  3. }
  4. function fn(a, b) {
  5. console.log(this);
  6. console.log(a+b)
  7. };
  8. fn(1,2)// 此时的this指向的是window 运行结果为3
  9. fn.call(o,1,2)//此时的this指向的是对象o,参数使用逗号隔开,运行结果为3

image.png
作继承

  1. // 父类
  2. function Person(name, age, sex) {
  3. this.name = name;
  4. this.age = age;
  5. this.sex = sex;
  6. }
  7. // 子类
  8. function Student(name, age, sex, score) {
  9. Person.call(this, name, age, sex); // 此时父类的 this 指向子类的 this,同时调用这个函数
  10. this.score = score;
  11. }
  12. var s1 = new Student('zs', 18, '男', 100);
  13. console.dir(s1);

3.2 apply方法

调用函数的方式
**apply()** 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

  1. // argsArray:传递的值,必须包含在数组里面,返回值就是函数的返回值,因为它就是调用函数
  2. fun.apply(thisArg, [argsArray])
  3. var o = {
  4. name: 'andy'
  5. }
  6. function fn(a, b) {
  7. console.log(this);
  8. console.log(a+b)
  9. };
  10. fn() // 此时的this指向的是window 运行结果为3
  11. fn.apply(o,[1,2])//此时的this指向的是对象o,参数使用数组传递 运行结果为3

应用场景: 经常跟数组有关系,比如使用 Math.max() 求数组的最大值

  1. var arr = [1, 66, 3, 99, 4];
  2. var max = Math.max.apply(Math, arr);
  3. console.log(max); // 99
  1. func.apply(thisArg, [argsArray])

thisArg 必选的。在 _func_ 函数运行时使用的 this 值。

请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

返回值:调用有指定**this**值和参数的函数的结果

3.3 bind

**bind()** 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

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

  1. var o = {
  2. name: 'andy'
  3. };
  4. function fn(a, b) {
  5. console.log(this);
  6. console.log(a + b);
  7. };
  8. var f = fn.bind(o, 1, 2); //此处的f是bind返回的新函数
  9. f();//调用新函数 this指向的是对象o 参数使用逗号隔开

案例

  1. var btns = document.querySelectorAll('button')
  2. btns.forEach((v, i) => {
  3. v.onclick = function () {
  4. this.disabled = true
  5. setTimeout((() => {
  6. this.disabled = false
  7. }).bind(this), 3000)
  8. }
  9. })

3.4 三者异同

  • 共同点 : 都可以改变this指向
  • 不同点:
    • call 和 apply 会调用函数, 并且改变函数内部this指向.
    • call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递
    • bind 不会调用函数, 可以改变函数内部this指向.
  • 应用场景

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

      4 严格模式

      ES5 的严格模式是采用具有限制性 JavaScript变体的一种方式,即在严格的条件下运行 JS 代码。
      严格模式对正常的 JavaScript 语义做了一些更改:
  • 消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。

  • 消除代码运行的一些不安全之处,保证代码运行的安全。
  • 提高编译器效率,增加运行速度。
  • 禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class,enum,export, extends, import, super 不能做变量名

    4.1 开启严格模式

  1. 为脚本开启严格模式

    1. <script>
    2. "use strict";
    3. console.log("这是严格模式。");
    4. </script>
  2. 为函数开启严格模式

    1. function fn(){
    2. "use strict";
    3. return "这是严格模式。";
    4. }

    4.2 严格模式变化

    1.变量规定

  • 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用 var 命令声明,然后再使用。
  • 严禁删除已经声明变量。例如,delete x; 语法是错误的
  1. 严格模式下 this 指向问题
    ① 以前在全局作用域函数中的 this 指向 window 对象。
    ② 严格模式下全局作用域中函数中的 this 是 undefined。
    ③ 以前构造函数时不加 new也可以调用, 当普通函数,this 指向全局对象
    ④ 严格模式下,如果构造函数不加new调用, this 指向的是undefined 如果给他赋值则会报错
    ⑤ new 实例化的构造函数指向创建的对象实例。
    ⑥ 定时器 this 还是指向 window 。
    ⑦ 事件、对象还是指向调用者。

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

5 闭包

闭包(closure)指有权访问另一个函数作用域中变量的函数。 ——- JavaScript 高级程序设计
简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。
作用:延伸变量的作用范围。

  1. function fn1(){ // fn1 就是闭包函数
  2. var num = 10;
  3. function fn2(){
  4. console.log(num); // 10
  5. }
  6. fn2()
  7. }
  8. fn1();

image.png
当执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。

计算打车价格:

  1. /*需求分析
  2. 打车起步价13(3公里内), 之后每多一公里增加 5块钱. 用户输入公里数就可以计算打车价格
  3. 如果有拥堵情况,总价格多收取10块钱拥堵费*/
  4. var car = (function() {
  5. var start = 13; // 起步价 局部变量
  6. var total = 0; // 总价 局部变量
  7. return {
  8. // 正常的总价
  9. price: function(n) {
  10. if (n <= 3) {
  11. total = start;
  12. } else {
  13. total = start + (n - 3) * 5
  14. }
  15. return total;
  16. },
  17. // 拥堵之后的费用
  18. yd: function(flag) {
  19. return flag ? total + 10 : total;
  20. }
  21. }
  22. })();
  23. console.log(car.price(5)); // 23
  24. console.log(car.yd(true)); // 33

作用域中的局部变量一直被使用着

  1. function getNum(){
  2. var n = 0
  3. function add() {
  4. return n++
  5. }
  6. return add
  7. }
  8. var c = getNum()
  9. console.log(c()); // 0
  10. console.log(c()); // 1
  11. console.log(c()); // 2

6 递归

函数内部自己调用自己

6.1 阶乘

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

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

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

6.3 遍历对象

  1. var data = [{
  2. id: 1,
  3. name: '家电',
  4. goods: [{
  5. id: 11,
  6. gname: '冰箱',
  7. goods: [{
  8. id: 111,
  9. gname: '海尔'
  10. }, {
  11. id: 112,
  12. gname: '美的'
  13. },]
  14. }, {
  15. id: 12,
  16. gname: '洗衣机'
  17. }]
  18. }, {
  19. id: 2,
  20. name: '服饰'
  21. }];
  22. // 我们想要做输入id号,就可以返回的数据对象
  23. function getID(json, id) {
  24. var o = {};
  25. json.forEach(function (item) {
  26. if (item.id == id) {
  27. o = item;
  28. // 2. 我们想要得里层的数据 11 12 可以利用递归函数
  29. // 里面应该有goods这个数组并且数组的长度不为 0
  30. } else if (item.goods && item.goods.length > 0) {
  31. o = getID(item.goods, id);
  32. }
  33. });
  34. return o;
  35. }
  36. console.log(getID(data, 1));

6.4 自定义深拷贝

深拷贝js库没有提供相应的方法

  1. // 深拷贝拷贝多层, 每一级别的数据都会拷贝.
  2. var obj = {
  3. id: 1,
  4. name: 'andy',
  5. msg: {
  6. age: 18
  7. },
  8. color: ['pink', 'red']
  9. };
  10. var o = {};
  11. // 封装函数
  12. function deepCopy(newobj, oldobj) {
  13. for (var k in oldobj) {
  14. // 判断我们的属性值属于那种数据类型
  15. // 1. 获取属性值 oldobj[k]
  16. var item = oldobj[k];
  17. // 2. 判断这个值是否是数组
  18. if (item instanceof Array) {
  19. newobj[k] = [];
  20. deepCopy(newobj[k], item)
  21. } else if (item instanceof Object) {
  22. // 3. 判断这个值是否是对象
  23. newobj[k] = {};
  24. deepCopy(newobj[k], item)
  25. } else {
  26. // 4. 属于简单数据类型
  27. newobj[k] = item;
  28. }
  29. }
  30. }
  31. deepCopy(o, obj);
  32. console.log(o);

还有一种便是先转换成json字符串

  1. // 深拷贝拷贝多层, 每一级别的数据都会拷贝.
  2. var obj = {
  3. id: 1,
  4. name: 'andy',
  5. msg: {
  6. age: 18
  7. },
  8. color: ['pink', 'red']
  9. };
  10. // 先转换成字符串
  11. var temp = JSON.stringify(obj)
  12. var o = JSON.parse(temp)