Skip to content

函数 #6

Open
Open
@webVueBlog

Description

@webVueBlog
  1. 函数表达式、函数声明及箭头函数
  2. 默认参数及扩展操作符
  3. 使用函数实现递归
  4. 使用闭包实现私有变量

每个函数都是Function类型的实例 函数名就是指向函数对象的指针

函数通常以函数声明的方式定义

function sum (num1, num2) {
 return num1 + num2;
}

注意函数定义最后没有加分号

函数表达式

let sum = function(num1, num2) {
 return num1 + num2;
};

注意这里的函数末尾是有分号的,与任何变量初始化语句一样

箭头函数

let sum = (num1, num2) => {
 return num1 + num2;
}; 

使用 Function 构造函数。这个构造函数接收任意多个字符串参数,最后一个参数始终会被当成函数体,而之前的参数都是新函数的参数。

let sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐

这段代码会被解释两次:第一次是将它当作常规ECMAScript 代码,第二次是解释传给构造函数的字符串。这显然会影响性能。

箭头函数

let arrowSum = (a, b) => {
 return a + b;
};
let functionExpressionSum = function(a, b) {
 return a + b;
};
console.log(arrowSum(5, 8)); // 13
console.log(functionExpressionSum(5, 8)); // 13

简洁

let ints = [1, 2, 3];
console.log(ints.map(function(i) { return i + 1; })); // [2, 3, 4]
console.log(ints.map((i) => { return i + 1 })); // [2, 3, 4] 

如果只有一个参数,那也可以不用括号。只有没有参数,或者多个参数的情况下,才需要使用括号:

// 以下两种写法都有效
let double = (x) => { return 2 * x; };
let triple = x => { return 3 * x; };
// 没有参数需要括号
let getRandom = () => { return Math.random(); };
// 多个参数需要括号
let sum = (a, b) => { return a + b; };
// 无效的写法:
let multiply = a, b => { return a * b; }; 

箭头函数不能使用 arguments、super 和new.target,也不能用作构造函数。此外,箭头函数也没有 prototype 属性。

函数名

函数名就是指向函数的指针 一个函数可以有多个名称

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

ECMAScript 6 的所有函数对象都会暴露一个只读的 name 属性

这个属性中保存的就是一个函数标识符,或者说是一个字符串化的变量名

即使函数没有名称,也会如实显示成空字符串。如果它是使用 Function 构造函数创建的,则会标识成"anonymous":

function foo() {}
let bar = function() {};
let baz = () => {};
console.log(foo.name); // foo
console.log(bar.name); // bar
console.log(baz.name); // baz
console.log((() => {}).name); //(空字符串)
console.log((new Function()).name); // anonymous

如果函数是一个获取函数、设置函数,或者使用 bind()实例化,那么标识符前面会加上一个前缀

function foo() {}
console.log(foo.bind(null).name); // bound foo
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

理解参数

ECMAScript 函数的参数在内部表现为一个数组 访问 arguments 对象,从中取得传进来的每个参数值

arguments 对象是一个类数组对象(但不是 Array 的实例),因此可以使用中括号语法访问其中的元素(第一个参数是 arguments[0],第二个参数是 arguments[1])。而要确定传进来多少个参数,可以访问 arguments.length 属性

function howManyArgs() {
 console.log(arguments.length);
}
howManyArgs("string", 45); // 2
howManyArgs(); // 0
howManyArgs(12); // 1

箭头函数中的参数

传给函数的参数将不能使用 arguments 关键字访问

function foo() {
 console.log(arguments[0]);
}
foo(5); // 5
let bar = () => {
 console.log(arguments[0]);
};
bar(5); // ReferenceError: arguments is not defined
function foo() {
 let bar = () => {
 console.log(arguments[0]); // 5
 };
 bar();
}
foo(5);

注意 ECMAScript 中的所有参数都按值传递的。不可能按引用传递参数。如果把对象作为参数传递,那么传递的值就是这个对象的引用。

没有重载

没有函数签名,自然也就没有重载

如果在 ECMAScript 中定义了两个同名函数,则后定义的会覆盖先定义的。

可以通过检查参数的类型和数量,然后分别执行不同的逻辑来模拟函数重载。

默认参数值

function makeKing(name) {
 name = (typeof name !== 'undefined') ? name : 'Henry';
 return `King ${name} VIII`;
}
console.log(makeKing()); // 'King Henry VIII'
console.log(makeKing('Louis')); // 'King Louis VIII' 
function makeKing(name = 'Henry') {
 return `King ${name} VIII`;
}
console.log(makeKing('Louis')); // 'King Louis VIII'
console.log(makeKing()); // 'King Henry VIII' 
function makeKing(name = 'Henry', numerals = 'VIII') {
 return `King ${name} ${numerals}`;
}
console.log(makeKing()); // 'King Henry VIII'
console.log(makeKing('Louis')); // 'King Louis VIII'
console.log(makeKing(undefined, 'VI')); // 'King Henry VI' 

在使用默认参数时,arguments 对象的值不反映参数的默认值,只反映传给函数的参数。

function makeKing(name = 'Henry') {
 name = 'Louis';
 return `King ${arguments[0]}`;
}
console.log(makeKing()); // 'King undefined'
console.log(makeKing('Louis')); // 'King Louis'
let romanNumerals = ['I', 'II', 'III', 'IV', 'V', 'VI'];
let ordinality = 0;
function getNumerals() {
 // 每次调用后递增
 return romanNumerals[ordinality++];
}
function makeKing(name = 'Henry', numerals = getNumerals()) {
 return `King ${name} ${numerals}`;
}
console.log(makeKing()); // 'King Henry I'
console.log(makeKing('Louis', 'XVI')); // 'King Louis XVI'
console.log(makeKing()); // 'King Henry II'
console.log(makeKing()); // 'King Henry III' 
let makeKing = (name = 'Henry') => `King ${name}`;
console.log(makeKing()); // King Henry 

默认参数作用域与暂时性死区

function makeKing(name = 'Henry', numerals = 'VIII') {
 return `King ${name} ${numerals}`;
}
console.log(makeKing()); // King Henry VIII
function makeKing() {
 let name = 'Henry';
 let numerals = 'VIII';
 return `King ${name} ${numerals}`;
}
function makeKing(name = 'Henry', numerals = name) {
 return `King ${name} ${numerals}`;
}
console.log(makeKing()); // King Henry Henry

参数初始化顺序遵循“暂时性死区”规则

// 调用时不传第一个参数会报错
function makeKing(name = numerals, numerals = 'VIII') {
 return `King ${name} ${numerals}`;
}
// 调用时不传第二个参数会报错
function makeKing(name = 'Henry', numerals = defaultNumeral) {
 let defaultNumeral = 'VIII';
 return `King ${name} ${numerals}`;
} 

参数扩展与收集

扩展操作符最有用的场景就是函数定义中的参数列表

扩展操作符既可以用于调用函数时传参,也可以用于定义函数参数

扩展参数

arguments 对象只是消费扩展操作符的一种方式

收集参数

在构思函数定义时,可以使用扩展操作符把不同长度的独立参数组合为一个数组

// 不可以
function getProduct(...values, lastValue) {}
// 可以
function ignoreFirst(firstValue, ...values) {
 console.log(values);
}
ignoreFirst(); // []
ignoreFirst(1); // []
ignoreFirst(1,2); // [2]
ignoreFirst(1,2,3); // [2, 3]
let getSum = (...values) => {
 return values.reduce((x, y) => x + y, 0);
}
console.log(getSum(1,2,3)); // 6
function getSum(...values) {
 console.log(arguments.length); // 3
 console.log(arguments); // [1, 2, 3]
 console.log(values); // [1, 2, 3]
}
console.log(getSum(1,2,3));

函数声明与函数表达式

// 没问题
console.log(sum(10, 10));
function sum(num1, num2) {
 return num1 + num2;
}
// 会出错
console.log(sum(10, 10));
let sum = function(num1, num2) {
 return num1 + num2;
};

// 会出错
console.log(sum(10, 10));
var sum = function(num1, num2) {
 return num1 + num2;
};

函数作为值

因为函数名在 ECMAScript 中就是变量,所以函数可以用在任何可以使用变量的地方。这意味着不仅可以把函数作为参数传给另一个函数,而且还可以在一个函数中返回另一个函数。

函数内部

函数内部存在两个特殊的对象:arguments 和 this

ECMAScript 6 新增了 new.target 属性。

arguments

arguments 对象 它是一个类数组对象,包含调用函数时传入的所有参数。

arguments 对象其实还有一个 callee 属性,是一个指向 arguments 对象所在函数的指针。

经典的阶乘函数

function factorial(num) {
 if (num <= 1) {
 return 1;
 } else {
 return num * factorial(num - 1);
 }
}

使用 arguments.callee

function factorial(num) {
 if (num <= 1) {
 return 1;
 } else {
 return num * arguments.callee(num - 1);
 }
}

this

它在标准函数和箭头函数中有不同的行为。

在标准函数中,this 引用的是把函数当成方法调用的上下文对象,这时候通常称其为 this 值(在网页的全局上下文中调用函数时,this 指向 windows)

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' 

箭头函数中的 this 会保留定义该函数时的上下文

function King() {
 this.royaltyName = 'Henry';
 // this 引用 King 的实例
 setTimeout(() => console.log(this.royaltyName), 1000);
}

function Queen() {
 this.royaltyName = 'Elizabeth';
 // this 引用 window 对象
 setTimeout(function() { console.log(this.royaltyName); }, 1000);
}
new King(); // Henry
new Queen(); // undefined

注意 函数名只是保存指针的变量。因此全局定义的 sayColor()函数和 o.sayColor()是同一个函数,只不过执行的上下文不同。

caller

ECMAScript 5 也会给函数对象上添加一个属性:caller。

这个属性引用的是调用当前函数的函数,或者如果是在全局作用域中调用的则为 null。

function outer() {
 inner();
}
function inner() {
 console.log(inner.caller);
}
outer();

VM201:5 ƒ outer() {
 inner();
}

会显示 outer()函数的源代码

如果要降低耦合度,则可以通过 arguments.callee.caller 来引用同样的值

function outer() {
 inner();
}
function inner() {
 console.log(arguments.callee.caller);
}
outer(); 

严格模式下还有一个限制,就是不能给函数的 caller 属性赋值,否则会导致错误。

new.target

ECMAScript 6 新增了检测函数是否使用 new 关键字调用的 new.target 属性

如果函数是正常调用的,则 new.target 的值是 undefined;如果是使用 new 关键字调用的,则 new.target 将引用被调用的构造函数。

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"

函数属性与方法

每个函数都有两个属性:length 和 prototype

ECMAScript 5中,prototype 属性是不可枚举的,因此使用 for-in 循环不会返回这个属性。

函数还有两个方法:apply()和 call()。这两个方法都会以指定的 this 值来调用函数,即会设置调用函数时函数体内 this 对象的值

apply()方法接收两个参数:函数内 this 的值和一个参数数组。第二个参数可以是 Array 的实例,但也可以是 arguments 对象。

function sum(num1, num2) {
 return num1 + num2;
}
function callSum1(num1, num2) {
 return sum.apply(this, arguments); // 传入 arguments 对象
}

function callSum2(num1, num2) {
 return sum.apply(this, [num1, num2]); // 传入数组
}
console.log(callSum1(10, 10)); // 20
console.log(callSum2(10, 10)); // 20

call()方法

function sum(num1, num2) {
 return num1 + num2;
}
function callSum(num1, num2) {
 return sum.call(this, num1, num2);
}
console.log(callSum(10, 10)); // 20 

apply()和 call()真正强大的地方 控制函数调用上下文即函数体内 this值的能力

window.color = 'red';
let o = {
 color: 'blue'
};
function sayColor() {
 console.log(this.color);
}
sayColor(); // red
sayColor.call(this); // red
sayColor.call(window); // red
sayColor.call(o); // blue

使用 call()或 apply()的好处是可以将任意对象设置为任意函数的作用域,

ECMAScript 5 定义了一个新方法:bind()。bind()方法会创建一个新的函数实例 其 this 值会被绑定到传给 bind()的对象

window.color = 'red';
var o = {
 color: 'blue'
};
function sayColor() {
 console.log(this.color);
}
let objectSayColor = sayColor.bind(o);
objectSayColor(); // blue

函数表达式

定义函数有两种方式:函数声明和函数表达式。

function functionName(arg0, arg1, arg2) {
 // 函数体
}

函数声明的关键特点是函数声明提升,即函数声明会在代码执行之前获得定义。

sayHi();
function sayHi() {
 console.log("Hi!");
}

JavaScript 引擎会先读取函数声明,然后再执行代码

第二种创建函数的方式就是函数表达式。

let functionName = function(arg0, arg1, arg2) {
 // 函数体
};

匿名函数(anonymous funtion)

sayHi(); // Error! function doesn't exist yet
let sayHi = function() {
 console.log("Hi!");
}; 

递归

function factorial(num) {
 if (num <= 1) {
 return 1;
 } else {
 return num * factorial(num - 1);
 }
}

arguments.callee 就是一个指向正在执行的函数的指针

function factorial(num) {
 if (num <= 1) {
 return 1;
 } else {
 return num * arguments.callee(num - 1);
 }
}

arguments.callee 是引用当前函数的首选。

使用命名函数表达式(named function expression)

const factorial = (function f(num) {
 if (num <= 1) {
 return 1;
 } else {
 return num * f(num - 1);
 }
});

命名函数表达式 f()

尾调用优化

ECMAScript 6 规范新增了一项内存管理优化机制,让 JavaScript 引擎在满足条件时可以重用栈帧。

“尾调用”,即外部函数的返回值是一个内部函数的返回值。

function outerFunction() {
 return innerFunction(); // 尾调用
}

(1) 执行到 outerFunction 函数体,第一个栈帧被推到栈上。

(2) 执行 outerFunction 函数体,到 return 语句。计算返回值必须先计算 innerFunction。

(3) 执行到 innerFunction 函数体,第二个栈帧被推到栈上。

(4) 执行 innerFunction 函数体,计算其返回值。

(5) 将返回值传回 outerFunction,然后 outerFunction 再返回值。

(6) 将栈帧弹出栈外。

在 ES6 优化之后

(1) 执行到 outerFunction 函数体,第一个栈帧被推到栈上。

(2) 执行 outerFunction 函数体,到达 return 语句。为求值返回语句,必须先求值 innerFunction。

(3) 引擎发现把第一个栈帧弹出栈外也没问题,因为 innerFunction 的返回值也是 outerFunction
的返回值。

(4) 弹出 outerFunction 的栈帧。

(5) 执行到 innerFunction 函数体,栈帧被推到栈上。

(6) 执行 innerFunction 函数体,计算其返回值。

(7) 将 innerFunction 的栈帧弹出栈外。

第一种情况下每多调用一次嵌套函数,就会多增加一个栈帧。而第二种情况下无论调用多少次嵌套函数,都只有一个栈帧。这就是 ES6 尾调用优化的关键

尾调用优化的条件

尾调用优化的条件就是确定外部栈帧真的没有必要存在了

  1. 代码在严格模式下执行;
  2. 外部函数的返回值是对尾调用函数的调用;
  3. 尾调用函数返回后不需要执行额外的逻辑;
  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();
} 

符合尾调用优化条件的

"use strict";
// 有优化:栈帧销毁前执行参数计算
function outerFunction(a, b) {
 return innerFunction(a + b);
}
// 有优化:初始返回值不涉及栈帧
function outerFunction(a, b) {
 if (a < b) {
 return a;
 }
 return innerFunction(a + b);
}
// 有优化:两个内部函数都在尾部
function outerFunction(condition) {
 return condition ? innerFunctionA() : innerFunctionB();
} 

优化在递归场景下的效果是最明显的,因为递归代码最容易在栈内存中迅速产生大量栈帧。

尾调用优化的代码

通过递归计算斐波纳契数列的函数:

function fib(n) {
 if (n < 2) {
 return n;
 }
 return fib(n - 1) + fib(n - 2);
}
console.log(fib(0)); // 0
console.log(fib(1)); // 1
console.log(fib(2)); // 1
console.log(fib(3)); // 2
console.log(fib(4)); // 3
console.log(fib(5)); // 5
console.log(fib(6)); // 8

fib(n)的栈帧数的内存复杂度是 O(2^n)

"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);
} 

闭包

闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。

function createComparisonFunction(propertyName) {
 return function(object1, object2) {
 let value1 = object1[propertyName];
 let value2 = object2[propertyName];
 if (value1 < value2) {
 return -1;
 } else if (value1 > value2) {
 return 1;
 } else {
 return 0;
 }
 };
}

函数执行时,每个执行上下文中都会有一个包含其中变量的对象。全局上下文中的叫变量对象,它会在代码执行期间始终存在。而函数局部上下文中的叫活动对象,只在函数执行期间存在。

function compare(value1, value2) {
 if (value1 < value2) {
 return -1;
 } else if (value1 > value2) {
 return 1;
 } else {
 return 0;
 }
}
let result = compare(5, 10);

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'

内存泄漏

function assignHandler() {
 let element = document.getElementById('someElement');
 element.onclick = () => console.log(element.id);
}

匿名函数引用着 assignHandler()的活动对象 阻止了对element 的引用计数归零 只要这个匿名函数存在,element 的引用计数就至少等于 1。

function assignHandler() {
 let element = document.getElementById('someElement');
 let id = element.id;
 element.onclick = () => console.log(id);
 element = null;
} 

立即调用的函数表达式

立即调用的匿名函数又被称作立即调用的函数表达式(IIFE,Immediately Invoked Function Expression)。

(function() {
 // 块级作用域
})();
// IIFE
(function () {
 for (var i = 0; i < count; i++) {
 console.log(i);
 }
})();
console.log(i); // 抛出错误

在执行到 IIFE 外部的 console.log()时会出错,因为它访问的变量是在 IIFE 内部定义的,在外部访问不到。

// 内嵌块级作用域
{
 let i;
 for (i = 0; i < count; i++) {
 console.log(i);
 }
}
console.log(i); // 抛出错误
// 循环的块级作用域
for (let i = 0; i < count; i++) {
 console.log(i);
}
console.log(i); // 抛出错误
let divs = document.querySelectorAll('div');
// 达不到目的!
for (var i = 0; i < divs.length; ++i) {
 divs[i].addEventListener('click', function() {
 console.log(i);
 });
}
let divs = document.querySelectorAll('div');
for (var i = 0; i < divs.length; ++i) {
 divs[i].addEventListener('click', (function(frozenCounter) {
 return function() {
 console.log(frozenCounter);
 };
 })(i));
} 
let divs = document.querySelectorAll('div');
for (let i = 0; i < divs.length; ++i) {
 divs[i].addEventListener('click', function() {
 console.log(i);
 });
} 

使用 ECMAScript 块级作用域变量

let divs = document.querySelectorAll('div');
for (let i = 0; i < divs.length; ++i) {
 divs[i].addEventListener('click', function() {
console.log(i);
 });
} 

私有变量

私有变量包括函数参数、局部变量,以及函数内部定义的其他函数。

特权方法(privileged method)是能够访问函数私有变量(及私有函数)的公有方法。在对象上有两种方式创建特权方法。第一种是在构造函数中实现

function MyObject() {
 // 私有变量和私有函数
 let privateVariable = 10;
 function privateFunction() {
 return false;
 }
 // 特权方法
 this.publicMethod = function() {
 privateVariable++;
 return privateFunction();
 };
}
function Person(name) {
 this.getName = function() {
 return name;
 };
 this.setName = function (value) {
 name = value;
 };
}
let person = new Person('Nicholas');
console.log(person.getName()); // 'Nicholas'
person.setName('Greg');
console.log(person.getName()); // 'Greg' 

构造函数模式的缺点是每个实例都会重新创建一遍新方法。使用静态私有变量实现特权方法可以避免这个问题。

静态私有变量

特权方法也可以通过使用私有作用域定义私有变量和函数来实现。

(function() {
 // 私有变量和私有函数
 let privateVariable = 10;
 function privateFunction() {
 return false;
 }
 // 构造函数
 MyObject = function() {};
 // 公有和特权方法
 MyObject.prototype.publicMethod = function() {
 privateVariable++;
 return privateFunction();
 };
})(); 
(function() {
 let name = '';
 Person = function(value) {
 name = value;
 };
 Person.prototype.getName = function() {
 return name;
 };
 Person.prototype.setName = function(value) {
 name = value;
 };
})();
let person1 = new Person('Nicholas');
console.log(person1.getName()); // 'Nicholas'
person1.setName('Matt');
console.log(person1.getName()); // 'Matt'
let person2 = new Person('Michael');
console.log(person1.getName()); // 'Michael'
console.log(person2.getName()); // 'Michael'

模块模式

在一个单例对象上实现了相同的隔离和封装。单例对象(singleton)就是只有一个实例的对象。

let singleton = {
 name: value, 
method() {
 // 方法的代码
 }
};

模块模式是在单例对象基础上加以扩展,使其通过作用域链来关联私有变量和特权方法。

let singleton = function() {
 // 私有变量和私有函数
 let privateVariable = 10;
 function privateFunction() {
 return false;
 }
 // 特权/公有方法和属性
 return {
 publicProperty: true,
 publicMethod() {
 privateVariable++;
 return privateFunction();
 }
 };
}();
let application = function() {
 // 私有变量和私有函数
 let components = new Array();
 // 初始化
 components.push(new BaseComponent());
 // 公共接口
 return {
 getComponentCount() {
 return components.length;
 },
 registerComponent(component) {
 if (typeof component == 'object') {
 components.push(component);
 }
 }
 };
}();

前一个方法返回注册组件的数量,后一个方法负责注册新组件。

模块增强模式

另一个利用模块模式的做法是在返回对象之前先对其进行增强。这适合单例对象需要是某个特定类型的实例,但又必须给它添加额外属性或方法的场景。

let singleton = function() {
 // 私有变量和私有函数
 let privateVariable = 10;
 function privateFunction() {
 return false;
 }
 // 创建对象
 let object = new CustomType();
 // 添加特权/公有属性和方法
 object.publicProperty = true;
 object.publicMethod = function() {
 privateVariable++;
 return privateFunction();
 };
 // 返回对象
 return object;
}();
let application = function() {
 // 私有变量和私有函数
 let components = new Array();
 // 初始化
 components.push(new BaseComponent());
 // 创建局部变量保存实例
 let app = new BaseComponent();
 // 公共接口
 app.getComponentCount = function() {
 return components.length;
 }; 
 app.registerComponent = function(component) {
 if (typeof component == "object") {
 components.push(component);
 }
 };
 // 返回实例
 return app;
}(); 
  1. 函数表达式与函数声明是不一样的。函数声明要求写出函数名称,而函数表达式并不需要。没有名称的函数表达式也被称为匿名函数。
  2. ES6 新增了类似于函数表达式的箭头函数语法,但两者也有一些重要区别。
  3. JavaScript 中函数定义与调用时的参数极其灵活。arguments 对象,以及 ES6 新增的扩展操作符,可以实现函数定义和调用的完全动态化。
  4. 函数内部也暴露了很多对象和引用,涵盖了函数被谁调用、使用什么调用,以及调用时传入了什么参数等信息。
  5. JavaScript 引擎可以优化符合尾调用条件的函数,以节省栈空间
  6. 闭包的作用域链中包含自己的一个变量对象,然后是包含函数的变量对象,直到全局上下文的变量对象
  7. 通常,函数作用域及其中的所有变量在函数执行完毕后都会被销毁。
  8. 闭包在被函数返回之后,其作用域会一直保存在内存中,直到闭包被销毁。
  9. 函数可以在创建之后立即调用,执行其中代码之后却不留下对函数的引用。
  10. 可以访问私有变量的公共方法叫作特权方法。

🆗

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions