重学 JavaScript[2] 函数
语法
JavaScript 中函数大致有三种写法
普通的函数写法:
function fn(a, b) { return a + b }
声明一个匿名函数并赋值给 fn:
let fn = function(a, b) { return a + b }
箭头函数:
let fn = (a, b) => { return a + b } // 或 let fn = (a, b) => a + b
另外,你可以直接写出“立即执行的函数”
;(function fn() { console.log('fn') })()
请注意:**箭头函数与“function”写法的函数不能完全等价!**其原因会在下一篇解释
高阶函数
高阶函数(HoC)可以接受函数为参数,在很多框架中都有类似这样的使用:
doSomeThingAsync(params, function(result) { console.log(result) })
一些操作数组的函数也有类似的调用方法,比如:
;[1, 2, 3].forEach(function(e) { console.log(e) })
闭包
现象
高阶函数还可以接受函数作为返回值,比如:
function fn() { let count = 0 return function() { count++ console.log(count) } }
你可以这样调用它:
let counter = fn() counter() counter() counter() //依次输出1 2 3
结果好像令人费解,fn()调用后返回了一个函数,并赋值给 counter
counter 函数对 fn 内部的变量count
进行了自增并打印,但每次执行 counter()后,打印出来的结果,就好像变量count
一直“维持”在 fn 函数内部
如果在代码后再追加一行,变成这样:
function fn() { let count = 0 return function() { count++ console.log(count) } } let counter = fn() counter() counter() counter() console.log(count) // 依次输出1 2 3后,ReferenceError报错
这个结果说明变量count
并不在全局作用域内,而是在 fn()形成的闭包内
使用闭包
闭包使得函数在执行后有了“自己的”作用域,且会将一些变量和值一直保存在内存中
在 Java 和 C++等语言里,函数执行过后,内部的变量就会被销毁,在这里,JavaScript 显得很“违反直觉”
但请不要认为 JavaScript 函数只是简单的“函数”
避免污染作用域
我们可以使用上面提到的“立即执行函数”来避免一些变量污染作用域,但又不影响去使用这个变量
这段代码避免了直接声明 num 带来的“污染”,并通过 add 和 getNum 函数对 num 变量进行保护
;(function() { this.num = 0 this.add = function() { this.num++ } this.getNum = function() { return this.num } })() add() console.log(getNum()) // 1
缓存
对于需要反复调用的函数,可以使用闭包来存放缓存,以提升性能
考虑以下计算斐波那契数列的代码:
function fib(n) { if (n < 2) { return n } else { return fib(n - 1) + fib(n - 2) } } // 有很多值被重复计算
使用闭包存放缓存:
let fib = (function() { let cache = [0, 1] return function(n) { let result = cache[n] if (!result) { result = fib(n - 1) + fib(n - 2) cache[n] = result } return result } })()
类
在 Java 类语言里,如果要持久的保存变量,应该写到类的静态或非静态变量里,然而 ES5 时代,JavaScript 并没有类的概念,甚至于 ES6 引入的class
关键字,也是用函数来实现的“类”
你可以在
https://www.babeljs.cn/
来尝试 ES6 中的类编译为 ES5 后的代码ES6:
class Person { constructor() { this.name = '王昭君' this.age = 21 } introduce() { console.log(this.name, this.age) } }
ES5:
'use strict' function _instanceof(left, right) { if ( right != null && typeof Symbol !== 'undefined' && right[Symbol.hasInstance] ) { return !!right[Symbol.hasInstance](left) } else { return left instanceof right } } function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError('Cannot call a class as a function') } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i] descriptor.enumerable = descriptor.enumerable || false descriptor.configurable = true if ('value' in descriptor) descriptor.writable = true Object.defineProperty(target, descriptor.key, descriptor) } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps) if (staticProps) _defineProperties(Constructor, staticProps) return Constructor } var Person = /*#__PURE__*/ (function() { function Person() { _classCallCheck(this, Person) this.name = '王昭君' this.age = 21 } _createClass(Person, [ { key: 'introduce', value: function introduce() { console.log(this.name, this.age) }, }, ]) return Person })()
当然,在 ES6 之前,JavaScript 程序员会使用一种“构造器函数”来充当类的作用,比如:
function Person() { this.name = '王昭君' this.age = 21 } const person = new Person() console.log(person.name, person.age) // 王昭君 21
这类函数通常遵循 Java 中类的命名方式(首字母大写),在调用时需要使用new
关键字(当然如果你不用new
会得到 undefined)
通常情况下,如果不是一定要写 ES5 语法,个人建议不要使用这样的方法(直接用 class 他不香么?还不容易出错)
函数的函数
如果你声明了一个函数,你会发现 IDE 有自动提示的函数的函数:
我们主要关注这几个函数:
- call()
- apply()
- bind()
考虑以下代码:
this.name = 'Global' let obj = { name: '王昭君', getName() { console.log(this.name) }, } function getName() { console.log(this.name) } getName() obj.getName() // Global // 王昭君
对象内部的函数,其this
指向对象本身(有关 this 指向问题,会在下一篇具体描述)
而使用call
,apply
,bind
可以改变函数中 this 的指向,比如:
this.name = 'Global' let obj = { name: '王昭君', } function getName() { console.log(this.name) } getName() getName.call(obj)
三者的区别
call
的定义:
(method) Function.call(this: Function, thisArg: any, ...argArray: any[]): any Calls a method of an object, substituting another object for the current object. @param thisArg — The object to be used as the current object. @param argArray — A list of arguments to be passed to the method.
apply
的定义:
(method) Function.apply(this: Function, thisArg: any, argArray?: any): any Calls the function, substituting the specified object for the this value of the function, and the specified array for the arguments of the function. @param thisArg — The object to be used as the this object. @param argArray — A set of arguments to be passed to the function.
bind
的定义:
(method) Function.bind(this: Function, thisArg: any, ...argArray: any[]): any For a given function, creates a bound function that has the same body as the original function. The this object of the bound function is associated with the specified object, and has the specified initial parameters. @param thisArg — An object to which the this keyword can refer inside the new function. @param argArray — A list of arguments to be passed to the new function.
可以看出,call
和apply
只有传参方式的区别
function fn() {} fn.call(this, arg1, arg2, ...) fn.apply(this, [arg1, arg2, ...])
而bind
会返回一个“绑定”了传入的 this 的函数
function fn() {} let fn2 = fn.bind(this, arg1, arg2, ...) fn2()
-
JavaScript
-
重学JavaScript
好
考古