函数定义
函数声明
function func1(a, b) {
return a + b;
}
函数表达式
实际上是一个匿名函数。函数存储在变量中,通过变量名来调用。
let func2 = function (a, b) {
return a * b;
};
自调用函数
函数声明并自调用,函数表达式可以 "自调用"。
(function () {
console.log("自调用函数");
})();
箭头函数ES6
(参数1, 参数2, …, 参数N) => { 函数声明 }
使用
- 当只有一个参数时,圆括号是可选的。
- 没有参数的函数应该写成一对圆括号。
- 函数声明只有return语句,可以省略return关键字。
注意
- 箭头函数没有自己的
this
,它会默认帮我们绑定外层this
的值。 - IE11 及更早 IE 版本不支持箭头函数。
函数参数
显式参数和隐式参数
函数显式参数在函数定义时列出。
函数隐式参数在函数调用时传递给函数真正的值。
arguments对象
arguments 对象包含了函数调用的参数数组。arguments表示的是传递到函数中的参数对象,它不是一个数组,它是一个类数组对象。
类数组对象
类数组对象
拥有若干索引属性和一个length属性的对象。
- 不能使用数组的方法。
- DOM中的HTMLCollection对象就是一个类数组对象。
let arrObj = {
0: 'name',
1: 'age',
2: 'sex',
length: 3
}
类数组对象转数组
//1.slice
Array.prototype.slice.call(arrObj);//[ 'name', 'age', 'sex' ]
//2.splice
Array.prototype.splice.call(arrObj, 0);//[ 'name', 'age', 'sex' ]
//3.ES6
Array.from(arrObj);//[ 'name', 'age', 'sex' ]
//4.apply
Array.prototype.concat.apply([], arrObj);//[ 'name', 'age', 'sex' ]
arguments对象
length属性
表示实参的长度
function foo(b, c, d){
console.log("实参的长度为:" + arguments.length)
}
console.log("形参的长度为:" + foo.length)
foo(1)
// 形参的长度为:3
// 实参的长度为:1
callee属性
通过它可以调用函数自身。
let data = [];
for (var i = 0; i < 3; i++) {
(data[i] = function () {
console.log(arguments.callee.i)
}).i = i;
}
data[0]();
data[1]();
data[2]();
// 0
// 1
// 2
arguments 的应用
arguments 和对应参数的绑定
非严格模式下:传入的参数,实参和 arguments 的值会共享,当没有传入时,实参与 arguments 值不会共享。
严格模式下,实参和 arguments 是不会共享的。
传递参数
将参数从一个函数传递到另一个函数
// 使用 apply 将 foo 的参数传递给 bar
function foo() {
bar.apply(this, arguments);
}
function bar(a, b, c) {
console.log(a, b, c);
}
foo(1, 2, 3)
ES6的 ... 运算符
function func(...arguments) {
console.log(arguments); // [1, 2, 3]
}
func(1, 2, 3);
函数调用
call()、apply()、bind()
- call()传入参数。
let myObject;
function myFunction(a, b) {
return a * b;
}
myObject = myFunction.call(myObject, 10, 2); // 返回 20
- apply()传入的是一个参数数组
let myObject;
function myFunction(a, b) {
return a * b;
}
let myArray = [10, 2]
myObject = myFunction.apply(myObject, myArray); // 返回 20
- bind() 与call()类似,但返回一个函数。
语法:
function.bind(thisArg[, arg1[, arg2[, ...]]])
参数:thisArg
:调用绑定函数时作为this
参数传递给目标函数的值。arg1, arg2, ...
:当目标函数被调用时,被预置入绑定函数的参数列表中的参数。
返回值:
返回一个原函数的拷贝,并拥有指定的this
值和初始参数。
// 模拟 bind
Function.prototype.bind1 = function () {
// 将参数拆解为数组
const args = [...arguments];
// 获取 this(数组第一项)
const t = args.shift();
// fn1.bind(...) 中的 fn1
const self = this;
// 返回一个函数
return function () {
return self.apply(t, args);
}
};
function fn1(a, b, c) {
console.log('this', this);
console.log(a, b, c);
return 'this is fn1';
}
const fn2 = fn1.bind1({x: 100}, 10, 20, 30);
console.log(fn2());
this 关键字
非箭头函数中,this指向函数执行时的当前对象。函数被调用时(即运行时)才会确定该函数内this的指向。
箭头函数中,箭头函数中的this在函数定义的时候就已经确定,它this指向的是它的外层作用域this的指向。
let a = 1;
let test = () => {
console.log(this.a)
};
let obj = {
a: 2,
test
};
obj.test();//undefined
四类场景
1."test()"形式
let a = 1;
function test() {
console.log(a);
}
test();//1
在非严格模式的情况下,this指向全局对象。严格模式下这个this其实是undefined的。
2."xxx.test()形式"
谁去调用这个函数的,这个函数中的this就绑定到谁身上,且this只对直接调者负责。
示例1:
let a = 1;
function test() {
console.log(this.a)
}
let obj = {
a: 2,
test
};
obj.test(); //2
示例2:
let a = 1;
function test() {
console.log(this.a)
}
let obj = {
a: 2,
test
};
let obj0 = {
a: 3,
obj
};
obj0.obj.test();//2
示例3:
let a = 1
function test () {
console.log(this.a)
};
var obj = {
a: 2,
test
};
var testCopy = obj.test;
testCopy(); //1
3.test.call(xxx)/test.apply(xxx)/test.bind()
this 指向xxx
let a = 1;
function test () {
console.log(this.a)
}
let obj = {
a: 2,
test
};
let testCopy = obj.test;
testCopy.call(obj);//2
4.new text()形式
构造函数里的this指的就是将要被new出来的新对象。
let a = 1;
function Test(a) {
this.a = a
}
let b = new Test(2);
console.log(b.a); //2
函数闭包
自由变量的值是在函数定义时候确定的。自由变量的查找是在函数定义的地方向上级作用域查找,不是在执行的地方!
概念
闭包就是能够读取其他函数内部变量的函数。可以理解成“定义在一个函数内部的函数,外部可以通过内部返回的函数访问内部函数的变量”。本质上,闭包是将函数内部和函数外部连接起来的桥梁。
优点:希望一个变量长期存储在内存中。避免全局变量的污染。
缺点:常驻内存,使用不当会很容易造成内存泄露。
应用场景:
- 函数作为参数被传递
//函数作为参数
function print(fn) {
let a = 200;
fn();
}
let a = 100;
function fn() {
console.log(a);
}
print(fn);//100
- 函数作为返回值被返回
//函数作为返回值
function create() {
let a = 100;
return function () {
console.log(a);
}
}
let fn = create();
let a = 200;
fn(); //100
函数柯里化
概念
在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
在数学和计算机科学中的柯里化函数,一次只能传递一个参数;
而在Javascript实际应用中的柯里化函数,可以传递一个或多个参数。
简单例子:
function add(a, b) {
return a + b;
}
function curryingAdd(a) {
return function(b) {
return a + b;
}
}
add(1, 2); // 3
curryingAdd(1)(2); // 3
用途
参数复用:柯里化实际是把简答的问题复杂化了,但是复杂化的同时,我们在使用函数时拥有了更加多的自由度。 而这里对于函数参数的自由处理,正是柯里化的核心所在。 柯里化本质上是降低通用性,提高适用性。来看一个例子:
function checkByRegExp(regExp,string) {
return regExp.test(string);
}
checkByRegExp(/^1\d{10}$/, '18642838455'); // 校验电话号码
checkByRegExp(/^1\d{10}$/, '13109840560'); // 校验电话号码
checkByRegExp(/^1\d{10}$/, '13204061212'); // 校验电话号码
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, '[email protected]'); // 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, '[email protected]'); // 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, '[email protected]'); // 校验邮箱
我们每次进行校验的时候都需要输入一串正则,再校验同一类型的数据时,相同的正则我们需要写多次,
这就导致我们在使用的时候效率低下,并且由于 checkByRegExp 函数本身是一个工具函数并没有任何意义,
一段时间后我们重新来看这些代码时,如果没有注释,我们必须通过检查正则的内容,
我们才能知道我们校验的是电话号码还是邮箱,还是别的什么。
此时,我们可以借助柯里化对 checkByRegExp 函数进行封装,以简化代码书写,提高代码可读性。
//进行柯里化
let _check = curry(checkByRegExp);
//生成工具函数,验证电话号码
let checkCellPhone = _check(/^1\d{10}$/);
//生成工具函数,验证邮箱
let checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
checkCellPhone('18642838455'); // 校验电话号码
checkCellPhone('13109840560'); // 校验电话号码
checkCellPhone('13204061212'); // 校验电话号码
checkEmail('[email protected]'); // 校验邮箱
checkEmail('[email protected]'); // 校验邮箱
checkEmail('[email protected]'); // 校验邮箱
常用的工具库 lodash 也提供了 curry 方法。
var abc = function(a, b, c) {
return [a, b, c];
};
var curried = _.curry(abc);
curried(1)(2)(3);
// => [1, 2, 3]
curried(1, 2)(3);
// => [1, 2, 3]
curried(1, 2, 3);
// => [1, 2, 3]