重学 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
关键字,也是用函数来实现的“类”
你可以在Babel 官网来尝试 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
好
考古