函数的定义方式

  • 函数声明

  • 函数表达式

  • new Function

函数声明

 function foo () {
 }

函数表达式

 var foo = function () {
 ​
 }

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

  • 函数声明必须有名字

  • 函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用

  • 函数表达式类似于变量赋值

  • 函数表达式可以没有名字,例如匿名函数

  • 函数表达式没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用

下面是一个根据条件定义函数的例子:

 if (true) {
   function f () {
     console.log(1)
  }
 } else {
   function f () {
     console.log(2)
  }
 }

以上代码执行结果在不同浏览器中结果不一致。

不过我们可以使用函数表达式解决上面的问题:

 var f
 ​
 if (true) {
   f = function () {
     console.log(1)
  }
 } else {
   f = function () {
     console.log(2)
  }
 }

函数的调用方式

  • 普通函数

  • 构造函数

  • 对象方法

函数内 this 指向的不同场景

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

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

函数也是对象

  • 所有函数都是 Function 的实例

call、apply、bind

那了解了函数 this 指向的不同场景之后,我们知道有些情况下我们为了使用某种特定环境的 this 引用, 这时候时候我们就需要采用一些特殊手段来处理了,例如我们经常在定时器外部备份 this 引用,然后在定时器函数内部使用外部 this 的引用。 然而实际上对于这种做法我们的 JavaScript 为我们专门提供了一些函数方法用来帮我们更优雅的处理函数内部 this 指向问题。 这就是接下来我们要学习的 call、apply、bind 三个函数方法。

call

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

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

语法:

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

参数:

  • thisArg

    • 在 fun 函数运行时指定的 this 值

    • 如果指定了 null 或者 undefined 则内部 this 指向 window

  • arg1, arg2, ...

    • 指定的参数列表

apply

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

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

语法:

 fun.apply(thisArg, [argsArray])

参数:

  • thisArg

  • argsArray

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

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

bind

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

语法:

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

参数:

  • thisArg

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

  • arg1, arg2, ...

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

返回值:

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

示例1:

 this.x = 9; 
 var module = {
   x: 81,
   getX: function() { return this.x; }
 };
 ​
 module.getX(); // 返回 81
 ​
 var retrieveX = module.getX;
 retrieveX(); // 返回 9, 在这种情况下,"this"指向全局作用域
 ​
 // 创建一个新函数,将"this"绑定到module对象
 // 新手可能会被全局的x变量和module里的属性x所迷惑
 var boundGetX = retrieveX.bind(module);
 boundGetX(); // 返回 81

示例2:

 function LateBloomer() {
   this.petalCount = Math.ceil(Math.random() * 12) + 1;
 }
 ​
 // Declare bloom after a delay of 1 second
 LateBloomer.prototype.bloom = function() {
   window.setTimeout(this.declare.bind(this), 1000);
 };
 ​
 LateBloomer.prototype.declare = function() {
   console.log('I am a beautiful flower with ' +
     this.petalCount + ' petals!');
 };
 ​
 var flower = new LateBloomer();
 flower.bloom();  // 一秒钟后, 调用'declare'方法

小结

  • call 和 apply 特性一样

    • 都是用来调用函数,而且是立即调用

    • 但是可以在调用函数的同时,通过第一个参数指定函数内部 this 的指向

    • call 调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分隔的方式依次传递即可

    • apply 调用的时候,参数必须是一个数组,然后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递

    • 如果第一个参数指定了 null 或者 undefined 则内部 this 指向 window

  • bind

    • 可以用来指定内部 this 的指向,然后生成一个改变了 this 指向的新的函数

    • 它和 call、apply 最大的区别是:bind 不会调用

    • bind 支持传递参数,它的传参方式比较特殊,一共有两个位置可以传递

        1. 在 bind 的同时,以参数列表的形式进行传递

        1. 在调用的时候,以参数列表的形式进行传递

      • 那到底以谁 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()

高阶函数

  • 函数可以作为参数

  • 函数可以作为返回值

作为参数

 function eat (callback) {
   setTimeout(function () {
     console.log('吃完了')
     callback()
  }, 1000)
 }
 ​
 eat(function () {
   console.log('去唱歌')
 })

作为返回值

 function genFun (type) {
   return function (obj) {
     return Object.prototype.toString.call(obj) === type
  }
 }
 ​
 var isArray = genFun('[object Array]')
 var isObject = genFun('[object Object]')
 ​
 console.log(isArray([])) // => true
 console.log(isArray({})) // => true

函数闭包

 function fn () {
   var count = 0
   return {
     getCount: function () {
       console.log(count)
    },
     setCount: function () {
       count++
    }
  }
 }
 ​
 var fns = fn()
 ​
 fns.getCount() // => 0
 fns.setCount()
 fns.getCount() // => 1

作用域、作用域链、预解析

  • 全局作用域

  • 函数作用域

  • 没有块级作用域

 {
   var foo = 'bar'
 }
 ​
 console.log(foo)
 ​
 if (true) {
   var a = 123
 }
 console.log(a)

作用域链示例代码:

 var a = 10
 ​
 function fn () {
   var b = 20
 ​
   function fn1 () {
     var c = 30
     console.log(a + b + c)
  }
 ​
   function fn2 () {
     var d = 40
     console.log(c + d)
  }
 ​
   fn1()
   fn2()
 }
  • 内层作用域可以访问外层作用域,反之不行

什么是闭包

闭包就是能够读取其他函数内部变量的函数, 由于在 Javascript 语言中,只有函数内部的子函数才能读取局部变量, 因此可以把闭包简单理解成 “定义在一个函数内部的函数”。 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的用途:

  • 可以在函数外部读取函数内部成员

  • 让函数内成员始终存活在内存中

一些关于闭包的例子

示例1:

 var arr = [10, 20, 30]
 for(var i = 0; i < arr.length; i++) {
   arr[i] = function () {
     console.log(i)
  }
 }

示例2:

 console.log(111)
 ​
 for(var i = 0; i < 3; i++) {
   setTimeout(function () {
     console.log(i)
  }, 0)
 }
 console.log(222)

示例3:投票

示例4:判断类型

示例5:沙箱模式

闭包的思考题

思考题 1:

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

思考题 2:

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

小结

函数递归

递归执行模型

 function fn1 () {
   console.log(111)
   fn2()
   console.log('fn1')
 }
 ​
 function fn2 () {
   console.log(222)
   fn3()
   console.log('fn2')
 }
 ​
 function fn3 () {
   console.log(333)
   fn4()
   console.log('fn3')
 }
 ​
 function fn4 () {
   console.log(444)
   console.log('fn4')
 }
 ​
 fn1()

举个栗子:计算阶乘的递归函数

 function factorial (num) {
   if (num <= 1) {
     return 1
  } else {
     return num * factorial(num - 1)
  }
 }

递归应用场景

  • 深拷贝

  • 菜单树

  • 遍历 DOM 树

JS函数进阶的更多相关文章

  1. js基础进阶--关于setTimeout的思考

    欢迎访问我的个人博客:http://www.xiaolongwu.cn 先热身 看看下面的额代码会打印出什么? for (var i = 0; i < 5; i++) { setTimeout( ...

  2. 【原创】分布式之数据库和缓存双写一致性方案解析(三) 前端面试送命题(二)-callback,promise,generator,async-await JS的进阶技巧 前端面试送命题(一)-JS三座大山 Nodejs的运行原理-科普篇 优化设计提高sql类数据库的性能 简单理解token机制

    [原创]分布式之数据库和缓存双写一致性方案解析(三)   正文 博主本来觉得,<分布式之数据库和缓存双写一致性方案解析>,一文已经十分清晰.然而这一两天,有人在微信上私聊我,觉得应该要采用 ...

  3. JavaScript的函数进阶

    函数进阶 1立即执行函数表达式 立即执行的函数表达式的英文全称为Immediately Invoked Function Expression,简称就为IIFE.这是一个如它名字所示的那样,在定义后就 ...

  4. 3.3 js函数

    1.函数语法: 函数声明的方式:function 函数名(参数1,参数2-){//函数体;}函数调用:函数名(参数1,参数2-); 函数内不一定都指定返回值. 如果需要指定返回值,可用 return ...

  5. Js函数function基础理解

    正文:我们知道,在js中,函数实际上是一个对象,每个函数都是Function类型的实例,并且都与其他引用类型一样具有属性和方法.因此,函数名实际上是指向函数对象的指针,不与某个函数绑定.在常见的两种定 ...

  6. js函数表达式和函数声明的区别

    我们已经知道,在任意代码片段外部添加包装函数,可以将内部的变量和函数定义"隐 藏"起来,外部作用域无法访问包装函数内部的任何内容. 例如: var a = 2; function ...

  7. 通用js函数集锦<来源于网络> 【二】

    通用js函数集锦<来源于网络> [二] 1.数组方法集2.cookie方法集3.url方法集4.正则表达式方法集5.字符串方法集6.加密方法集7.日期方法集8.浏览器检测方法集9.json ...

  8. 通用js函数集锦<来源于网络/自己> 【一】

    通用js函数集锦<来源于网络/自己>[一] 1.返回一个全地址2.cookie3.验证用户浏览器是否是微信浏览器4.验证用户浏览器是否是微博内置浏览器5.query string6.验证用 ...

  9. 100多个基础常用JS函数和语法集合大全

    网站特效离不开脚本,javascript是最常用的脚本语言,我们归纳一下常用的基础函数和语法: 1.输出语句:document.write(""); 2.JS中的注释为//3.传统 ...

随机推荐

  1. DO_DEVICE_INITIALIZING

    这个东西的位置在DEVICE_OBJECT的Flags字段中, 本来这个Flags大多的情况下都是在设置IO方式,如DO_BUFFERED_IO, 但特殊的位也可能需要在这里设置. 用处是防止当自己的 ...

  2. 微信小程序开发简易计算器改进版

    微信小程序开发计算器有多种方法,但是大部分代码比较复杂.不容易理解.本案例进行了改进,主要是组件bindtap属性绑定的自定义函数clickBtn(),采用了switch语句,使得代码结构更加清晰,学 ...

  3. axios的介绍及使用

    特点:支持promise API . 拦截请求和响应.转换请求数据和响应数据.取消请求.自动转换JSON数据.客户端支持防御XSRF等: axios请求方法(需后端定义):get获取数据. post提 ...

  4. vue keep-alive缓存问题

    搬运自:https://blog.csdn.net/dongguan_123/article/details/80910231 我的问题:列表页  > 详情页a  > 支付页  >  ...

  5. 代码托管平台--GitHub 使用小结

    一.GitHub介绍 GitHub是一个面向开源及私有软件项目的托管平台,因为只支持git作为唯一的版本库格式进行托管.在GitHub,用户可以十分轻易地找到海量的开源代码. 很多IT行业的人到求职的 ...

  6. 关于apache 重定向设定

    本人在研究关于apache重定向的资料,在网上找了很多,但是就本人来说,方便理解的,找到了这么一个,记录了下来,原帖地址:http://www.exehack.net/8.html 关于apache配 ...

  7. (PASS)什么是原子性和原子性操作?

    什么是原子性操作呢? 下面我举一个例子来说明一下: A想要从自己的帐户中转1000块钱到B的帐户里.那么从A开始转帐,到转帐结束的这一个过程,称之为一个事务.在这个事务里,要做如下操作: 1. 从A的 ...

  8. 微信小程序学习之navigate(1)navigateTo方法与navigateBack方法对于page生命周期不同的触发影响

    小程序的每个页面都有一些生命周期,每个生命周期由分别有着不同的生命周期钩子函数.而我们的业务逻辑写在这些生命周期的钩子函数中,那么弄清楚那种情形下会触发那些生命周期钩子函数就非常重要了 先上一段代码 ...

  9. leetcode-回溯③

    题77 回溯: class Solution: def combine(self, n: int, k: int) -> List[List[int]]: res = [] def backtr ...

  10. Laravel 开发环境搭建

    本人使用的是Laravel5.5版本,需要PHP7支持,所以安装的环境是Apache2.php7.0.mysql5.7,系统为ubuntu14.04LTS(14以下的版本对php7支持不够),主要参考 ...