本文深入解析JavaScript函数增强特性,涵盖函数属性、arguments与剩余参数区别、纯函数概念及副作用控制。详细讲解柯里化原理、组合函数封装及严格模式的限制与优势,帮助开发者提升代码质量与性能。
一、函数增强
1.1 函数对象的属性
在JavaScript中,函数本质上也是一种对象,因此它同样具备属性与方法。
name:通过此属性可以获取函数的名称。
length:该属性用于返回函数定义时形参的数量。
- 需要注意的是,rest参数(剩余参数)不计入length属性的统计中。
var bar = function(m, n, ...others) {
}
console.log(bar.length) // 21.2 arguments
arguments是一个类数组对象,用于对应传递给函数的所有参数。
需要明确的是,类数组(array-like)意味着它并非真正的数组类型,而是一个对象类型。
它具备数组的部分特性,例如拥有length属性,并且可以通过索引访问元素。
但它不具备数组的方法,例如filter、map等。
arguments转数组
可以通过遍历arguments对象,将其元素添加到一个新数组中。
另一种方法是调用数组slice函数的apply方法。
ES6中的两个方法[].slice.apply(arguments) Array.prototype.slice.apply(arguments)- ES6提供了Array.from方法。
- 也可以使用扩展运算符[…arguments]。
- 特别注意:箭头函数不绑定arguments
- 在箭头函数内部使用arguments时,它会向上层作用域查找arguments对象。
function foo() { var bar = () => { console.log(arguments) } bar() } foo(111, 222) // Arguments(2) [111, 222, callee: ƒ, Symbol(Symbol.iterator): ƒ]
1.3 剩余rest参数
ES6引入了rest parameter(剩余参数),可以将不定数量的参数收集到一个数组中。
- 如果最后一个参数以...为前缀,剩余的实参将被放入该参数中,并作为一个数组处理。
function foo(num1, num2, ...otherNums) { // otherNums数组 console.log(otherNums) // [111, 222, 333] } foo(20, 30, 111, 222, 333)那么剩余参数和arguments有什么区别呢?
剩余参数仅包含那些没有对应形参的实参,而arguments对象包含了传给函数的所有实参。
arguments对象不是真正的数组,而rest参数是一个真正的数组,支持所有数组操作方法。
arguments是早期ECMAScript中为了方便获取所有参数而提供的数据结构,而rest参数是ES6中提供并旨在替代arguments的新特性。
注意:剩余参数必须放置在参数的最后位置,否则会导致报错。
1.4 纯函数的使用
- 纯函数的概念:
- 纯函数的定义是:确定的输入一定产生确定的输出。
- 执行过程中不能副作用
- 副作用是指在执行函数时,除了返回函数值之外,还对调用者产生了附加影响,例如修改了全局变量、修改了参数或改变了外部存储。
- 实际开发中应用:
- 在React中,无论是函数组件还是class组件,都要求像纯函数一样保护其props不被修改。
- 并非所有函数都必须是纯函数。
在数组方法中,slice是一个纯函数,不会修改原数组;而splice不是一个纯函数。
slice:截取数组时不会对原数组进行任何操作,而是生成并返回一个新的数组。
splice:截取数组时会返回新数组,同时也会修改原数组。
1.5 柯里化的过程
什么是柯里化
- 柯里化是指只传递给函数一部分参数来调用它,让它返回一个处理剩余参数的函数。
两个案例:
logInfo
// 1.没有柯里化的时候做法 function logInfo(date, type, message) { console.log(`时间:${date} 类型:${type} 内容:${message}`) } // // 打印日志 // logInfo("2022-06-01", "DEBUG", "修复界面搜索按钮点击的bug") // // 又修复了一个bug // logInfo("2022-06-01", "DEBUG", "修复了从服务器请求数据后展示的bug") // logInfo("2022-06-01", "DEBUG", "修复了从服务器请求数据后展示的bug") // logInfo("2022-06-01", "DEBUG", "修复了从服务器请求数据后展示的bug") // logInfo("2022-06-01", "FEATURE", "增加了商品的过滤功能") // 2.对函数进行柯里化: 柯里化函数的做法 // var logInfo = date => type => message => { // console.log(`时间:${date} 类型:${type} 内容:${message}`) // } function logInfo(date) { return function(type) { return function(message) { console.log(`时间:${date} 类型:${type} 内容:${message}`) } } } var logToday = logInfo("2022-06-01") var logTodayDebug = logToday("DEBUG") var logTodayFeature = logToday("FEATURE") // 打印debug日志 logTodayDebug("修复了从服务器请求数据后展示的bug") logTodayDebug("修复界面搜索按钮点击的bug") logTodayDebug("修复界面搜索按钮点击的bug") logTodayDebug("修复界面搜索按钮点击的bug") logTodayDebug("修复界面搜索按钮点击的bug") logTodayFeature("新建过滤功能") logTodayFeature("新建搜索功能")makeAdder
function makeAdder(count) { function add(num) { return count + num } return add } // 1.数字和5相加 var adder5 = makeAdder(5) adder5(10) adder5(15) adder5(18) // 2.数组和10相加 var adder10 = makeAdder(10) adder10(10) adder10(16) adder10(19)
柯里化优势:
- 函数的职责更加单一。
- 实现了函数参数的复用。
自动柯里化的函数封装
// 将一个普通函数转化成柯里化函数 function curried(fn) { function curryFn(...args) { // 传入的参数大于等于fn的需要的参数个数,直接执行fn函数 // 否则返回新的函数继续接收参数 if(args.length >= fn.length) { return fn.apply(this, args) } else { // 返回新的函数并接收参数 return function(...newArgs) { // 递归调用currFn,此处的参数为上一次传入的参数和之后传入的参数 return curryFn.apply(this, args.concat(newArgs)) } } } return curryFn }
- 注意:函数柯里化的性能并不高,因为使用了闭包,这可能会导致内存泄露。
1.6 组合函数的使用
- 组合函数
- 组合函数是指将多个函数组合在一起,依次调用。
封装组合函数
// 传入多个函数,自动的将多个函数组合在一起依次调用 function composeFn(...fns) { var length = fns.length // 边界判断 if (length <= 0) return for (var fn of fns) { if (typeof fn !== "function") { throw new Error("Expected a function") } } return function (...args) { // 第一个函数可能传入多个参数 var result = fns[0].apply(this, args) // 之后的函数依次调用 for (var i = 1; i < length; i++) { result = fns[i].apply(this, [result]) } return result } }
二、额外知识
2.1 with
with语句用于扩展一个语句的作用域链。
var obj = { message: "Hello World" } with (obj) { console.log(message) // Hello World }不建议使用with语句,因为它可能是混淆错误和兼容性问题的根源。
2.2 eval
内建函数eval允许执行一个代码字符串。
eval是一个特殊的函数,它可以将传入的字符串当作JavaScript代码来运行。
eval会将最后一句执行语句的结果作为返回值。
var message = "Hello World" var codeString = `var name = "kobe"; console.log(name); console.log(message); "abc";` var result = eval(codeString) console.log(result) // abc不建议在开发中使用eval:
eval代码的可读性非常差(代码的可读性是高质量代码的重要原则)。
eval接收的是一个字符串,在执行过程中有可能被刻意篡改,从而造成被攻击的风险。
eval的执行必须经过JavaScript解释器,因此不能被JavaScript引擎优化。
2.3 严格模式
- 什么是严格模式?
严格模式是一种具有限制性的JavaScript模式,从而使代码隐式地脱离了“懒散(sloppy)模式”。
支持严格模式的浏览器在检测到代码中有严格模式时,会以更加严格的方式对代码进行检测和执行。
严格模式对正常的JavaScript语义进行了一些限制:
严格模式通过抛出错误来消除一些原有的静默(silent)错误。
严格模式让JS引擎在执行代码时可以进行更多的优化(不需要对一些特殊的语法进行处理)。
严格模式禁用了在ECMAScript未来版本中可能会定义的一些语法。
- 如何开启严格模式:
- 严格模式通过在文件或者函数开头使用use strict来开启。
// 给整个script开启严格模式 "use strict" // 给一个函数开启严格模式 function foo() { "use strict" } - 严格模式的限制:
无法意外地创建全局变量。
严格模式会使引起静默失败(silently fail,注:不报错也没有任何效果)的赋值操作抛出异常。
严格模式下试图删除不可删除的属性会抛出错误。
严格模式不允许函数参数有相同的名称。
不允许使用0开头的八进制语法。
在严格模式下,不允许使用with语句。
在严格模式下,eval不再为上层引用变量。
严格模式下,this绑定不会默认转换成对象。
"use strict" // 1.不会意外创建全局变量 // function foo() { // message = "Hello World" // } // foo() // console.log(message) // 报错 // 2.发现静默错误 var obj = { name: "kobe" } Object.defineProperty(obj, "name", { writable: false, configurable: false }) // obj.name = "curry" console.log(obj.name) // delete obj.name console.log(obj) // 3.参数名称不能相同 // function foo(num, num) { // } // 4.不能以0开头 // console.log(0o123) // 0o是ES6新语法可以写 // 5.eval函数不能为上层创建变量 // eval(`var message = "Hello World"`) // console.log(message) // 6.严格模式下, this是不会转成对象类型的 function foo() { console.log(this) } foo.apply("abc") foo.apply(123) foo.apply(undefined) foo.apply(null) // 独立函数执行默认模式下, 绑定window对象 // 在严格模式下, 不绑定全局对象而是undefined foo()
总结
到此这篇关于JavaScript函数增强及额外知识的文章就介绍到这了,更多相关js函数增强知识内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!