目录
- 函数的属性
- arguments
- 将arguments转换成数组
- rest
- 纯函数
- 柯里化函数
- 自动实现函数柯里化
- 组合函数
- 自动实现组合化
- with与eval
- with
- eval
- 严格模式
- 严格模式的限制
函数的属性
函数其实也是一个对象
是对象就会有对应的方法与属性
以下是几个常用属性
- name
name属性主要用于访问给定函数的名字,在匿名函数中这个值为空 - length
length属性中存放的则是函数参数的个数
注意,如果是rest参数的话则不会计入 - prototype
这个属性指向了函数的原型对象,当函数作为构造函数生成新对象时新对象会继承原型对象中的属性
关于原型与继承可以看我这篇文章
(未动笔,未来可寄)
arguments
arguments
是一个对应于传递给函数的参数的类数组对象
即你调用函数时传入的参数
都存放在arguments
里
arguments
是一个类数组(like-Array)对象
它有着length属性
和能通过索引访问
元素
但没有数组对应的方法
将arguments转换成数组
将arguments
转换成数组
有以下三种办法
- 遍历整个
arguments
,将其中的元素一个一个push
到新数组中
这种方法最简单,同样的代码也很多function foo() { var newArguments = []; for (var i = 0; i < arguments.length; i++) { newArguments.push(arguments[i]) } console.log(newArguments) } foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
- 使用
ES6
提供的新语法
这种方法使用function foo() { var newArguments = Array.from(arguments); console.log(newArguments) } foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
ES6
提供的from
方法实现
也可以通过function foo() { var newArguments = [...arguments]; console.log(newArguments) } foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
展开运算符
来实现 - 使用
slice
方法加显式绑定this
实现
如果需要兼容function foo() { var newArguments = [].slice.apply(arguments); console.log(newArguments) } foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
ES5
以下环境的话可以使用这种方法或第一种方法
注意,不能直接Array.slice
,因为slice
是实例方法
可以通过Array.prototype.slice
实现
注意,arguments
只存在于非箭头函数中,在箭头函数
中arguments
被rest
替代
rest
箭头函数中不绑定arguments
,在箭头函数中使用arguments
会在上层作用域中查找
作为替代,ES6
中引入了了rest parameter
,可以将不定数量的参数放入数组中
与arguments
不同的是,rest
是一个数组
rest
只包含那些没有形参对应的参数
如果一个函数有形参
,那么rest必须放到最后面
function foo(a, b, ...args) {
console.log(a, b)
console.log(args)
}
foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
控制台打印结果
纯函数
在函数式编程中有个概念叫纯函数
只要一个函数符合以下条件就是一个纯函数
函数在有相同的输入时,需要有相同的输出
函数不能有副作用
副作用
即函数除了返回值之外还对函数调用产生了附加影响
如修改全局变量
,修改参数或外部存储
纯函数
的最大好处就是你可以安心的编写以及安心的使用
只需要关心业务逻辑
,不需要关心传入的值
和是否依赖外部变量
在用的时候也可以确定输入内容不会被任意篡改
,并且自己确定的输入,一定会有确定的输出
以下是几个简单的纯函数
例子
function sum(num1, num2) {
return num1 + num2
}
var num = sum(1, 2)
下面就不是一个纯函数
,因为确定的输入并不会导致确定的输出
var flag = true
function sum(num1, num2) {
if (flag) {
return num1 + num2
} else {
return num1 * num2
}
}
var num = sum(1, 2)
flag = false
num = sum(1, 2)
柯里化函数
函数柯里化
即将函数一次接收多个参数变成一次接收一个并且返回一个新函数接收剩下的参数
这个过程就称之为柯里化
柯里化的优势有以下几点
函数职责单一
在函数式编程
中,我们其实往往希望一个函数处理的问题尽可能的单一
,而不是将一大堆的处理过程交给一个函数来处理
那么我们就可以将每次传入的参数在单一的函数中进行处理
,处理完后在下一个函数中再使用处理后的结果
函数的参数复用
如下代码所示
如果我们想计算多次5+7和5+3的话就可以利用柯里化的这个特点来实现每次只输入一个参数,剩下的参数进行复用function addCurried(a) { return function(b) { return add(a, b); } } const add5 = addCurried(5); console.log(add5(3)); // 输出 8 console.log(add5(7)); // 输出 12
以下是一个简单的柯里化
函数
function sum(x, y, z) {
return x + y + z
}
function sum2(x) {
return function sum3(y) {
return function sum4(y) {
return x + y + z
}
}
}
console.log(sum(1, 2, 3))
console.log(sum2(1)(2)(3))
在这其中,sum为改造之前的函数,sum2为改造后的柯里化函数
自动实现函数柯里化
封装一个函数
来帮我们自动实现柯里化
操作
function sum(x, y, z) {
return x + y + z
}
function logInfo(error, msg) {
console.log(`${error}:${msg}`)
}
function hyCurrying(fn) {
function curryFn(...args) {
if (args.length >= fn.length) {
return fn(...args)
} else {
return function (...rest) {
return curryFn(...args.concat(rest))
}
}
}
return curryFn
}
var sumCurry = hyCurrying(sum)
var infoCurry = hyCurrying(logInfo)
console.log(sumCurry(1)(2)(3))
console.log(sum(1, 2, 3))
infoCurry("ERROR")("not defined")
logInfo("ERROR", "not defined")
这里使用hyCurrying
构造一个柯里化函数curryFn
在curryFn
中主要做两件事
- 当传入的参数
大于等于
原本函数所需的参数时,就执行原函数
,并将原函数可能得返回值返回
- 当传入的参数
小于
原本函数所需的参数时就需要再次调用函数
(即以这样的形式func()()
),定义一个新函数并将这个新函数返回给sumCurry
调用,将再次传进去的参数rest
合并到原本的args
参数中然后调用curryFn
函数,并将curryFn
的返回值返回
最后来看看结果
有些时候,我们可能在使用函数的时候给他显式绑定this
,这个时候以上的代码就需要进行更改
function hyCurrying(fn) {
function curryFn(...args) {
if (args.length >= fn.length) {
return fn.apply(this,args)
} else {
return function (...rest) {
return curryFn.apply(this,args.concat(rest))
}
}
}
return curryFn
}
组合函数
有时候我们需要对某一个数据进行函数调用,会执行两个函数
每调用次函数,就要执行两个函数,操作上就显得重复了
将两个函数组合起来,自动调用,这个过程就是组合函数
以下是一个组合函数
的例子
function double(num) {
return num * 2
}
function square(num) {
return num ** 2
}
function compose(fn1, fn2) {
return function (x) {
return fn2(fn1(x))
}
}
var calcFn = compose(double, square)
console.log(calcFn(20))
自动实现组合化
上面列举的代码只能实现两个函数的组合,有时候我们需要传入更多的函数与参数时就需要对以上代码进行优化
function double(num) {
return num * 2
}
function square(num) {
return num ** 2
}
function compose(...fns) {
for (var fn of fns) {
if (typeof fn !== "function") {
throw new TypeError(`${fn} Expected a function`)
}
}
if (fns.length == 0) return
return function (...args) {
var result = fns[0].apply(this, args)
for (var i = 1; i < fns.length; i++) {
result = fns[i].call(this, result)
}
return result
}
}
var calcFn = compose(double, square)
console.log(calcFn(20))
在函数compose
中我们先对传入的参数进行判断,如果传入的不是函数,或者传入的参数为空就直接抛出异常结束执行
在之后我们返回一个新函数
新函数首先会调用fns
中的第一个函数来得到result
,以此作为以后函数传入的参数
每次函数调用完毕后都将更新result里的值
,直到fns
里的函数运行完毕
,返回result的值
with与eval
无论是with
还是eval
都会造成兼容和性能问题
,真实开发中一般并不常用仅做了解
with
with
语句主要用于延长语句的作用域链
它会在包裹的语句上额外添加一层作用域链
with会造成许多兼容性问题
var obj = {
message: "hello world"
}
with (obj) {
console.log(message)
}
打印结果
eval
eval
可以将传入的字符串
当做JavaScript代码
来运行
eval
会将最后一句执行语句的结果,作为返回值
eval
代码必须加上分号
eval代码的可读性十分差
eval
代码因为是字符串容易被刻意篡改,造成安全问题
eval代码无法被浏览器优化
var str = 'var message = "hello world"; console.log(message)'
eval(str)
结果
严格模式
JavaScript
长久以来的发展或多或少的留下了一些错误或者不完善的问题
为了兼容旧代码
,这些问题会永远的留在了JavaScript
里
为了解决这个问题,在ES5
中正式提出了严格模式
的概念
严格模式是一种具有限制性的JavaScript模式
在这种模式运行下的代码
会受到更为严格的检测
严格模式
对正常的JavaScript
语义进行了一些限制
严格模式
会通过抛出错误
来替换以前的静默错误
严格模式也会让代码得到更好的优化
严格模式
也会禁用ES未来版本中可能定义的一些语法
开启严格模式只需要在代码最上方加一行
"use strict"
严格模式
支持在js文件
中开启
在文件中使用即代表整个文件开启严格模式
也支持在函数
中开启
在函数中使用即代表整个函数开启严格模式
必须将此代码写在最上方
才能生效
注意,开启严格模式
后无法返回默认模式
严格模式的限制
- 无法
意外的创建全局变量
- 严格模式会使引起
静默失败的赋值操作抛出异常
- 严格模式下不允许
删除不可删除的属性
- 4.严格模式不允许
函数参数有相同的名称
- 不允许
0开头的八进制语法
- 在严格模式下,不允许
使用with
- 在严格模式下,
eval不再为上层引用变量
- 严格模式下,
this绑定不会默认转成对象