函数
函数
- 在函数中声明的变量只在该函数内部可见。
- 函数对外部变量拥有全部的访问权限。函数也可以修改外部变量。
- 只有在没有局部变量的情况下才会使用外部变量。
- 函数的参数也是局部变量。作为参数传递给函数的值,会被复制到函数的局部变量。
- 在 JavaScript 中,每次函数在没带个别参数的情况下被调用,默认参数会被计算出来。
function showMessage(from, text = anotherFunction()) {
// anotherFunction() 仅在没有给定 text 时执行
// 其运行结果将成为 text 的值
// 如果传递了参数 text,那么 anotherFunction() 就不会被调用。
} - 空值的
return
或没有return
的函数返回值为undefined
函数表达式
用 函数声明 的方式创建函数
function sum(a, b) {
return a + b;
}用 函数表达式 的方式创建函数
let sum = function(a, b) {
return a + b;
};JavaScript 引擎 对于 函数声明 与 函数表达式 创建函数的时机不同。在函数声明被定义之前,它就可以被调用。例如,一个全局函数声明对整个脚本来说都是可见的,无论它被写在这个脚本的哪个位置。这是内部算法的缘故。当 JavaScript 准备 运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数。我们可以将其视为“初始化阶段”。在处理完所有函数声明后,代码才被执行。所以运行时能够使用这些函数。
函数表达式 是在代码执行到它时才会被创建,并且仅从那一刻起可用。一旦代码执行到赋值表达式
let sum = function…
的右侧,此时就会开始创建该函数,并且可以从现在开始使用(分配,调用等)。严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见。
let age = 16;
if (age < 18) {
welcome(); // \ (运行)
// |
function welcome() { // |
alert("Hello!"); // | 函数声明在声明它的代码块内任意位置都可用
} // |
// |
welcome(); // / (运行)
} else {
function welcome() {
alert("Greetings!");
}
}
// 在这里,我们在花括号外部调用函数,我们看不到它们内部的函数声明。
welcome(); // Error: welcome is not defined// 使用函数表达式
let age = prompt("What is your age?", 18);
let welcome;
if (age < 18) {
welcome = function() {
alert("Hello!");
};
} else {
welcome = function() {
alert("Greetings!");
};
}
welcome(); // 现在可以了// 使用问号运算符 ? 来进一步对代码进行简化
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
function() { alert("Hello!"); } :
function() { alert("Greetings!"); };
welcome(); // 现在可以了无论函数是如何创建的,函数都是一个值。可以像使用其他类型的值一样使用它。
function sayHi() { // 声明创建了函数,并把它放入到变量 sayHi
alert( "Hello" );
}
let func = sayHi; // 将 sayHi 复制到了变量 func
func(); // 运行复制的值(正常运行)输出 Hello
sayHi(); // 运行输出 Hello在大多数情况下,当我们需要声明一个函数时,最好使用函数声明,因为函数在被声明之前也是可见的。这使我们在代码组织方面更具灵活性,通常也会使得代码可读性更高。仅当函数声明不适合对应的任务时,才应使用函数表达式。
回调函数
主要思想是我们传递一个函数,并期望在稍后必要时将其“回调”。如下例子中,showOk
是回答 “yes” 的回调,showCancel
是回答 “no” 的回调。
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
function showOk() {
alert( "You agreed." );
}
function showCancel() {
alert( "You canceled the execution." );
}
// 用法:函数 showOk 和 showCancel 被作为参数传入到 ask
ask("Do you agree?", showOk, showCancel);
可以使用函数表达式来编写一个等价的、更简洁的函数。直接在
ask(...)
调用内声明两个匿名函数,这样的函数在ask
外无法访问(因为没有对它们分配变量),这正符合 JavaScript 语言的思想。function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
ask(
"Do you agree?",
function() { alert("You agreed."); },
function() { alert("You canceled the execution."); }
);
日常开发使用场景之一:
const brandInfo = {
id: 1,
name: '选择品牌',
children: [],
expanded: true,
};
const normInfo = {
id: 2,
name: '选择规格',
children: [],
expanded: true,
}
const chosenBrand = null;
function created() {
if (id) getCategoryInfo(id, (data) => {
brandInfo.children = data;
brandInfo.children = brandInfo.children.map(ele => {
ele.imgUrl = process.env.VUE_APP_IMG + ele.imgUrl;
return ele;
});
});
}
async getCategoryInfo(id, cb) {
try {
const categoryResult = await queryMeterCategory(id);
if (!categoryResult.data) throw categoryResult.msg;
cb(categoryResult.data);
} catch (error) {
this.$nutToast.text(error);
}
},
chooseBrandSku(chosenSku) {
chosenBrand = chosenSku;
brandInfo.expanded = false;
getCategoryInfo(chosenSku.id, (data) => {
normInfo.children = data;
normInfo.children = normInfo.children.map(ele => {
return { ...ele, checked: false };
})
});
},
箭头函数
创建函数还有另外一种非常简单的语法,并且这种方法通常比函数表达式更好,即箭头函数:
// 创建了一个函数 func,它接受参数 arg1..argN,然后使用参数对右侧的 expression 求值并返回其结果
let func = (arg1, arg2, ..., argN) => expression;
// 上面是下面这段代码的更短的版本
let func = function(arg1, arg2, ..., argN) {
return expression;
};
// 如果没有参数,括号则是空的(但括号必须保留)
let sayHi = () => alert("Hello!");
箭头函数可以像函数表达式一样使用。
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
() => alert('Hello!') :
() => alert("Greetings!");
welcome();多行的箭头函数: 用花括号括起来,需要包含 return 才能返回值
let sum = (a, b) => { // 花括号表示开始一个多行函数
let result = a + b;
return result; // 如果我们使用了花括号,那么我们需要一个显式的 “return”
};
alert( sum(1, 2) ); // 3箭头函数没有自己的
this
。当我们并不想要一个独立的this
,反而想从外部上下文中获取时,它很有用。
构造函数
构造函数(或简称构造器)在技术上是常规函数。不过有两个约定:
- 它们的命名以大写字母开头。
- 它们只能由
new
操作符来执行。
new User(...)
做的事情可以简单理解为:
function User(name) {
// this = {};(隐式创建)
// 添加属性到 this
this.name = name;
this.isAdmin = false;
// return this;(隐式返回)
}
function User(name) {
this.name = name;
// 添加方法到 this
this.sayHi = function() {
alert( "My name is: " + this.name );
};
}
let john = new User("John");
john.sayHi(); // My name is: John
/*
john = {
name: "John",
sayHi: function() { ... }
}
*/
- 构造器的主要目的: 创建多个类似的对象。
- 从技术上讲,任何函数(除了箭头函数,它没有自己的
this
)都可以用作构造器。 - 如果没有参数,可以省略括号,但不建议这样做
let user = new User; // <-- 没有参数
// 等同于
let user = new User();
new function() { … }
可以用 new function() { … }
封装构建单个对象的代码,而无需将来重用。这个构造函数不能被再次调用,因为它不保存在任何地方,只是被创建和调用。如果我们有许多行用于创建单个复杂对象的代码,就可以使用 new function() { … }
,例如:
let user = new function() {
this.name = "John";
this.isAdmin = false;
// ……用于用户创建的其他代码
// 也许是复杂的逻辑和语句
// 局部变量等
};
new.target
在一个函数内部,我们可以使用 new.target
属性来检查它是否被使用 new
进行调用了。对于常规调用,它为 undefined
,对于使用 new
的调用,则等于该函数。
如下方式有时被用在库中以使语法更加灵活。这样人们在调用函数时,无论是否使用了 new
,程序都能工作。
构造器的return
通常,构造器没有 return
语句。如果有,则:
- 如果
return
返回的是一个对象,则返回这个对象,而不是this
。 - 如果
return
返回的是一个原始类型,则忽略。