JavaScript函数增强
# 函数对象的属性
JavaScript中函数也是一个对象,对象就可以有属性和方法
# name属性
一个函数的名字可以通过name来访问
function foo(){}
console.log(foo.name) // foo
var bar = function(){}
console.log(bar.name) // bar
2
3
4
5
# length 属性
length属性用于返回函数参数中形参的个数,其中rest(剩余参数或者默认值,例如...args)参数是不参与参数的个数的
var baz = (name, age, ...args) => {}
console.log(baz.length) // 2
2
# arguments
arguments是一个对应于传递给函数的参数的类数组对象
function foo(x, y) {
console.log(arguments);
// for循环遍历
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[1]);
}
// for of 遍历
for (var arg of arguments) {
console.log(arg);
}
// 因为arguments不是一个真正的数组,因此不能使用forEach, filter等高阶函数
}
foo(1, 2, 3, 4);
foo(1, 2);
2
3
4
5
6
7
8
9
10
11
12
13
14
类数组意味着它并不是一个数组类型,而是一个对象类型;但是
- 拥有数组的一些特性,比如说length,比如说可以使用index索引来访问
- 但是没有数组的一些方法,例如filter, map等。
# arguments转array
在开发中,需要景arguments转成array,以便使用数组的一些特性
- 方式一:遍历arguments,添加到一个新数组中
- 方式二:调用数组slice函数的call方法
- 方式三:使用ES6中的两个方法
- Array.from(可迭代对象)
- [...arguments]
function foo(x, y) {
// 方案一
var length = arguments.length;
var arr = [];
for (var i = 0; i < length; i++) {
arr.push(arguments[i]);
}
console.log(arr);
// 方案二 ES6中的方法
const arr2 = Array.from(arguments);
const arr3 = [...arguments];
console.log(arr2);
console.log(arr3);
// 方案三 slice 方法
var arr4 = Array.prototype.slice.call(arguments);
var arr5 = [].slice.call(arguments);
console.log(arr4);
console.log(arr5);
}
foo(1, 2, 3, 4);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 箭头函数不绑定arguments
箭头函数是不绑定arguments的,所以在箭头函数中使用arguments会去上层作用域中查找
# 函数剩余(rest)参数
ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中,如果最后一个参数是...
为前缀的,那么它将会将剩余的参数放到该参数中,并且作为一个数组
剩余参数和arguments的区别
- 剩余参数只包含那些没有对应形参的实参,而arguments对象包含了传给函数的所有实参
- arguments对象不是一个真正的数组,而rest参数是一个真正的数组,可以进行数组的所有操作
- arguments是早期的ECMAScript中为了方便去获取所有参数的提供的一个数据结构,而rest参数是ES6中提供并且希望以此代替arguments的
- 注意事项:剩余参数必须放到最后一个位置,否则报错
# JavaScript纯函数
函数式编程中有一个重要的概念叫做纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念
- 在react开发中纯函数会被多次提及
- react组件就被要求像是一个纯函数,
- redux中的reducer也被要求必须是一个纯函数
# 纯函数的定义
- 在程序设计中,若一个函数符合以下条件,那么这个函数被称为纯函数
- 此函数在相同的输入值时,需产出相同的输出
- 函数的输出和输入值以外的其它隐藏信息或状态无关,也和I/O设备产生的外部输出无关
- 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。
# 总结
- 确定的输入,一定会产出确定的输出
- 函数在执行过程中,不能产生副作用。
# 什么是副作用
在计算机科学中,副作用表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,比如修改了全局变量,修改参数或者改变外部存储
纯函数在执行过程中就是不能产生副作用,副作用可能产生隐藏的bug
# 纯函数的案例
操作数组时使用的函数
- slice:该函数截取数组后返回新的数组,不会修改原数组,是一个纯函数
- splice:该函数截取数组后,会返回一个新的数组,但是会修改原数组,因此不是一个纯函数。
# 纯函数的作用和优势
- 安心用,安心写
- 写函数的时候保证函数的纯度,单纯实现业务逻辑即可,不需要关心传入的内容是如何获得或者依赖其它的外部变量
- 用的时候,确定输入的内容不会被任意篡改,确定自己的输出一定会有确定的输入。
# 柯里化
柯里化是函数式编程中重要的概念
- 是一种关于函数的高阶技术
- 不仅用于JavaScript语言,也被用于其它语言
维基百科解释
- 柯里化,卡瑞化,加里化
- 把接收的多个参数编程接受一个单一参数(接受最初函数的第一个参数)的函数,并且返回接受余下的参数,而且返回结果的新函数的技术
- 如果固定某些参数,将得到接受余下参数的一个函数
总结
- 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数
- 这个过程称之为柯里化
柯里化是一种函数的转换,将一个函数从可调用的f(a,b,c)转换为可调用的f(a)(b)(c),柯里化不会调用函数,只是对函数进行转换
# 柯里化转换的代码表现
// 未柯里化的函数
function add1(x, y, z) {
return x + y + z;
}
console.log(add1(10, 20, 30));
// 柯里化处理的函数
function add2(x) {
return function (y) {
return function (z) {
return x + y + z;
};
};
}
console.log(add2(10)(20)(30));
var add3 = (x) => (y) => (z) => {
return x + y + z;
};
console.log(add3(10)(20)(30));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 柯里化优势一 --- 函数的职责单一
为什么需要有柯里化
- 函数式编程中,往往希望一个函数处理的问题尽可能的单一,而不是将一大堆的处理过程交给一个函数来处理
- 可以将每次传入的参数在单一的函数中进行处理,处理完后在下一个函数中再使用处理后的结果
例如 传入的函数需要分别被进行如下处理
- 第一个参数 + 2
- 第二个参数 * 2
- 第三个参数 ** 2
function add2(x) {
x = x + 2;
return function (y) {
y = y * 2;
return function (z) {
z = z ** 2;
return x + y + z;
};
};
}
console.log(add2(10)(20)(30));
2
3
4
5
6
7
8
9
10
11
# 柯里化优势二 --- 函数的参数复用
另外一个使用柯里化的场景是可以帮助我们可以复用参数逻辑
function createAdder(count) {
function adder(num) {
return count + num;
}
return adder;
}
var adder5 = createAdder(5);
adder5(10); // 15
var adder10 = createAdder(10);
adder10(6); // 16
2
3
4
5
6
7
8
9
10
# 柯里化案例
// 模拟打印日志案例
function log(date, type, message) {
console.log(
`[${date.getHours()}:${date.getMinutes()}] [${type}] [${message}]`
);
}
log(new Date(), "DEBUG", "修复问题");
log(new Date(), "FEATURE", "新功能");
var logc = (date) => (type) => (message) => {
console.log(
`[${date.getHours()}:${date.getMinutes()}] [${type}] [${message}]`
);
};
var logNow = logc(new Date());
logNow("DEBUG")("轮播debug");
logNow("FEATURE")("添加新功能");
var logNowDebug = logc(new Date())("DEBUG");
logNowDebug("轮播debug");
logNowDebug("添加新功能");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 自动柯里化函数
function hyCurring(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
// return fn(...args);
return fn.apply(this, args);
} else {
return function (...newArgs) {
// return curried(args.concat(newArgs))
return curried.apply(this, args.concat(newArgs));
};
}
};
}
function sum(a, b, c) {
return a + b + c;
}
var sumCur = hyCurring(sum);
console.log(sumCur(10)(20)(30));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 组合函数
组合函数是在JavaScript开发过程中一种对函数的使用技巧,模式。比如
- 现在需要对某一个数据进行函数的调用,执行两个函数fn1和fn2,这两个函数是依次执行的
- 如果每次都需要进行两个函数的调用,操作上就会显得重复
- 可以将这两个函数组合起来,自动依次调用
- 这个过程就是对函数的组合,我们称之为 组合函数(Compose Function)
function compose(fn1, fn2) {
return function (x) {
return fn2(fn1(x));
};
}
function double(num) {
return num * 2;
}
function square(num) {
return num ** 2;
}
var calcFn = compose(double, square);
console.log(calcFn(20));
2
3
4
5
6
7
8
9
10
11
12
13
# 自动组合函数
function double(num) {
return num * 2;
}
function square(num) {
return num ** 2;
}
var calcFn = compose(double, square);
console.log(calcFn(20));
function compose(...fns) {
// 边界情况
var length = fns.length;
if (length <= 0) return;
for (var i = 0; i < length; i++) {
var fn = fns[i];
if (typeof fn !== "function") {
throw new TypeError("Expected a function ");
}
}
// 取出所有的函数一次调用
return function (...args) {
var result = fns[0].apply(this, args);
for (var i = 1; i < length; i++) {
var fn = fns[i];
result = fn.apply(this, [result]);
}
return result
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# with语句的使用
with语句扩展一个语句的作用域链
var obj = {
name: 'Hello World',
age: 18
}
with(obj){
console.log(name)
console.log(age)
}
2
3
4
5
6
7
8
实际开发中并不推荐使用with语句,因为它可能是混淆错误和兼容性问题的根源。
# eval函数
内建函数eval允许执行一个代码字符串
- eval是一个特殊的函数,它可以将传入的字符串当做JavaScript代码来执行
- eval会将最后一句执行语句的结果作为返回值
不建议在开发中使用eval
- eval代码的可读性非常的差
- eval是一个字符串,那么有可能在执行的过程中被可以篡改,那么可能会造成被攻击的风险
- eval的执行必须进过JavaScript解释器,不能被JavaScript引擎优化
var evalString = `var message = 'Hello World'; console.log(message)`
eval(evalString)
console.log(message)
2
3
# 严格模式
# JavaScript历史局限性
# JavaScript严格模式的概念---ES5标准中提出
# 开启严格模式
在现代JavaScript中(ES6)之后会自动启动严格模式
# 严格模式的限制
- 不会意外的创建全局变量
"use strict"
function foo(){
message = "Hello World"
}
foo()
console.log(message) // 严格模式下此行代码报错,message is not defined
2
3
4
5
6
7
8
- 严格模式会使引起静默失败的赋值操作抛出异常
"use strict"
var obj = {
name: "zs"
}
Object.defineProperty(obj,"name", {
writable: true // 禁止name属性被修改
})
obj.name = "ls" // 一旦开启严格模式就会报错
console.log(obj.name) // 非严格模式下输出 zs,并且不会报任何错误,
2
3
4
5
6
7
8
9
10
11
12
- 严格模式下视图删除不可删除的属性
"use strict"
var obj = {
name: "zs"
}
Object.defineProperty(obj,"name", {
writable: false // 禁止name属性被修改
configurable: false // 属性不可配置,例如删除属性,严格模式下会报错,非严格模式下不会报错,属性不会被删除
})
delete obj // 一旦开启严格模式就会报错
console.log(obj.name) // 非严格模式下输出 zs,并且不会报任何错误,
2
3
4
5
6
7
8
9
10
11
12
13
严格模式下不允许函数参数有相同的名字
不允许使用以0开头的八进制语法,可以使用以
0o
开头的八进制语法在严格模式下,不允许使用with
在严格模式下,eval不再为上层创建变量
在严格模式下,this是不会转成对象类型的