目录

JavaScript函数增强

# 函数对象的属性

JavaScript中函数也是一个对象,对象就可以有属性和方法

# name属性

一个函数的名字可以通过name来访问

function foo(){}
console.log(foo.name) // foo

var bar = function(){}
console.log(bar.name) // bar
1
2
3
4
5

# length 属性

length属性用于返回函数参数中形参的个数其中rest(剩余参数或者默认值,例如...args)参数是不参与参数的个数的

var baz = (name, age, ...args) => {}
console.log(baz.length) // 2
1
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);
1
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);
1
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));
1
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));
1
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
1
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("添加新功能");
1
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));
1
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));
1
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
  };
}
1
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)
}
1
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)
1
2
3

# 严格模式

# JavaScript历史局限性

# JavaScript严格模式的概念---ES5标准中提出

# 开启严格模式

在现代JavaScript中(ES6)之后会自动启动严格模式

# 严格模式的限制

  • 不会意外的创建全局变量
"use strict"
function foo(){
    message = "Hello World"
}

foo()

console.log(message) // 严格模式下此行代码报错,message is not defined
1
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,并且不会报任何错误,
1
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,并且不会报任何错误,
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 严格模式下不允许函数参数有相同的名字

  • 不允许使用以0开头的八进制语法,可以使用以0o开头的八进制语法

  • 在严格模式下,不允许使用with

  • 在严格模式下,eval不再为上层创建变量

  • 在严格模式下,this是不会转成对象类型的

上次更新: 2022/10/31, 08:19:15
最近更新
01
防抖和节流
02-06
02
正则表达式
01-29
03
async_await函数
12-30
更多文章>