函数
定义函数
普通函数
// 普通函数
function sum(num1, num2) {
return num1 + num2;
}
// 函数表达式
let sum = function (num1, num2) {
return num1 + num2;
};
箭头函数
定义箭头函数
// 箭头函数
let arrowSum = (a, b) => {
return a + b;
};
prototype 属性
- 箭头函数定义的函数没有 prototype 属性。
省略
// 单行箭头函数可省略花括号
// 默认返回该行代码的值
let double = (x) => {
return 2 * x;
};
let triple = (x) => 3 * x;
函数名和函数参数
函数名
函数名的实质
// 函数名是指向 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 属性定义
// 一般 name 属性为其标识符名称
function foo() {}
console.log(foo.name); // foo
// 使用 function constructor 定义函数, name 属性值为 anonymous
console.log(new Function().name); // anonymous
// 当函数为 get/set 或者是 bind() 的一个实例时, name 属性值添加对应前缀.
let dog = {
years: 1,
get age() {
return this.years;
},
set age(newAge) {
this.years = newAge;
},
};
let propertyDescriptor = Object.getOwnPropertyDescriptor(dog, "age");
console.log(propertyDescriptor.get.name); // get age
console.log(propertyDescriptor.set.name); // set age
函数参数
函数传递的实质
- js 使用值传递传参;
- 函数传递时,参数数量和类型不用于函数定义相一致;
- ECMAScript 将函数参数作为一个名为 arguments 的 arrayLike 传递,但不关心 arguments 内部;
- 箭头函数无法访问 arguments 对象;
- arguments 的行为类似 Array,函数参数可通过 arguments[index] 访问和修改;
- 任何函数定义中没有被传递的函数参数赋值 undefined;
默认参数值
定义函数默认值
function makeKing(name = "Henry") {
return `King ${name} VIII`;
}
console.log(makeKing("Louis")); // 'King Louis VIII'
console.log(makeKing()); // 'King Henry VIII'
传递 undefined
- 当函数参数为 undefined 时;
- js 视其为没有传递函数参数;
function makeKing(name = "Henry", numerals = "VIII") {
return `King ${name} ${numerals}`;
}
console.log(makeKing(undefined, "VI")); // 'King Henry VI'
arguments
- arguments 无视函数默认值;
- 对于没有传递的函数参数,即使其有函数默认值
- 依旧视其为 undefined;
function makeKing(name = "Henry") {
name = "Louis";
return `King ${arguments[0]}`;
}
console.log(makeKing()); // 'King undefined'
console.log(makeKing("Louis")); // 'King Louis'
函数默认值的作用域
- 函数默认值等效于在函数内部顶端;
- 根据函数参数的位置顺序;
- 依次使用 let 定义;
参数收集
// 定义函数时, 使用 ... 操作符表示任意数量的参数
// Rest Parameter 只能放置于最后
function getSum(...values) {
return values.reduce((x, y) => x + y, 0);
}
console.log(getSum(1, 2, 3)); // 6
length 属性
- 函数预期接受的参数数量;
console.log((() => {}).length); // 0
console.log(((a) => {}).length); // 1
console.log(((a, b) => {}).length); // 2 etc.
使用函数
函数提升
- 具体机制详见 上下文
// JavaScript engine 获取所有 Function declarations 并提升至顶部
console.log(sum(10, 10));
function sum(num1, num2) {
return num1 + num2;
}
// Function Expressions 无函数提升机制
console.log(sum(10, 10)); // wrong
let sum = function (num1, num2) {
return num1 + num2;
};
匿名函数
- 箭头函数或函数表达式创建的函数又叫做 anonymous function;
- 因为其没有 name 属性,为空字符串;
- 若赋值给一个变量,name 属性为变量名;
console.log((() => 1).name); // ""
const fn = () => 1;
console.log(fn.name); // "fn"
函数重载
- js 不支持 overloading;
- 如果存在两个同名函数;
- 后一个函数覆盖前一个;
函数内部属性
callee 属性
// arguments 的 callee 属性指向 arguments 所在函数
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
let trueFactorial = factorial;
factorial = function () {
return 0;
};
console.log(trueFactorial(5)); // 120
console.log(factorial(5)); // 0
this 属性
// 指向函数被调用时被访问的对象 (object/构造函数/类实例)
// 全局普通函数非严格模式下 this 指向 window, 严格模式下 tis 为 undefined
window.color = "red";
let o = {
color: "blue",
};
function sayColor() {
console.log(this.color);
}
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'blue
// 指向箭头函数定义时所在的词法环境的 this
window.color = "red";
let o = {
color: "blue",
};
let sayColor = () => console.log(this.color);
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'red
caller 属性
// 指向该函数被调用时所在作用域的函数代码
// 当函数在 global scope 被调用时为 null
// 在 strict mode 下该属性报错
function outer() {
inner();
}
function inner() {
console.log(inner.caller);
}
outer(); // outer() 的源代码
new.target 属性
// 判断函数是否被 new 调用
function King() {
if (!new.target) {
throw 'King must be instantiated using "new"';
}
console.log('King instantiated using "new"');
}
new King(); // King instantiated using "new"
King(); // Error: King must be instantiated using "new"
递归
基础
经典递归函数
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
赋值问题
// factorial 内部调用 factorial
// factorial 已经为 null, 故报错
let anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(4)); // 报错
改进
// 使用 arguments.callee 属性
// 但严格模式无法访问
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
尾调用优化
优化机制
- 当外部函数的返回值是一个内部函数的返回值时;
- 销毁外部函数的栈帧;
- 无论调用多少次嵌套函数,只有一个栈帧;
- 无法监测尾调用优化是否起作用;
优化条件
- 严格模式执行;
- 外部函数的返回值是对尾调用函数的调用;
- 尾调用函数返回后不需要额外逻辑操作;
- 尾调用函数不是引用外部函数作用域中自由变量的闭包;
错误示例
"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 和 arguments
- 内部函数不能直接访问外部函数的 this 和 arguments;
- 但可以将其保存到闭包可以访问的变量中;
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
属性
- length:函数参数数量;
- name:函数名称;
调用函数并改变 this 指向
// 使用 apply() 或 call() 方法, 两者作用相同, 仅是参数形式不一样
// 第一个参数定义 this, 第二个参数定义函数参数
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers);
// 第一个参数定义 this, 其余参数定义函数参数
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price);
this.category = "food";
}
创建函数并改变 this 指向
// 第一个参数定义 this, 其余参数定义函数参数
function list(...args) {
return args;
}
const fn = list.bind(null, 1, 2, 3);
console.log(fn()); // [1, 2, 3]
打印函数
function sum(a, b) {
return a + b;
}
console.log(sum.toString());
// Expected output: "function sum(a, b) {
// return a + b;
// }"
最佳实践
手写 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