【前端系列教程之JavaScript】16_JavaScript函数进阶

朴灿烈づ我的快乐病毒、 2024-04-28 13:12 188阅读 0赞

函数进阶

函数回顾

函数的定义方式

  • 函数声明

    function foo () {

    }

  • 函数表达式

    var foo = function () {

    }

函数声明与函数表达式的区别

  • 函数声明必须有名字
  • 函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用
  • 函数表达式类似于变量赋值
  • 函数表达式可以没有名字,例如匿名函数
  • 函数表达式没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用

函数的调用方式

  • 普通函数

    function foo () {

    }

    foo()

  • 构造函数

    function Person (name,age) {

    1. thia.name = name;
    2. this.age = age
    3. ...

    }

    new Person();

  • 对象方法

    var obj = {

    1. name:"zhangsan",
    2. age:12,
    3. eat:function(){
    4. ..
    5. }

    }

    obj.eat();

函数内 this 指向的不同场景

函数的调用方式决定了 this 指向的不同:



































调用方式 非严格模式 备注
普通函数调用 window 严格模式下是 undefined
构造函数调用 实例对象 原型方法中 this 也是实例对象
对象方法调用 该方法所属对象 紧挨着的对象
事件绑定方法 绑定事件对象
定时器函数 window

这就是对函数内部 this 指向的基本整理,写代码写多了自然而然就熟悉了。

  1. <ul id="ul1">
  2. <li>我是第1个li</li>
  3. <li>我是第2个li</li>
  4. <li>我是第3个li</li>
  5. <li>我是第4个li</li>
  6. <li>我是第5个li</li>
  7. </ul>
  8. <script>
  9. // 普通函数内部this指向
  10. // 这是全局函数,挂载在window上
  11. var a = 100;
  12. function fn1() {
  13. // 所以在这个函数内部使用this,指向的就是window
  14. console.log(this, this.a);
  15. }
  16. fn1();
  17. console.log("-----------------------------");
  18. // 构造函数调用,构造函数里面的this,是实例对象
  19. // 当我们使用new来调用构造函数的时候,会隐式创建一个对象,然后把这个对象给this,最后把this反馈出去
  20. function Person(name, age) {
  21. this.name = name;
  22. this.age = age;
  23. this.sayHello = function () {
  24. console.log(this.name + "说:Hello");
  25. }
  26. }
  27. var p1 = new Person("张三", 18);
  28. p1.sayHello();
  29. console.log("-----------------------------");
  30. // 对象方法调用,this就是该方法所属对象
  31. var obj = {
  32. name: "李四",
  33. // this代表的是window
  34. age: a,
  35. // 对象方法里面的this,代表的就是方法所属对象
  36. sayHello: function () {
  37. console.log(this.name + "说:Hello");
  38. }
  39. }
  40. obj.sayHello();
  41. console.log(obj.age);
  42. console.log("-----------------------------");
  43. // 事件绑定方法
  44. // document.onclick = function () {
  45. // // 事件函数里面的this,代表事件的当前对象
  46. // console.log(this)
  47. // }
  48. // 定时器函数
  49. setTimeout(function () {
  50. console.log(this);
  51. }, 1000)
  52. // 获取li的下标
  53. var lis = document.querySelectorAll("#ul1>li");
  54. for (var i = 0; i < lis.length; i++) {
  55. var li = lis[i];
  56. li.index = i;
  57. //在鼠标函数里面,this是点击的li
  58. li.onclick = function () {
  59. // 通过变量来保存this
  60. var _this = this;
  61. // 1s之后,输出li的下标
  62. setTimeout(function () {
  63. // 因为在定时器里面,this是window
  64. console.log(_this.index);
  65. }, 1000)
  66. }
  67. }
  68. </script>

call、apply、bind

例子1:

  1. // window下面定义的两个全局变量
  2. var name = "小王",
  3. age = 17;
  4. var obj = {
  5. name: "小张",
  6. // 这里的this是window
  7. objAge: this.age,
  8. myFun: function () {
  9. // 对象函数里面的this就是函数所在的对象,obj
  10. console.log(this.name + "年龄" + this.age);
  11. }
  12. }
  13. console.log(obj.objAge); // 17
  14. obj.myFun(); // 小张年龄 undefined

例子2:

  1. var fav = "小张";
  2. function shows() {
  3. console.log(this.fav)
  4. }
  5. shows(); // 小张

那了解了函数 this 指向的不同场景之后,我们知道有些情况下我们为了使用某种特定环境的 this 引用, 这时候时候我们就需要采用一些特殊手段来处理了,例如我们经常在定时器外部备份 this 引用,然后在定时器函数内部使用外部 this 的引用。

然而实际上对于这种做法我们的 JavaScript 为我们专门提供了一些函数方法用来帮我们更优雅的处理函数内部 this 指向问题。

这就是接下来我们要学习的 call、apply、bind 三个函数方法。

语法

call讲解:

call() 方法调用一个函数, 其具有一个指定的 this 值和分别地提供的参数(参数的列表)。

注意:该方法的作用和 apply() 方法类似,只有一个区别,就是 call() 方法接受的是若干个参数的列表,而 apply() 方法接受的是一个包含多个参数的数组。

语法:

  1. fun.call(thisArg[, arg1[, arg2[, ...]]])

参数:

  • thisArg

    • 在 fun 函数运行时指定的 this 值
    • 如果指定了 null 或者 undefined 则内部 this 指向 window
  • arg1, arg2, ...

    • 指定的参数列表

apply

apply() 方法调用一个函数, 其具有一个指定的 this 值,以及作为一个数组(或类似数组的对象)提供的参数。

注意:该方法的作用和 `call()` 方法类似,只有一个区别,就是 `call()` 方法接受的是若干个参数的列表,而 `apply()` 方法接受的是一个包含多个参数的数组。

语法:

  1. fun.apply(thisArg, [argsArray])

参数:

  • thisArg
  • argsArray

apply()call() 非常相似,不同之处在于提供参数的方式。 apply() 使用参数数组而不是一组参数列表。例如:

  1. fun.apply(this, ['eat', 'bananas'])

bind

bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。 当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。 一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

语法:

  1. fun.bind(thisArg[, arg1[, arg2[, ...]]])

参数:

  • thisArg

    • 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。
  • arg1, arg2, …

    • 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

返回值:

返回由指定的this值和初始化参数改造的原函数拷贝。

测试

1、call()、apply()、bind() 都是用来重定义 this 这个对象的!

  1. var name = "小王",
  2. age = 17;
  3. var obj = {
  4. name: "小张",
  5. objAge: this.age,
  6. myFun: function () {
  7. console.log(this.name + "年龄" + this.age);
  8. }
  9. }
  10. var db = {
  11. name: "德邦总管",
  12. age: 99
  13. }
  14. obj.myFun.call(db); // 德邦总管年龄 99
  15. obj.myFun.apply(db); // 德邦总管年龄 99
  16. obj.myFun.bind(db)(); // 德邦总管年龄 99

以上出了 bind 方法后面多了个 () 外 ,结果返回都一致!

由此得出结论,bind 返回的是一个新的函数,你必须调用它才会被执行。

2、对比call 、bind 、 apply 传参情况下

  1. var name = "小王",
  2. age = 17;
  3. var obj = {
  4. name: "小张",
  5. objAge: this.age,
  6. myFun: function (fm, to) {
  7. console.log(this.name + " 年龄 " + this.age, " 来自 " + fm + " 去往 " + to);
  8. }
  9. }
  10. var db = {
  11. name: "德邦总管",
  12. age: 99
  13. }
  14. obj.myFun.call(db, '成都', '上海'); // 德邦总管 年龄 99 来自 成都去往上海
  15. obj.myFun.apply(db, ['成都', '上海']); // 德邦总管 年龄 99 来自 成都去往上海
  16. obj.myFun.bind(db, '成都', '上海')(); // 德邦总管 年龄 99 来自 成都去往上海
  17. obj.myFun.bind(db, ['成都', '上海'])(); // 德邦总管 年龄 99 来自 成都, 上海去往 undefined

应用

call的应用

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Document</title>
  7. </head>
  8. <body>
  9. <span>1</span>
  10. <span>2</span>
  11. <span>3</span>
  12. <script>
  13. // 获取到页面的所有span,得到HTMLCollection,无论是HTMLCollection还是NodeList,我们都称之为伪数组
  14. // 伪数组本质是一个对象,只是模拟的数组的特性
  15. // 用下标作为对象的key,并且也提供的length
  16. var spans = document.getElementsByTagName("span");
  17. console.log(spans)
  18. // spans.shift(); // spans.shift is not a function
  19. var arr = ["zhangsan", "lis", "wangwu"]
  20. console.log(arr)
  21. // getElementsByTagName()
  22. // 伪数组
  23. var obj = {
  24. 0: 100,
  25. 1: 10,
  26. 2: 11,
  27. 3: 20,
  28. length: 4
  29. };
  30. console.log("伪数组下标2的值:" + obj['2']);
  31. // 向伪数组添加一个值
  32. // obj.push(30); // obj.push is not a function
  33. // obj['4'] = 30;
  34. // obj.length++;
  35. // console.log(obj);
  36. // push(参数) 向当前数组的末尾添加一个参数
  37. // push() this[最后下标+1] = 值; length++;
  38. Array.prototype.push.call(obj, 330); // this[最后下标+1] = 值;
  39. // Array.prototype.splice.call(obj, 0, 3);
  40. console.log(obj);
  41. var obj = {
  42. name: 'zs'
  43. };
  44. console.log(obj.toString()); // [object object]
  45. var arr = [5, 9];
  46. console.log(arr.toString()); // 5,9
  47. console.log(Object.prototype.toString.call(arr));
  48. </script>
  49. </body>
  50. </html>

apply应用

  1. // fn.apply(,[])
  2. // Math.max(3, 5, 6);
  3. var arr = [5, 10, 1, 3, 6];
  4. // Math.max不能求数组中的最大值
  5. // console.log(Math.max(arr));
  6. console.log(Math.max.apply(null, arr));
  7. console.log(Math.max.apply(Math, arr));
  8. // console.log(1, 2, 3);
  9. // console.log(arr);

bind应用

  1. // 3 bind的应用
  2. function Bloomer() {
  3. this.petalCount = Math.ceil(Math.random() * 12) + 1;
  4. }
  5. // 1秒后调用declare函数
  6. Bloomer.prototype.bloom = function () {
  7. window.setTimeout(this.declare.bind(this), 1000);
  8. };
  9. Bloomer.prototype.declare = function () {
  10. console.log('我有 ' + this.petalCount + ' 朵花瓣!');
  11. };
  12. var test = new Bloomer();
  13. test.bloom();
  14. <ul id="ul1">
  15. <li>我是第1li</li>
  16. <li>我是第2li</li>
  17. <li>我是第3li</li>
  18. <li>我是第4li</li>
  19. <li>我是第5li</li>
  20. </ul>
  21. <script>
  22. // 获取li的下标
  23. var lis = document.querySelectorAll("#ul1>li");
  24. for (var i = 0; i < lis.length; i++) {
  25. var li = lis[i];
  26. li.index = i;
  27. //在鼠标函数里面,this是点击的li
  28. li.onclick = function () {
  29. // // 通过变量来保存this
  30. // var _this = this;
  31. // 1s之后,输出li的下标
  32. setTimeout(function () {
  33. // 因为在定时器里面,this是window
  34. console.log(this.index, this.innerHTML);
  35. }.bind(this), 1000)
  36. }
  37. }
  38. </script>

小结

  • call 和 apply 特性一样

    • 都是用来调用函数,而且是立即调用
    • 但是可以在调用函数的同时,通过第一个参数指定函数内部 this 的指向
    • call 调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分隔的方式依次传递即可
    • apply 调用的时候,参数必须是一个数组,然后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递
    • 如果第一个参数指定了 null 或者 undefined 则内部 this 指向 window
  • bind

    • 可以用来指定内部 this 的指向,然后生成一个改变了 this 指向的新的函数
    • 它和 call、apply 最大的区别是:bind 不会调用
    • bind 支持传递参数,它的传参方式比较特殊,一共有两个位置可以传递

      • 在 bind 的同时,以参数列表的形式进行传递
      • 在调用的时候,以参数列表的形式进行传递
      • 那到底以谁 bind 的时候传递的参数为准呢还是以调用的时候传递的参数为准
      • 两者合并:bind 的时候传递的参数和调用的时候传递的参数会合并到一起,传递到函数内部

函数的其它成员

  • arguments

    • 实参集合
  • caller

    • 函数的调用者
  • length

    • 形参的个数
  • name

    • 函数的名称

    function fn(x, y, z) {
    console.log(fn.length) // => 形参的个数
    console.log(arguments) // 伪数组实参参数集合
    console.log(arguments.callee === fn) // 函数本身
    console.log(fn.caller) // 函数的调用者
    console.log(fn.name) // => 函数的名字
    }

    function f() {
    fn(10, 20, 30)
    }

    f()

高阶函数

  • 函数可以作为参数
  • 函数可以作为返回值

作为参数

  1. function eat (callback) {
  2. setTimeout(function () {
  3. console.log('吃完了')
  4. callback()
  5. }, 1000)
  6. }
  7. eat(function () {
  8. console.log('去唱歌')
  9. })

作为返回值

  1. function genFun (type) {
  2. return function (obj) {
  3. return Object.prototype.toString.call(obj) === type
  4. }
  5. }
  6. var isArray = genFun('[object Array]')
  7. var isObject = genFun('[object Object]')
  8. console.log(isArray([])) // => true
  9. console.log(isArray({})) // => true

发表评论

表情:
评论列表 (有 0 条评论,188人围观)

还没有评论,来说两句吧...

相关阅读

    相关 前端开发JavascriptNodejs

    曾经有下面的同事问我,“精通了前端的js,是不是入手nodejs会很快就能入手干活了”。 由此我梳理了一下Node.js、ES、JS之间的区别,前端开发人员转Nodejs需