我们知道JavaScript中函数也是一个对象,那么对象中就可以有属性和方法。
属性name
:一个函数的名词我们可以通过name来访问;
function foo() {
}
console.log(foo.name);// foo
var bar = function () {}
console.log(bar.name);// bar
属性length
:属性length用于返回函数参数(形参)的个数;
var baz = (name, age, ...args) => {
console.log(baz.length);//2
}
arguments 是一个对应于传递给函数的参数的 类数组
(array-like)对象。
array-like
意味着它不是一个数组类型,而是一个对象类型
:
length
,比如可以通过index
索引来访问;filter
、map
等;function foo(m, n) {
// arguments 类似数组对象
console.log(arguments)
// 1.默认用法:
// 通过索引获取内容
// console.log(arguments[0])
// console.log(arguments[1])
// // 遍历
// for (var i = 0; i < arguments.length; i++) {
// console.log(arguments[i])
// }
// for (var arg of arguments) {
// console.log(arg)
// }
// 2.需求获取所有参数中的偶数
// 数组 filter
// for (var arg of arguments) {
// if (arg % 2 === 0) {
// console.log(arg)
// }
// }
// var evenNums = arguments.filter(item => item % 2 === 0)
// console.log(eventNums)
// 2.1.将arguments转成数组方式一:
// var newArguments = []
// for (var arg of arguments) {
// newArguments.push(arg)
// }
// console.log(newArguments)
// 2.2.将arguments转成数组方式三: ES6中方式
// var newArgs1 = Array.from(arguments)
// console.log(newArgs1)
// var newArgs2 = [...arguments]
// console.log(newArgs2)
// 2.3.将arguments转成数组方式二: 调用slice方法
var newArgs = [].slice.apply(arguments)
// var newArgs = Array.prototype.slice.apply(arguments)
console.log(newArgs)
}
foo(10, 25, 32, 41)
在开发中,我们经常需要将arguments转成Array
,以便使用数组的一些特性。
转化方式一: 遍历arguments
,添加到一个新数组中;
var newArguments = []
for (var arg of arguments) {
newArguments.push(arg)
}
console.log(newArguments)
转化方式二:较难理解(有点绕),了解即可 ,调用数组slice
函数的call
方法;
var newArgs = [].slice.apply(arguments);
var newArr = Array.prototype.slice.call(arguments);
转化方式三:ES6中的两个方法
Array.from
var newArgs1 = Array.from(arguments)
console.log(newArgs1)
var newArgs2 = [...arguments]
console.log(newArgs2)
箭头函数是不绑定arguments的,所以我们在箭头函数中使用arguments会去上层作用域
查找:
// 1.箭头函数不绑定arguments
// var bar = () => {
// console.log(arguments)
// }
// bar(11, 22, 33)
// 2.函数的嵌套箭头函数
function foo() {
var bar = () => {
console.log(arguments)
}
bar()
}
foo(111, 222)
ES6中引用了rest parameter
,可以将不定数量
的参数放入到一个数组中:
...
为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组;function foo(name, age, ...args) {
console.log(name, age);
console.log(args);
}
那么剩余参数和arguments有什么区别
呢?
没有对应形参的实参
,而 arguments 对象包含了传给函数的所有实参
;不是一个真正的数组
,而rest参数是一个真正的数组
,可以进行数组的所有操作;替代arguments
的;剩余参数必须放到最后一个位置,否则会报错。
函数式编程
中有一个非常重要的概念叫纯函数
,JavaScript符合函数式编程的范式,所以也有纯函数的概念;
react
开发中纯函数是被多次提及的;像
是一个纯函数(为什么是像,因为还有class组件),redux中有一个reducer的概念,也是要求必须是一个纯函数;纯函数的维基百科定义:
相同的输入值
时,需产生相同的输出
。函数副作用
,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。当然上面的定义会过于的晦涩,所以我简单总结一下:
那么这里又有一个概念,叫做副作用,什么又是副作用
呢?
执行一个函数
时,除了返回函数值
之外,还对调用函数产生了附加的影响
, 比如修改了全局变量,修改参数或者改变外部的存储;纯函数在执行的过程中就是不能产生这样的副作用
:
产生bug
的 “温床”。function sum(num1, num2) {
return num1 + num2
}
// 不是一个纯函数
var address = "广州市"
function printInfo(info) {
console.log(info.name, info.age, info.message)
info.flag = "已经打印结束"
address = info.address
}
var obj = {
name: "why",
age: 18,
message: "哈哈哈哈"
}
printInfo(obj)
console.log(obj)
if (obj.flag) {
}
我们来看一个对数组操作的两个函数:
slice
就是一个纯函数,不会修改数组本身
,而splice
函数不是一个纯函数;
var names = ["abc", "cba", "nba", "mba"]
// 1.slice: 纯函数
var newNames = [].slice.apply(names, [1, 3])
console.log(names)
// 2.splice: 操作数组的利器(不是纯函数)
names.splice(2, 2)
console.log(names)
为什么纯函数在函数式编程中非常重要呢?
编写
和安心的使用
;外部变量
是否已经发生了修改;React中就要求我们无论是函数还是class声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改
// 1.安心的写: 你不需要去关心外层作用域中的值, 目前是什么状态
var counter = 0
function add(num) {
return num
}
// 2.安心的用: 调用函数时, 可以知道: 确定的输入一定产生确定的输出
add(5) // 10
add(5) // 10
// react中编写函数组件
function Foo(props) {
console.log(props.name)
props.name = "kobe"
}
柯里化
也是属于函数式编程里面一个非常重要的概念。
高阶
技术;我们先来看一下维基百科的解释:
维基百科的结束非常的抽象,我们这里做一个总结:
柯里化是一种函数的转换
,将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)©。
调用
函数。它只是对函数进行转换
。// 未柯里化的函数
function sum2(num1, num2) {
return num1 + num2;
}
console.log(sum2(1, 3));
//柯里化处理的函数
function sum(num1) {
return function (num2) {
return num1 + num2;
}
}
console.log(sum(1)(3));
// 箭头函数柯里化
var add3 = x => y => z => {
return x + y + z;
}
add3(1)(3)(5);
那么为什么需要有柯里化呢?
单一
,而不是将一大堆的处理过程交给一个函数来处理;function add(x) {
x = x + 2;
return function (y) {
y = y * 2;
return function (z) {
z = z ** 2;
return x + y + z;
}
}
}
add(3)(6)(9);
比如上面的案例我们进行一个修改:传入的函数需要分别被进行如下处理
// 案例一: 打印一些日志
// 信息一: 日志的时间
// 信息二: 日志的类型: info/debug/feature
// 信息三: 具体的信息
// 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函数就是对sum的柯里化
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 foo(x, y, z) {
console.log(x + y + z)
}
function sum(num1, num2) {
return num1 + num2
}
function logInfo(date, type, message) {
console.log(`时间:${date} 类型:${type} 内容:${message}`)
}
// 手动转化
// 封装函数: 自动转化柯里化过程(有一点难度)
function hyCurrying(fn) {
function curryFn(...args) {
// 两类操作:
// 第一类操作: 继续返回一个新的函数, 继续接受参数
// 第二类操作: 直接执行fn的函数
if (args.length >= fn.length) { // 执行第二类
// return fn(...args)
return fn.apply(this, args)
} else { // 执行第一类
return function(...newArgs) {
// return curryFn(...args.concat(newArgs))
return curryFn.apply(this, args.concat(newArgs))
}
}
}
return curryFn
}
// 对其他的函数进行柯里化
var fooCurry = hyCurrying(foo)
fooCurry(10)(20)(30)
fooCurry(55, 12, 56)
var sumCurry = hyCurrying(sum)
var sum5 = sumCurry(5)
console.log(sum5(10))
console.log(sum5(15))
console.log(sum5(18))
var logInfoCurry = hyCurrying(logInfo)
logInfoCurry("2022-06-01")("DEBUG")("我发现一个bug, 哈哈哈哈")
// 举个栗子
// var names = ["abc", "cba", "nba"]
// // spread
// console.log(...names)
组合(Compose)函数是在JavaScript开发过程中一种对函数的使用技巧、模式:
依次执行
的;重复
;组合函数
(Compose Function);var num = 100
// 第一步对数字*2
function double(num) {
return num * 2
}
// 第二步对数字**2
function pow(num) {
return num ** 2
}
console.log(pow(double(num)))
console.log(pow(double(55)))
console.log(pow(double(22)))
// 将上面的两个函数组合在一起, 生成一个新的函数
function composeFn(num) {
return pow(double(num))
}
console.log(composeFn(100))
console.log(composeFn(55))
console.log(composeFn(22))
刚才我们实现的compose函数比较简单
我们需要考虑更加复杂的情况:比如传入了更多的函数,在调用compose函数时,传入了更多的参数:
// 第一步对数字*2
function double(num) {
return num * 2
}
// 第二步对数字**2
function pow(num) {
return num ** 2
}
// 封装的函数: 你传入多个函数, 我自动的将多个函数组合在一起挨个调用
function composeFn(...fns) {
// 1.边界判断(edge case)
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 Error(`index position ${i} must be function`)
}
}
// 2.返回的新函数
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
}
}
var newFn = composeFn(double, pow, console.log)
newFn(100)
newFn(55)
newFn(22)
// console.log(newFn(100))
// console.log(newFn(55))
// console.log(newFn(22))
with语句用于扩展一个语句的作用域链,它的作用是操作同一个对象的多个属性时,提供一些书写的方便。
var obj = {
message: "Hello World"
}
with (obj) {
console.log(message)
}
不建议使用with语句,因为它可能是混淆错误和兼容性问题的根源。
内建函数 eval
允许执行一个代码字符串。
不建议在开发中使用eval:
var message = "Hello World"
var codeString = `var name = "why"; console.log(name); console.log(message); "abc";`
var result = eval(codeString)
console.log(result)
function foo() {}
JavaScript历史的局限性
:
永远被保留
在 JavaScript 语言中;在ECMAScript5标准中,JavaScript提出了严格模式的概念(Strict Mode):
限制性
的JavaScript模式,从而使代码隐式的脱离了 ”懒散(sloppy)模式“;严格的方式
对代码进行检测和执行;严格模式对正常的JavaScript语义进行了一些限制:
抛出错误
来消除一些原有的 静默
(silent)错误;那么如何开启严格模式呢?严格模式支持粒度化
的迁移:
js文件
中开启严格模式;函数
开启严格模式;// 给整个script开启严格模式
"use strict"
// 给一个函数开启严格模式
function foo() {
"use strict"
}
class Person {
}
严格模式通过在文件或者函数开头使用 use strict
来开启。
没有类似于 “no use strict” 这样的指令可以使程序返回默认模式。
这里我们来说几个严格模式下的严格语法限制
:
容易上手
,所以有时候本来错误语法,被认为也是可以正常被解析的;安全隐患
;无法意外的创建全局变量
严格模式会使引起静默失败(silently fail,注:不报错也没有任何效果
)的赋值操作抛出异常
严格模式下试图删除不可删除的属性
严格模式不允许函数参数有相同的名称
不允许0的八进制语法
在严格模式下,不允许使用with
在严格模式下,eval不再为上层引用变量
严格模式下,this绑定不会默认转成对象
"use strict"
// 1.不会意外创建全局变量
// function foo() {
// message = "Hello World"
// }
// foo()
// console.log(message)
// 2.发现静默错误
var obj = {
name: "why"
}
Object.defineProperty(obj, "name", {
writable: false,
configurable: false
})
// obj.name = "kobe"
console.log(obj.name)
// delete obj.name
console.log(obj)
// 3.参数名称不能相同
// function foo(num, num) {
// }
// 4.不能以0开头
// console.log(0o123)
// 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()