参数对象arguments,纯函数,柯里化,组合函数,with语句,eval函数,严格模式
1. arguments对象
1.1. 认识arguments
arguments 是一个对应于
传递给函数的参数
的类数组(array-like)对象
1
2
3
4
5
6function foo(x,y,z){
// [Arguments] { '0': 1, '1': 2, '2': 3 }
console.log(arguments);
}
foo(1,2,3);array-like
意味着它不是一个数组类型,而是一个对象类型- 但是它却拥有数组的一些特性,比如说length,比如可以通过index索引来访问
- 但是它却没有数组的一些方法,比如forEach、map等
1
2
3
4
5
6
7
8function foo(x,y,z){
// 3
console.log(arguments.length);
// 6
console.log(arguments[0] + arguments[1] + arguments[2]);
}
foo(1,2,3);
1.2. arguments转成array
1.2.1. push
1 | var length = arguments.length; |
1.2.2. slice
1 | var arr1 = Array.prototype.slice.call(arguments); |
其中slice的实现是数组的复制
1 | Function.prototype.hyslice = function(start, end){ |
1.2.3. Array.from
1 | var arr3 = Array.from(arguments); |
1.2.4. …
1 | var arr4 = [...arguments]; |
1.3. 箭头函数不绑定arguments
箭头函数是不绑定arguments的
1 | // 箭头函数不绑定arguments |
在箭头函数中使用arguments会去上层作用域查找
1 | function fun(m,n){ |
2. 纯函数
2.1. 认识纯函数
函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念
- 在react开发中纯函数是被多次提及的
- 比如react中组件就被要求像是一个纯函数(为什么是像,因为还有class组件),redux中有一个reducer的概念,也是要求必须是一个纯函数
- 所以掌握纯函数对于理解很多框架的设计是非常有帮助的
纯函数的
维基百科定义
- 在程序设计中,若一个函数
符合以下条件
,那么这个函数被称为纯函数 - 此函数在
相同的输入值
时,需产生相同的输出
- 函数的输出和输入值以外的
其他隐藏信息或状态无关
,也和由I/O设备产生的外部输出无关
- 该函数
不能有语义上可观察的函数副作用
,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等
- 在程序设计中,若一个函数
当然上面的定义会过于的晦涩,简单总结一下
- 确定的输入,一定会产生确定的输出
- 函数在执行过程中,不能产生副作用
2.2. 副作用的理解
什么是副作用
- 副作用(side effect)其实本身是医学的一个概念,比如经常说吃什么药本来是为了治病,可能会产生一 些其他的副作用
- 在计算机科学中,也引用了副作用的概念,表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,比如修改了全局变量,修改参数或者改变外部的存储
纯函数在执行的过程中就是不能产生这样的副作用
- 副作用往往是产生bug的 “温床”
2.3. 纯函数的案例
- 一个对数组操作的两个函数
- slice:slice截取数组时不会对原数组进行任何操作,而是生成一个新的数组
- splice:splice截取数组,会返回一个新的数组,也会对原数组进行修改
- slice就是一个纯函数,不会修改传入的参数
1 | var names = ['abc','cba','nba']; |
2.4. 纯函数判断
是
相同的输入有相同的输出
执行的过程中不会产生任何副作用
1 | function sum(num1, num2){ |
不是
相同的输入没有相同的输出
1 | let foo = 5; |
不是
修改了传入的参数
1 | function printInfo(info){ |
2.5. 纯函数的优势
为什么纯函数在函数式编程中非常重要
- 因为可以安心的编写和安心的使用
- 在写的时候保证了函数的纯度,只是单纯实现自己的业务逻辑即可,不需要关心传入的内容是如何获得的或 者依赖其他的外部变量是否已经发生了修改
- 在用的时候,确定输入内容不会被任意篡改,并且确定的输入,一定会有确定的输出
React中就要求无论是函数还是class声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改
3. 柯里化
3.1. 认识柯里化
柯里化也是属于函数式编程里面一个非常重要的概念
维基百科的解释
- 在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化
- 是把
接收多个参数的函数
,变成接收一个单一参数
(最初函数的第一个参数)的函数,并且返回接收余下的参数
,而且返回结果的新函数
的技术 - 柯里化声称 “如果固定某些参数,将得到接受余下参数的一个函数”
做一个总结
- 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩余的参数
- 这个过程就称之为柯里化
3.2. 柯里化的结构
1 | // 未柯里化 |
3.3. 柯里化作用
为什么需要有柯里化
- 在函数式编程中,往往希望一个函数处理的问题尽可能的单一,而不是将一大堆的处理过程交给一个函数来处理
- 那么是否就可以将每次传入的参数在单一的函数中进行处理,处理完后在下一个函数中再使用处理后的结果
比如对上面的案例进行一个修改
- 传入的函数需要分别被进行如下处理
- 第一个参数 + 2
- 第二个参数 * 2
- 第三个参数 ** 2
1 | function add2(x){ |
3.4. 柯里化的复用
- 另外一个使用柯里化的场景是可以复用参数逻辑
- makeAdder函数要求传入一个num(并且可以在这里对num进行一些修改)
- 在之后使用返回的函数时,不需要再继续传入num了
1 | function makeAdder(num){ |
3.5. 打印日志的柯里化
- 演示一个案例,需求是打印一些日志
- 日志包括时间、类型、信息
1 | var log = date => type => message => { |
3.6. 自动柯里化函数
1 | function add1(x, y, z){ |
4. 组合函数
4.1. 理解组合函数
- 组合(Compose)函数是在JavaScript开发过程中一种对函数的使用技巧、模式
- 比如需要对某一个数据进行函数的调用,执行两个函数fn1和fn2,这两个函数是依次执行的
- 如果每次都需要进行两个函数的调用,操作上就会显得重复
- 那么可以将这两个函数组合起来,自动依次调用
- 这个过程就是对函数的组合,称之为组合函数(Compose Function)
1 | function double(num){ |
4.2. 实现组合函数
- 需要考虑更加复杂的情况
- 比如传入了更多的函数
- 在调用 compose函数时,传入了更多的参数
1 | function double(num){ |
5. with语句
- with语句扩展一个语句的作用域链,形成自己的作用域
- 不建议使用with语句,因为它可能是混淆错误和兼容性问题的根源
1 | var obj = { |
6. eval函数
- eval是一个特殊的函数,它可以将传入的字符串当做JavaScript代码来运行
- 不建议在开发中使用eval
- eval代码的可读性非常的差(代码的可读性是高质量代码的重要原则)
- eval是一个字符串,那么有可能在执行的过程中被刻意篡改,那么可能会造成被攻击的风险
- eval的执行必须经过JS解释器,不能被JS引擎优化
1 | var evalStr = `var name = 'test'; console.log(name);`; |
7. 严格模式
7.1. 认识严格模式
在ECMAScript5标准中,JavaScript提出了严格模式的概念(Strict Mode)
- 严格模式很好理解,是一种具有限制性的JavaScript模式,从而使代码隐式的脱离了 “懒散(sloppy)模式”
- 支持严格模式的浏览器在检测到代码中有严格模式时,会以更加严格的方式对代码进行检测和执行
严格模式对正常的JavaScript语义进行了一些限制
- 严格模式通过 抛出错误 来消除一些原有的 静默(silent)错误
- 严格模式让JS引擎在执行代码时可以进行更多的优化(不需要对一些特殊的语法进行处理)
- 严格模式禁用了在ECMAScript未来版本中可能会定义的一些语法
7.2. 开启严格模式
那么如何开启严格模式呢?严格模式支持粒度话的迁移可以
- 支持在js文件中开启严格模式
- 也支持对某一个函数开启严格模式
严格模式通过在文件或者函数开头使用
use strict
来开启开发中,打包工具会自动开启严格模式
1 | function foo(){ |
7.3. 严格模式限制
- 严格模式下的严格语法限制
- JavaScript被设计为新手开发者更容易上手,所以有时候本来错误语法,被认为也是可以正常被解析的
- 但是这种方式可能给带来留下来安全隐患
- 在严格模式下,这种失误就会被当做错误,以便可以快速的发现和修正
无法意外的创建全局变量
1
2
3
4
5
6
7
8
9
10
11
12
13;
// ReferenceError: name is not defined
name = "Hello World";
console.log(name);
function foo(){
m = "foo";
}
// ReferenceError: m is not defined
foo();
console.log(m);严格模式会使引起静默失败(silently fail,注:不报错也没有任何效果)的赋值操作抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
13// TypeError: Cannot create property 'name' on boolean 'true'
// true.name = "abc";
// TypeError: Cannot assign to read only property 'NaN' of object '#<Object>'
// NaN = 123;
var obj = {}
Object.defineProperty(obj,"name",{
writable: false,
value: 'test'
});
// TypeError: Cannot assign to read only property 'name' of object '#<Object>'
// obj.name = '123';严格模式下试图删除不可删除的属性
严格模式不允许函数参数有相同的名称
1
2
3
4
5
6
7function foo(x, y, x){
// SyntaxError: Duplicate parameter name not allowed in this context
console.log(x);
}
foo(1, 2, 3);不允许0的八进制语法
1
2
3
4
5
6
7
8
9
10// 八进制
// SyntaxError: Octal literals are not allowed in strict mode.
// var num = 0123;
// console.log(num);
var num1 = 0o123;
// 十六进制
var num2 = 0x123;
// 二进制
var num3 = 0b100;
console.log(num1, num2, num3);在严格模式下,不允许使用with
在严格模式下,eval不再为上层引用变量
1
2
3
4var evalStr = `var message = 'test'; console.log(message);`;
eval(evalStr);
// ReferenceError: message is not defined
// console.log(message);严格模式下,this绑定中自执行函数(默认绑定)会指向undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function foo(){
console.log(this);
}
// undefined
foo();
// {}
foo.call({});
// {name:'test'}
foo.apply({name:'test'});
var obj = {
name: 'test',
foo: foo
}
// obj
obj.foo();
// foo
var fn = new foo();而 setTimeout中的this仍指向window
1
2
3
4
5
6
7
8const recordFunction = goog.require('goog.testing.recordFunction');
state.real = {setTimeout, setInterval, requestAnimationFrame};
state.fake = {
setTimeout: recordFunction(state.real.setTimeout.bind(null))
};
window.setTimeout = state.fake.setTimeout;1
2
3
4
5
6
7
8
9goog.testing.recordFunction = function(opt_f) {
'use strict';
var f = opt_f || goog.nullFunction;
function recordedFunction() {
var owner = /** @type {?} */ (this);
var ret = f.apply(owner, arguments);
}
}