JavaScript
是支持 函数式编程的,在 JavaScript
中函数是 一等公民。函数可以作为 别的函数的参数、函数的返回值,赋值给变量或存储在数据结构中
JavaScript
中也可以称为定义函数,这个过程是对某些功能的封装过程,声明方式有以下几种:
function functionName() { // 函数体 }
const sum = function add(a, b) {}
const sum = () => console.log("sum")
const greet = function() {}
Function
构造函数:不推荐使用,const sum = new Function('a', 'b', 'console.log(a, b)')
()
即可:比如test()test``
调用,在react
中css-in-js
时使用了
raw
属性是字符串模板字面量解析过程中生成的一个只读属性,包含模板字面量的原始字符串形式const obj = {
name: "小小",
age: 18,
height: 188,
};
function foo(...args) {
console.log(args);
}
foo`hello world`;
foo`hello${obj.name},年龄${obj.age},height${obj.height}${obj}`;
函数调用时,按照函数定义的参数顺序,把希望在函数内部处理的数据通过参数传递到内部,进行需要的数据处理
parameter
):定义函数时,小括号中的参数是用来接收参数用的,在函数内部作为变量使用,可以写默认值即 function greet(name = 'Guest') { console.log(name) }
argument
):调用函数时,小括号中的参数,是用来把数据传递到函数内部用的arguments
在文章后面有详细讲解undefined
时会使用默认值,参数的默认值我们通常会将其放到最后length
的个数,默认值以及后面的参数都不计算在length
之内了function foo1(x, y = 20) {
console.log(x, y);
console.log(arguments.length);
}
// 和解构一起用
function foo2({ name, age } = { name: "小小", age: 18 }) {
console.log(name, age);
}
function foo3({ name = "小小", age = 20 } = {}) {
console.log(name, age);
}
foo1(10); // 10 20 这时参数arguments.length = 1
foo1(20, 40); // 20 40
foo1(30, 0); // 30 0
foo1(40, null); // 40 null
foo1(50, undefined); // 50 20
foo2(); // 小小 18
foo3(); // 小小 20
函数可以通过 return
语句返回一个值。
undefined
、this
以及 Promise
。return
语句时,会立即停止执行,并返回指定的值return
语句或者return
后面没有跟任何值,那么函数默认返回undefined
JavaScript
中函数也是一个对象,那么就可以有属性和方法 :
name
:一个函数的名称可以通过name
来访问,如果函数是匿名的,则返回空字符串length
:属性length
用于返回函数参数的个数,个数是不包括剩余参数的prototype
:每个函数都有一个 prototype
属性,这是用于创建对象实例时提供的原型对象,具体学习这篇文章:toString()
:返回函数的字符串表示bind()/call()/apply()
:这些方法可以用来调用函数、绑定 this
值和设置参数,具体在文章后面学习arguments
是一个类数组对象,包含传递给函数的所有参数
length
属性也可以通过索引来访问每个参数forEach
、map
、filter
等)rest
参数代替。arguments
转Array
:以便使用数组的一些特性
arguments
,添加到一个新数组中slice
函数的call/apply
方法Array.from
和[...arguments]
function foo(name) {
console.log(arguments, arguments[1]);
var arrArguments1 = [];
for (var i = 0; i < arguments.length; i++) {
arrArguments1.push(arguments[i]);
}
var arrArguments2 = [...arguments];
// Array.from() An iterable object to convert to an array.
var arrArguments3 = Array.from(arguments);
// [].slice() 这时slice函数中 this指向[]并进行截取
// 当我们使用apply调用slice函数时this指向了传的arguments,也就对arguments截取并返回
var arrArguments4 = [].slice.apply(arguments);
console.log(arrArguments1, arrArguments2, arrArguments3, arrArguments4);
}
foo("nihao", 18);
rest
)ES6
中引用了rest parameter
,可以将不定数量的参数放入到一个数组中:
function exampleFunction(...args) {
// args 是一个包含所有传入参数的数组
console.log(args);
}
exampleFunction(1, 2, 3); // 输出: [1, 2, 3]
function greet(greeting, ...names) {
return `${greeting}, ${names.join(' and ')}!`;
}
console.log(greet('Hello', 'Alice', 'Bob')); // 输出: Hello, Alice and Bob!
var multiply = (...args) => args.reduce((acc, num) => acc * num, 1);
console.log(multiply(2, 3, 4)); // 输出: 24
作用域(Scope
)表示一些标识符的作用有效范围
具体学习这篇文章:https://blog.csdn.net/qq_45730399/article/details/141104727?spm=1001.2014.3001.5501
this
指向具体学习这篇文章:https://blog.csdn.net/qq_45730399/article/details/140995147?spm=1001.2014.3001.5501
具体学习这篇文章:https://blog.csdn.net/qq_45730399/article/details/141055268?spm=1001.2014.3001.5501
with
函数with
语句 扩展一个语句的作用域链,不建议使用with
语句,因为它可能是混淆错误和兼容性问题的根源
var obj = {
name: "你好",
age: 18,
};
with (obj) {
console.log(name, age); // 你好 18
}
eval
函数内建函数 eval
允许执行一个代码字符串
eval
是一个特殊的函数,它可以将传入的字符串当做JavaScript
代码来运行eval
会将最后一句执行语句的结果,作为返回值var str = "var msg = 'hello';console.log(msg)";
eval(str);
console.log(msg); // hello
不建议在开发中使用eval
:
eval
代码的可读性非常的差(代码的可读性是高质量代码的重要原则)eval
是一个字符串,那么有可能在执行的过程中被刻意篡改,会造成被攻击的风险eval
的执行必须经过JavaScript
解释器,不能被JavaScript
引擎优化;在 JavaScript
中,apply
、call
和 bind
是 Function
原型对象的三个方法,用于改变函数 this
的值。它们的主要区别在于传递参数的方式以及是否立即调用函数
apply:functionName.apply(thisArg, [argsArray])
this
的值,后面是参数作为数组传递call:functionName.call(thisArg, arg1, arg2, ...)
this
的值,后面是参数单独传递bind:const boundFunction = functionName.bind(thisArg, arg1, arg2, ...)
this
值被永久绑定到 bind
的第一个参数,新函数的 this
值不能再被重新绑定bind
返回的新函数可以用作构造函数,其 this
会被设置为新创建的对象length
属性(参数个数)会去掉绑定的参数个数function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const boundGreet = greet.bind(null, 'Hello');
console.log(greet.length); // 输出: 2
console.log(boundGreet.length); // 输出: 1
Function.prototype.myFn = function (thisArg) {
console.log(this); // 当前调用的函数
// 如果thisArg为null或undefined,设置为window(或globalThis)
thisArg = thisArg === null || thisArg === undefined ? window : Object(thisArg);
thisArg.fn = this; // 将当前函数赋值给thisArg对象的fn属性
};
Function.prototype.myApply = function (thisArg, argsArray) {
console.log(this) // foo函数
this.myFn(thisArg);
// 使用argsArray数组调用函数
const result = thisArg.fn(...argsArray);
delete thisArg.fn; // 删除临时添加的fn属性
return result; // 返回结果
};
Function.prototype.myCall = function (thisArg, ...args) {
this.myFn(thisArg);
// 使用展开运算符将参数传递给函数
const result = thisArg.fn(...args);
delete thisArg.fn; // 删除临时添加的fn属性
return result; // 返回结果
};
Function.prototype.myBind = function (thisArg, ...args) {
// 这里用到上层的this,使用箭头函数
return (...newArgs) => {
// 调用myCall,将参数合并
return this.myCall(thisArg, ...args, ...newArgs);
};
};
// 测试
function foo(a, b) {
console.log(this.name, a, b);
}
const obj = { name: 'Alice' };
foo.myApply(obj, [1, 2]); // 输出: Alice 1 2
foo.myCall(obj, 1, 2); // 输出: Alice 1 2
const boundFoo = foo.myBind(obj, 1);
boundFoo(2); // 输出: Alice 1 2
纯函数(Pure Function
)是函数式编程中的一个重要概念。纯函数具有以下两个主要特性:
slice
就是一个纯函数,不会修改数组本身,而splice
函数不是一个纯函数作用和优势:
是ES6
之后增加的一种编写函数的方法,它比函数表达式要更加简洁
this
、arguments
和super
参数,没有原型prototype
属性new
一起来使用,会抛出错误)()
函数的参数;{}
函数的执行体,var add = (num1, num2) =>{ console.log(num1 + num2) }
编写优化:
()
可以省略{}
,省略{}
时不能写return
,但函数内部会将这行代码的返回值作为整个函数的返回值{}
时, 那么需要给这个对象加上()
var add = num => console.log(num);
var add1 = (num1, num2) => num1 + num2;
var add2 = () => ({ name: "add3", num1: 88, num2: 99 });
add(10); // 10
console.log(add(), add1(2, 3), add2());
// undefined 5 {name: 'add3', num1: 88, num2: 99}
IIFE
一个函数定义完后被立即执行,专业名字:Immediately-Invoked Function Expression
(IIFE
立即调用函数表达式)立即执行函数必须是一个表达式,表达式是任何可以进行计算并产生一个值的代码单元
()
包裹函数时,它会默认将函数作为表达式去解析,而不是函数声明(function(){
console.log('函数无需调用会立即执行')
})()
+function bar() {
console.log("bar");
}();
(function bar() {
console.log("bar");
}());
var btnEl = document.querySelector(".btn");
/*
1. 使用var声明时 i 会被提升值为undefined
2. 再执行for循环,i = 0,
3. 判断 0 < btnEl.children.length,得出0小于4
4. 执行循环体的代码btnEl.children[0].onclick = 函数
5. i++,i = 1,再重复执行for循环
当循环执行完时 i = 4,这时你点击按钮会找 i,
在函数作用域没找到会向外层即全局查找,找到 i=4
所以不管你点击第几个按钮都是4,那么怎么解决呐?
*/
for (var i = 0; i < btnEl.children.length; i++) {
// 事件处理函数是闭包,创建时会捕获其外部作用域(全局作用域)
btnEl.children[i].onclick = function () {
console.log(`第${i + 1}个按钮被点击了`);
};
}
/*
使用立即执行函数解决:
当执行到循环体代码时,立即执行函数会立即调用创建FEC,
形成自己的作用域,并定义传入的参数ii=0和事件,
当点击时执行事件函数,取到立即执行函数中的ii,不会取到全局的4
*/
for (var i = 0; i < btnEl.children.length; i++) {
(function (ii) {
// 事件处理函数是闭包,创建时会捕获其外部作用域(立即执行函数的函数作用域)
btnEl.children[ii].onclick = function () {
console.log(`第${ii + 1}个按钮被点击了`);
};
})(i);
}
高阶函数必须至少满足两个条件之一:
function foo(fn){
fn()
}
function bar(){
console.log("我是bar函数被调用")
}
foo(bar)
foo
这种函数我们可以称之为高阶函数
递归是一种重要的编程思想,它将一个复杂的任务,转化成可以重复执行的相同任务
案例:实现一个自己的幂函数pow()
function myPow(n, m) {
// console.log(Math.pow(2, 3)); // 8
// var sum = 1
// for (var i = 0; i < m; i++) {
// sum *= n;
// }
// console.log(sum) // 8
return m === 1 ? n : n * myPow(n, m - 1);
}
console.log(myPow(2, 3)); // 8
组合(Compose
)函数是在JavaScript
开发过程中一种对函数的使用技巧和模式,基本思想是将多个函数结合起来使其依次执行,将一个函数的输出作为下一个函数的输入
例如现在有三个函数f1, f2, f3
,现在希望输入一个x
依次调用这三个函数来得到y
,过程大概是这样:y = f1(f2(f3(x)))
,我们要做的就是实现一个函数可以传入多个回调函数做参数,内部自动执行f1(f2(f3(x)))
,并且返回一个函数,让其调用时再传入x
,实现组合函数如下:
function compose(...fns) {
if (!fns.length) return;
for (var i = 0; i < fns.length; i++) {
if (typeof fns[i] !== "function") {
throw new Error(`index position ${i + 1} must be function`);
}
}
return (...args) => {
/* // 方式一
if (fns.length === 1) {
return fns[0](...args);
} else {
return fns[0](
compose.apply(this, fns.slice(1, fns.length)).apply(this, args)
);
}
*/
/* // 方式二
return fns.length === 1
? fns[0](...args)
: fns[0](
compose.apply(this, fns.slice(1, fns.length)).apply(this, args)
);
*/
// 方式三: reduceRight从数组的右端(即从最后一个函数)开始,依次对每个函数进行处理
return fns.reduceRight((acc, fn) => [fn(...acc)], args)[0];
};
}
var addNum = (x, y) => x + y;
var multiply = (x) => x * 2;
var subtract = (x) => x - 2;
var combinedFn = compose(subtract, multiply, addNum);
console.log(combinedFn(10, 5)); // 输出 28
柯里化也是属于函数式编程里面一个非常重要的概念,它属于一种关于函数的高阶技术还被用于其他编程语言。
是指将一个接受多个参数的函数转换成一系列每次只接受一个参数的函数的技术,这个技术以逻辑学家Haskell Curry
命名
f(a, b, c)
转换为可调用的 c
// 未柯里化
function calcNum(x, y, z) {
return x + y * z;
}
// 进行柯里化后 f(x)(y)(z)
// function curryFn(x) {
// return function (y) {
// return function (z) {
// return x + y * z;
// };
// };
// }
// 简化代码
var curryFn = x => y => z => x + y * z;
console.log(calcNum(1, 2, 3), curryFn(1)(2)(3)); // 7 7
那么为什么需要有柯里化呢?
x + 2;y * 2;z ** 2
function curryFn(x) {
x = x + 2
return function (y) {
y = y * 2
return function (z) {
z = z ** 2
return x + y * z;
};
};
}
makeAdder
函数要求我们传入一个num
(并且如果我们需要的话,可以在这里对num
进行一些修改)num
了// 未柯里化
function calcAdd(num1, num2) {
return num1 + num2;
}
console.log(calcAdd(5, 3), calcAdd(5, 6), calcAdd(5, 10)); // 8 11 15
// 上面函数调用时我们可以看到有重复的部分,这时如果柯里化之后
function curryCalcAdd(num1) {
return (num2) => num1 + num2;
}
var curryCalcAdd5 = curryCalcAdd(5);
console.log(curryCalcAdd5(3), curryCalcAdd5(6), curryCalcAdd5(10)); // 8 11 15
实现一个自动柯里化工具函数,将一个或多个普通函数转换成柯里化函数
function curry(fn) {
// 返回一个内部的柯里化函数
return function curried(...args) {
// 检查传入的参数数量是否大于或等于原始函数的参数数量
if (args.length >= fn.length) {
// 如果参数足够,调用原始函数
return fn.apply(this, args);
} else {
// 如果参数不足,返回一个新的函数,继续收集参数
return function(...newArgs) {
// 递归调用 curried 函数,合并当前的参数和新传入的参数
return curried.apply(this, args.concat(newArgs));
};
}
};
}
// 原始的多参数函数
function add(x, y, z) {
return x + y + z;
}
// 创建柯里化版本的 add 函数
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 输出 6
console.log(curriedAdd(1, 2)(3)); // 输出 6
console.log(curriedAdd(1)(2, 3)); // 输出 6
console.log(curriedAdd(1, 2, 3)); // 输出 6
具体学习这篇文章:https://blog.csdn.net/qq_45730399/article/details/141143522?spm=1001.2014.3001.5501