函数
定义函数
普通函数
// 普通函数
function sum(num1, num2) {
return num1 + num2;
}
// 函数表达式
let sum = function (num1, num2) {
return num1 + num2;
};
箭头函数
定义箭头函数
// 箭头函数
let arrowSum = (a, b) => {
return a + b;
};
// 单行箭头函数可省略花括号
// 默认返回该行代码的值
let triple = (x) => 3 * x;
prototype 属性
- 箭头函数定义的函数没有 prototype 属性;
函数名和函数参数
函数名
函数名的实质
// 函数名是指向 function 的一个指针
function sum(num1, num2) {
return num1 + num2;
}
console.log(sum(10, 10)); // 20
let anotherSum = sum;
console.log(anotherSum(10, 10)); // 20
sum = null;
console.log(anotherSum(10, 10)); // 20
name 属性
// 函数名通过一个只读的 name 属性定义
function foo() {}
console.log(foo.name); // foo
// 使用 function constructor 定义函数, name 属性值为 anonymous
console.log(new Function().name); // anonymous
匿名函数
- 箭头函数或函数表达式创建的函数又叫做 anonymous function;
- 因为其没有 name 属性, 为空字符串;
- 若赋值给一个变量, name 属性为变量名;
console.log((() => 1).name); // ""
const fn = () => 1;
console.log(fn.name); // "fn"
函数参数
函数传递的实质
- js 使用值传递传参;
- 函数传递时, 参数数量和类型不用于函数定义相一致;
- 任何函数定义中没有被传递的函数参数赋值 undefined;
定义函数默认值
function makeKing(name = "Henry") {
return `King ${name} VIII`;
}
console.log(makeKing("Louis")); // 'King Louis VIII'
console.log(makeKing()); // 'King Henry VIII'
函数默认值的作用域
- 函数默认值等效于在函数内部顶端;
- 根据函数参数的位置顺序;
- 依次使用 let 定义;
传递 undefined
- 当函数参数为 undefined 时, 视其为没有传递函数参数;
function makeKing(name = "Henry", numerals = "VIII") {
return `King ${name} ${numerals}`;
}
console.log(makeKing(undefined, "VI")); // 'King Henry VI'
参数收集
- 定义函数时, 使用 ... 操作符表示任意数量的参数;
- Rest Parameter 只能放置于最后;
function getSum(...values) {
return values.reduce((x, y) => x + y, 0);
}
console.log(getSum(1, 2, 3)); // 6
length 属性
- 函数预期接受的参数数量;
console.log(((a) => {}).length); // 1
使用函数
函数提升
- JavaScript engine 获取所有 Function declarations 并提升至顶部;
- Function Expressions 无函数提升机制;
console.log(sum(10, 10));
function sum(num1, num2) {
return num1 + num2;
}
console.log(sum(10, 10)); // wrong
let sum = function (num1, num2) {
return num1 + num2;
};
函数重载
- js 不支持 overloading;
- 如果存在两个同名函数;
- 后一个函数覆盖前一个;
递归
基础
经典递归函数
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
赋值问题
let anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(4)); // 报错
尾调用优化
优化机制
- 当外部函数的返回值是一个内部函数的返回值时;
- 销毁外部函数的栈帧;
- 无论调用多少次嵌套函数, 只有一个栈帧;
- 无法监测尾调用优化是否起作用;
优化条件
- 严格模式执行;
- 外部函数的返回值是对尾调用函数的调用;
- 尾调用函数返回后不需要额外逻辑操作;
- 尾调用函数不是引用外部函数作用域中自由变量的闭包;
错误示例
"use strict";
// 无优化: 尾调用没有返回
function outerFunction() {
innerFunction();
}
// 无优化: 尾调用没有直接返回
function outerFunction() {
let innerFunctionResult = innerFunction();
return innerFunctionResult;
}
// 无优化: 尾调用返回后必须转型为字符串
function outerFunction() {
return innerFunction().toString();
}
// 无优化: 尾调用是一个闭包
function outerFunction() {
let foo = "bar";
function innerFunction() {
return foo;
}
return innerFunction();
}
优化示例
// 优化前
function fib(n) {
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
// 优化后
("use strict");
function fib(n) {
return fibImpl(0, 1, n);
}
// 执行递归
function fibImpl(a, b, n) {
if (n === 0) {
return a;
}
return fibImpl(b, a + b, n - 1);
}
闭包
基础
闭包
- 引用了其他函数作用域中变量的函数;
闭包的缺陷
- 普通函数执行完毕后, 其变量对象销毁, 只保留全局变量对象;
- 闭包函数会将其外部函数的变量对象添加至自己的作用域链中;
- 闭包函数可以访问外部函数的所有变量;
- 外部函数执行完成后, 闭包函数依旧保留对其变量对象的引用, 在闭包函数销毁之前无法销毁;
// 创建比较函数
let compareNames = createComparisonFunction("name"); // createComparisonFunction 对应变量对象没有被销毁
let result = compareNames({ name: "Nicholas" }, { name: "Matt" });
compareNames = null; // 解除对 createComparisonFunction 的引用, 其变量对象没有被销毁
访问外部函数的属性
this
- 内部函数不能直接访问外部函数的 this;
- 但可以将其保存到闭包可以访问的变量中;
window.identity = "The Window";
let object = {
identity: "My Object",
getIdentityFunc() {
let that = this;
return function () {
return that.identity;
};
},
};
console.log(object.getIdentityFunc()()); // 'My Object'
应用场景
- 防抖节流;
- 实现私有变量和方法;
- 模块化;
- IIFE;
- 函数工厂;
- 各种回调函数, 定时器;
- 保存外部状态, 缓存函数结果;
进阶
函数的本质
- 函数在 js 中实质是一个 object, 是 Function 类型的实例, 具有自己的属性和方法;
函数二义性
- function 既可以作为普通函数, 又可以作为构造函数;
- 该行为即函数的二义性, 导致出现歧义和安全隐患;
- 使用不同的命名方式区分, 但只是君子协定;
- 故 ES6 引入 class 和箭头函数消除函数二义性;
立即调用的函数表达式 (IIFE)
语法格式
(function () {
// ...
})();
块级作用域
// 可通过 IIFE 模拟块级作用域
(function () {
for (var i = 0; i < count; i++) {
console.log(i);
}
})();
console.log(i); // 抛出错误
私有变量
私有变量
- 定义在函数或块中的变量;
特权方法
// 能够访问函数私有变量的公有方法
// 通过构造函数实现, 但每个实例都会创建一次
function MyObject() {
// 私有变量和私有函数
let privateVariable = 10;
function privateFunction() {
return false;
}
// 特权方法
this.publicMethod = function () {
privateVariable++;
return privateFunction();
};
}
const o = new MyObject();
// 通过匿名函数表达式实现
(function () {
// 私有变量和私有函数
let privateVariable = 10;
function privateFunction() {
return false;
}
// 构造函数, 不使用任何关键字, 绑定在全局执行上下文中;
MyObject = function () {};
// 公有和特权方法, 定义在原型中, 所有实例共享
MyObject.prototype.publicMethod = function () {
privateVariable++;
return privateFunction();
};
})();
const o = new MyObject();
// 通过基于单例对象的模块模式实现
let singleton = (function () {
// 私有变量和私有函数
let privateVariable = 10;
function privateFunction() {
return false;
}
// 特权/公有方法和属性
return {
publicProperty: true,
publicMethod() {
privateVariable++;
return privateFunction();
},
};
})();
常用 API
- Function(...args: string[]): Function 构造函数: 创建新函数;
- Function.prototype.apply(thisArg: any, argArray?: any[]): any: 调用函数, 指定 this 值和参数数组;
- Function.prototype.bind(thisArg: any, ...args: any[]): (...args: any[]) => any: 创建一个新函数, 其 this 值被绑定到指定值;
- Function.prototype.call(thisArg: any, ...args: any[]): any: 调用函数, 指定 this 值和参数列表;
- Function.prototype.toString(): string: 返回函数的源代码字符串;
- Function.prototype[Symbol.hasInstance](value: any): boolean: 判断对象是否为该构造函数的实例;
- Function.prototype.length: number: 返回函数的形参数量;
- Function.prototype.name: string: 返回函数的名称;
- Function.prototype.prototype: object: 用于原型继承的原型对象;
最佳实践
手写 new
- 创建一个对象 obj, prototype 设置为 fn 的 prototype;
- 使用 fn.apply, 将 this 指向 obj, 执行 fn;
- 若 fn 返回对象, 返回 fn 返回值, 否则返回 obj;
function myNew(fn, ...args) {
const obj = Object.create(fn.prototype);
const value = fn.apply(obj, args);
return value instanceof Object ? value : obj;
}
手写 call
- 定义于 Function 的 prototype 上;
- 设置 this 指向, 如果没有传入设置为 windows;
- 将调用函数设置为 context 的方法, 此处为 this, 因为是 fn.myCall();
- 删除 context 上的方法, 返回 fn 返回值;
Function.prototype.myCall = function (context, ...args) {
context = context || window;
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
};
function fun(age) {
console.log(this.name, age);
}
const _this = { name: "kxh" };
fun.myCall(_this, 21); // kxh 21
手写 apply
- 同手写 call;
Function.prototype.myApply = function (context, args) {
context = context || window;
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
};
function fun(age) {
console.log(this.name, age);
}
const _this = { name: "kxh" };
fun.myApply(_this, [21]); // kxh 21
手写 bind
- 类似手写 call;
Function.prototype.myBind = function (context) {
context = context || window;
const fn = this;
return (...args) => fn.apply(context, args);
};
function fun() {
console.log(this.name);
}
const _this = { name: "kxh" };
const newFun = fun.myBind(_this);
newFun(); // kxh