js 函数
# JS 函数类型
函数声明
函数表达式(匿名函数)
函数构造器创建的函数
自执行函数
ES6 箭头函数
惰性函数
高阶函数(柯里化、偏应用、组合、管道)
Generator 函数
# 1. 函数声明
function functionName(parameters) {
// 执行的代码
}
2
3
函数声明后不会立即执行,只是在初始化的时候会将函数声明提升(函数声明会被提升到作用域的最前面),会在我们需要的时候调用到。
用函数声明创建的函数可以在函数定义之前就进行调用;而用函数表达式创建的函数不能在函数被赋值之前进行调用。
用函数声明创建的函数可以在函数解析后调用(解析时进行等逻辑处理)
用函数表达式创建的函数是在运行时进行赋值,且要等到表达式赋值完成后才能调用。
# 2. 函数表达式(匿名函数)
var x = function(a, b) {
return a * b;
};
var z = x(4, 3);
2
3
4
以上函数实际上是一个 匿名函数 (函数没有名称)。
函数存储在变量中,不需要函数名称,通常通过变量名来调用。
# 3. 函数构造器创建的函数
通过内置的 JavaScript 函数构造器(Function())定义。
var myFunction = new Function("a", "b", "return a * b");
var x = myFunction(4, 3);
2
构造函数的调用会创建一个新的对象。新对象会继承构造函数的属性和方法。
构造函数中 this 关键字没有任何的值。 this 的值在函数调用实例化对象(new object)时创建。
使用 new Function 构造函数创建函数有 3 个注意点:
(1) 在 JS 运行的时候可以动态创建 Function;
(2) Function()构造函数创建的函数的执行效率比较低;
(3) Function()构造函数创建的函数执行作用域是全局的;
# 4. 自执行函数
概念:声明一个匿名函数,马上调用这个匿名函数就是自执行函数
主要作用:隔离作用域,这个作用域里面的变量,外面访问不到(即避免「变量污染」)。
# 三种写法
// 第一种
(function(参数) {
// 函数逻辑
})(给参数传的值);
2
3
4
// 第二种
(function (参数) { 函数方法 } ( 给参数传的值 ))
// 第三种 第三种!可以换作其他运算符或者void。
!(function() {
alert("布尔运算符");
})();
2
3
4
事实上,使用括号包裹定义函数体,解析器将会以函数表达式的方式去调用定义函数。换句话说,任何能将函数变为一个函数表达式的做法,都可以使解析器正确的调用函数。
比较常见的做法为在 function 关键字前面添加!、+、-、~
# 5. ES6 箭头函数
参考:https://www.cnblogs.com/mengff/p/9656486.html (opens new window)
- 箭头函数没有自己的 this 对象,箭头函数 this 为父作用域的 this
不是调用时的 this,任何方法都改变不了,包括 call,apply,bind。
箭头函数通过 call 和 apply 调用,不会改变 this 指向,只会传入参数
普通函数的 this 指向调用它的那个对象。
箭头函数不能作为构造函数,不能使用 new
箭头函数没有 arguments,caller,callee
箭头函数没有原型属性
箭头函数不能作为 Generator 函数,不能使用 yield 关键字
箭头函数返回对象时,要加一个小括号
var func = () => ({ foo: 1 }); //正确
var func = () => {
foo: 1;
}; //错误
2
3
4
- 箭头函数在 ES6 class 中声明的方法为实例方法,不是原型方法
//demo2
class Super {
sayName = () => {
//do some thing here
};
}
//通过Super.prototype访问不到sayName方法,该方法没有定义在prototype上
var a = new Super();
var b = new Super();
a.sayName === b.sayName; //false
//实例化之后的对象各自拥有自己的sayName方法,比demo1需要更多的内存空间
2
3
4
5
6
7
8
9
10
11
因此,在 class 中尽量少用箭头函数声明方法。
- 多重箭头函数就是一个高阶函数,相当于内嵌函数
const add = (x) => (y) => y + x;
//相当于
function add(x) {
return function(y) {
return y + x;
};
}
2
3
4
5
6
7
箭头函数为什么不能作为构造函数?
构造函数需要 this 这个对象,箭头函数没有自己的 this 对象,箭头函数 this 为父作用域的 this
构造函数需要用到原型属性,箭头函数没有原型属性
# 6. 惰性函数
惰性函数表示函数执行的分支在函数第一次带调用的时候执行,在第一次调用过程中,函数会被覆盖成一个新的适合执行的函数,之后的函数调用不会对原函数的分支进行调用。
# 背景
在开发过程中,有时候需要对浏览器环境进行检测,比如封装一个 AJAX 函数的时候需要写一个函数进行检测,但是常规的写法有点累赘了,会检测很多遍,但是浏览器环境是固定不变的,检测只需要检测一遍.这样就会造成每次调用这个函数的时候浏览器都会浪费太多时间去再检测一次.这时候就应该用到惰性函数来解决这个问题
# 应用场景:
ajax 兼容浏览器
function createXHR() {
var xhr;
if (typeof XMLHttpRequest != "undefined") {
xhr = new XMLHttpRequest();
createXHR = function() {
return new XMLHttpRequest();
};
} else {
try {
xhr = new ActiveXObject("Msxml2.XMLHTTP");
createXHR = function() {
return new ActiveXObject("Msxml2.XMLHTTP");
};
} catch (e) {
try {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
createXHR = function() {
return new ActiveXObject("Microsoft.XMLHTTP");
};
} catch (e) {
createXHR = function() {
return null;
};
}
}
}
return xhr;
}
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
# 7. 高阶函数
参考链接:https://www.cnblogs.com/chenwenhao/p/11668894.html (opens new window)
高阶函数就是接受函数作为参数或者返回函数作为输出的函数。
高阶函数思想:以变量作用域作为根基,以闭包为工具来实现各种功能
高阶函数的核心是闭包,利用闭包缓存一些未来会用到的变量,可以实现柯里化、偏应用...
# (1)函数作为参数传入
函数作为参数传入最常见的就是回调函数。
Array.prototype.map,Array.prototype.filter,Array.prototype.reduce 和 Array.prototype.sort 是 JavaScript 中内置的高阶函数。它们接受一个函数作为参数,并应用这个函数到列表的每一个元素。
# (2)函数作为返回值输出
function isType (type) {
return function (obj) {
return Object.prototype.toString.call(obj) === `[object ${type}]
}
}
2
3
4
5
# 8. 柯里化
柯里化是一种将使用多个参数的函数转换成一系列使用一个参数的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
柯里化的 3 个常见作用:
- 参数复用 – 当在多次调用同一个函数,并且传递的参数绝大多数是相同的,那么该函数可能是一个很好的柯里化候选
- 提前返回 – 多次调用多次内部判断,可以直接把第一次判断的结果返回外部接收
- 延迟计算/运行 – 避免重复的去执行程序,等真正需要结果的时候再执行
柯里化多参数封装:
// 支持多参数传递
function currying(fn, ...args) {
return function() {
let rest = [...args, ...arguments];
if (rest.length < fn.length) {
return currying.call(this, fn, ...rest);
} else {
return fn.apply(this, rest);
}
};
}
// 柯里化函数
const curry = (fn) => {
if (typeof fn !== "function") {
throw Error("No function provided");
}
return function curriedFn(...args) {
if (fn.length > args.length) {
// 未达到触发条件,继续收集参数
return function() {
return curriedFn.apply(null, args.concat([].slice.call(arguments)));
};
}
return fn.apply(null, args);
};
};
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
经典柯里化面试题
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() {
//建立args,利用闭包特性,不断保存arguments
var args = [].slice.call(arguments);
//方法一,新建_add函数实现柯里化
var _add = function() {
if (arguments.length === 0) {
//参数为空,对args执行加法
return args.reduce(function(a, b) {
return a + b;
});
} else {
// 否则,保存参数到args,返回一个函数
[].push.apply(args, arguments);
return _add;
}
};
//返回_add函数
return _add;
}
add(1)(2)(3); // 6
add(1, 2, 3)(4); // 10
add(1)(2)(3)(4)(5); // 15
add(2, 6)(1); // 9
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
29
# 9. 构造函数
概念:constructor 返回创建实例对象时构造函数的引用。此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。
// 构造函数
function Parent(age) {
this.age = age;
}
var p = new Parent(50);
p.constructor === Parent; // true
p.constructor === Object; // false
Parent.prototype.constructor === Parent; // true
// 普通函数
function parent3(age) {
return {
age: age,
};
}
var p2 = parent3(50);
p2.constructor === Object; // true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
constructor 值只读吗?
对于引用类型来说 constructor 属性值是可以修改的,但是对于基本类型来说是只读的。
箭头函数为什么不能作为构造函数?
构造函数需要 this 这个对象,箭头函数没有自己的 this 对象,箭头函数 this 为父作用域的 this
构造函数需要用到原型属性,箭头函数没有原型属性
参考:https://github.com/habc0807/fe-interview/issues/16 (opens new window)
匿名函数能作为构造函数么?
可以,匿名函数因为被赋值给了一个变量(函数表达式),所以可以被看作是普通函数
Symbol 是构造函数吗?
Symbol 是基本数据类型,但作为构造函数来说它并不完整,因为它不支持语法 new Symbol(),Chrome 认为其不是构造函数,如果要生成实例直接使用 Symbol() 即可
# JS 函数调用
- 函数名()
//函数声明
function fn() {
console.log(1);
}
//函数的调用
fn();
2
3
4
5
6
7
- 在事件中调用,直接写函数名,不使用括号
//函数声明
function fn() {
console.log(1);
}
//函数在事件中的调用
document.onclick = fn;
2
3
4
5
6
7
# JS 函数相关问题
# 普通函数和构造函数的区别
- 写法不同
构造函数一般首字母大写
- 调用方式不同
普通函数的调用方式:直接调用 person();
构造函数的调用方式:需要使用 new 关键字来调用 new Person();
- this 指向不同
在普通函数内部,this 指向的是 window 全局对象
在构造函数内部,this 指向的是构造出来的新对象
- 返回值不同
普通函数如果没有 return 值的话,返回 undefined
构造函数会默认返回 this,也就是新的实例对象
# 箭头函数和普通函数区别
箭头函数是匿名函数,不能作为构造函数,不能使用 new
箭头函数不绑定 this,会捕获其所在的上下文的 this 值,作为自己的 this 值
箭头函数没有原型属性
箭头函数不绑定 arguments,取而代之用 rest 参数...解决
# 函数的 length
function 的 length,就是第一个具有默认值之前的参数个数
123['toString'].length + 123 = 124
function fn1(name) {}
function fn2(name = "林三心") {}
function fn3(name, age = 22) {}
function fn4(name, age = 22, gender) {}
function fn5(name = "林三心", age, gender) {}
console.log(fn1.length); // 1
console.log(fn2.length); // 0
console.log(fn3.length); // 1
console.log(fn4.length); // 1
console.log(fn5.length); // 0
// 链接:https://juejin.cn/post/7003369591967596552
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17