《JavaScript筑基 - JavaScript 基础知识》

281 阅读22分钟

fushishan.jpg

前言

近期, 我们团队又开始了 JavaScript 突击,重新学习 JavaScript ,并且进行查漏补缺。
同时也是为了加强团队内人员的自主学习能力。
不得不说,很久不写文章的我,又要慢慢捡起来了。

本篇大纲如下,主要以学习章节的顺序来进行知识的排序,其中会带入一些自己的描述或者理解。
InkedJavaScript 基础知识_LI.jpg

一、变量

变量是数据的“命名存储”。 我们可以使用变量来保存商品、访客和其他信息。

1.创建变量

在 JavaScript 中创建一个变量,我们需要用到 let 关键字。

let message;

我们可以通过赋值运算符 = 为变量添加一些数据:

let message;
message = 'Hello'; // 保存字符串

let msg = 'World';

alert(message); // 'Hello'
alert(msg); // 'World'

我们还可以直接在一行中声明多个变量

let user = 'John', age = 25, message = 'Hello';


还有其他写法:

let user = 'John';
let age = 25;
let message = 'Hello';

let user = 'John',
  age = 25,
  message = 'Hello';
  
let user = 'John'
  , age = 25
  , message = 'Hello';

上述写法在结果上是一样的,只是写法有所区别,因人而异。

1.1 var

在较旧的脚本中,你也可能发现另一个关键字 var,而不是 let

var message = 'Hello';

varlet 非常相似,但是被let声明的变量不会作为全局对象window的属性。

let msgLet = 'Hello';
var msgVar = 'World';

console.log(window.msgLet); //undefined
console.log(window.msgVar); //World

在函数作用域中,上述两种声明方式声明出来的变量的意义是一样的。但是在块级作用域中两者的区别较为明显。

function  aFun1(){
    // i 对于for循环外的范围是不可见的(报错,i is not defined)
    console.log('for循环之前',i)
    for(let i = 1; i<5; i++){
        //  i只有在这里是可见的
        console.log('for循环内',i)
    }
    // i 对于for循环外的范围是不可见的(报错,i is not defined)
    console.log('for循环之后',i)
}

function aFun2(){
    // i 对于for循环外的范围是可见的,但是往前位置未赋值。
    console.log('for循环之前',i) //undefined
    for(var i = 1;i<5; i++){
        // i 在for 在整个函数体内都是可见的
        console.log('for循环内',i)
    }
    // i 对于for循环外的范围是可见的,但是只会得到循环后的值
    console.log('for循环之后',i) //5
}


同时var还允许在同一个作用域重复声明相同的变量,前者会被后者覆盖。而let不行。

let a  = 'foo';
let a  = 'bar'; //SyntaxError: Identifier 'me' has already been declared

var b = 'foo';
var b = 'bar'; //这里me被替代了,是可以重复声明的

1.2 一个现实生活的类比

如果将变量想象成一个“数据”的盒子,盒子上有一个唯一的标注盒子名字的贴纸。这样我们能更轻松地掌握“变量”的概念。
例如,变量 message 可以被想象成一个标有 "message" 的盒子,盒子里面的值为 "Hello!"
image.png
图片来源:《现代 JavaScript 教程 - JavaScript 基础知识》
我们可以在盒子内放入任何值。
并且,这个盒子的值,我们想改变多少次,就可以改变多少次:

let message;
message = 'Hello!';
message = 'World!'; // 值改变了
alert(message);

当值改变的时候,之前的数据就被从变量中删除了:
image.png
图片来源:《现代 JavaScript 教程 - JavaScript 基础知识》

同时,还可以将一个变量的数据拷贝到另一个变量中。

let hello = 'Hello world!';
let message;
// 将字符串 'Hello world' 从变量 hello 拷贝到 message
message = hello;
// 现在两个变量保存着相同的数据
alert(hello); // Hello world!
alert(message); // Hello world!

2. 变量命名

JavaScript 的变量命名有两个限制:

  1. 变量名称必须仅包含字母,数字,符号 $_
  2. 首字符必须非数字。


如果命名包括多个单词,通常采用驼峰式命名法(camelCase)。也就是,单词一个接一个,除了第一个单词,其他的每个单词都以大写字母开头:myVeryLongName

  1. 在命名或者使用变量时,需要注意区分大小写。命名为 apple 和 AppLE 的变量是不同的两个变量。
  2. 可以使用任何语言,包括西里尔字母(cyrillic letters)甚至是象形文字。技术上讲,完全没有错误,这样的命名是完全允许的,但是用英文进行变量命名是国际传统。
  3. 有一张 保留字列表,这张表中的保留字无法用作变量命名,因为它们被用于编程语言本身了。比如,letclassreturnfunction 都被保留了。

3. 常量

声明一个常数(不变)变量,可以使用 const 而非 let

const myBirthday = '18.04.1982';

常量不能被修改当程序员能确定这个变量永远不会改变的时候,就可以使用 const 来确保这种行为,并且清楚地向别人传递这一事实。

4. 正确命名变量

一个变量名应该有一个清晰、明显的含义,对其存储的数据进行描述。
变量命名是编程过程中最重要且最复杂的技能之一。快速地浏览变量的命名就知道代码是一个初学者还是有经验的开发者写的。
声明变量之前,多花点时间思考它的更好的命名。你会受益良多。
一些可以遵循的规则:

  • 使用易读的命名,比如 userName 或者 shoppingCart
  • 离诸如 abc 这种缩写和短名称远一点,除非你真的知道你在干什么。
  • 变量名在能够准确描述变量的同时要足够简洁。不好的例子就是 datavalue,这样的名称等于什么都没说。如果能够非常明显地从上下文知道数据和值所表达的含义,这样使用它们也是可以的。
  • 命名的术语要和团队保持一致。如果网站的访客称为“用户”,则我们采用相关的变量命名,比如 currentUser 或者 newUser,而不要使用 currentVisitor 或者一个 newManInTown

5. 小结

我们可以使用 varletconst 声明变量来存储数据。

  • let — 现代的变量声明方式。
  • var — 老旧的变量声明方式。一般情况下,我们不会再使用它。但是,我们会在 旧时的 "var" 章节介绍 varlet 的微妙差别,以防你需要它们。
  • const — 类似于 let,但是变量的值无法被修改。

变量应当以一种容易理解变量内部是什么的方式进行命名。

二、数据类型

JavaScript 中的变量可以保存任何数据。同时允许改变变量的类型。
允许这种操作的编程语言称为“动态类型”(dynamically typed)的编程语言,意思是虽然编程语言中有不同的数据类型,但是你定义的变量并不会在定义后,被限制为某一数据类型。

// 没有错误
let message = "hello";
message = 123456;

1. Number类型

number 类型代表整数和浮点数。
数字可以有很多操作,比如,乘法 *、除法 /、加法 +、减法 - 等等。
除了常规的数字,还包括所谓的“特殊数值(“special numeric values”)”也属于这种类型:Infinity-InfinityNaN

  • Infinity 代表数学概念中的 无穷大 ∞。是一个比任何数字都大的特殊值。
    我们可以通过除以 0 来得到它:
alert( 1 / 0 ); // Infinity

或者在代码中直接使用它:

alert( Infinity ); // Infinity
  • NaN 代表一个计算错误。它是一个不正确的或者一个未定义的数学操作所得到的结果,比如:
alert( "not a number" / 2 ); // NaN,这样的除法是错误的

NaN 是粘性的。任何对 NaN 的进一步操作都会返回 NaN

alert( "not a number" / 2 + 5 ); // NaN

所以,如果在数学表达式中有一个 NaN,会被传播到最终结果。

在 JavaScript 中做数学运算是安全的。我们可以做任何事:除以 0,将非数字字符串视为数字,等等。
脚本永远不会因为一个致命的错误而停止。最坏的情况下,我们会得到 NaN 的结果。

2. BigInt 类型


在 JavaScript 中,“number” 类型无法代表大于 2(或小于 -2)的整数,这是其内部表示形式导致的技术限制。

下面参考知乎《JavaScript 里最大的安全的整数为什么是2的53次方减一?》问题中 Jim Liu 的回答:

-22的闭区间内,都是能够one-by-one表示的整数,也就是说在(-2^53, 2^53)范围内,双精度数表示和整数是一对一的,反过来说,在这个范围以内,所有的整数都有唯一的浮点数表示,这叫做安全整数
而超过这个范围,会有两个或更多整数的双精度表示是相同的;反过来说,超过这个范围,有的整数是无法精确表示的,只能round到与它相近的浮点数表示,这种情况下叫做不安全整数

BigInt 类型是最近被添加到 JavaScript 语言中的,用于表示任意长度的整数。

通过将 n 附加到整数字段的末尾来创建 BigInt

// 尾部的 "n" 表示这是一个 BigInt 类型
const bigInt = 1234567890123456789012345678901234567890n;


兼容性问题:
目前 Firefox 和 Chrome 已经支持 BigInt 了,但 Safari/IE/Edge 还没有。

3. String 类型

JavaScript 中的字符串必须被括在引号里

在 JavaScript 中,有三种包含字符串的方式。

  1. 双引号:"Hello"
  2. 单引号:'Hello'
  3. 反引号:Hello
let str = "Hello";
let str2 = 'Single quotes are ok too';
let phrase = `can embed another ${str}`;


双引号和单引号都是“简单”引用,在 JavaScript 中两者几乎没有什么差别。
反引号是 功能扩展 引号。它们允许我们通过将变量和表达式包装在 ${…} 中,来将它们嵌入到字符串中。例如:

let name = "John";
// 嵌入一个变量
alert( `Hello, ${name}!` ); // Hello, John!
// 嵌入一个表达式
alert( `the result is ${1 + 2}` ); // the result is 3


${…} 内的表达式会被计算,计算结果会成为字符串的一部分。可以在 ${…} 内放置任何东西:诸如名为 name 的变量,或者诸如 1 + 2 的算数表达式,或者其他一些更复杂的。

4. Boolean 类型

boolean 类型仅包含两个值:truefalse
这种类型通常用于存储表示 yes 或 no 的值:true 意味着 “yes,正确”,false 意味着 “no,不正确”。

所以 boolean 常见可以用于判断,也可以作为比较的结果:

let isTrue = true;
if(istrue){
 console.log('这个值是正确的')
};

let isGreater = 4 > 1;
alert( isGreater ); // true(比较的结果是 "yes")

5. "null"

特殊的 null 值不属于上述任何一种类型。
它构成了一个独立的类型,只包含 null 值:

let age = null;


JavaScript 中的 null 仅仅是一个代表“无”、“空”或“值未知”的特殊值。

6. "undefined"值

特殊值 undefinednull 一样自成类型。
undefined 的含义是 未被赋值
如果一个变量已被声明,但未被赋值,那么它的值就是 undefined

let x;
alert(x); // 弹出 "undefined"


undefined 仅仅用于检验,例如查看变量是否被赋值或者其他类似的操作。

7. object 类型和 symbol 类型

object 类型是一个特殊的类型,object 用于储存数据集合和更复杂的实体。
JavaScript 中的所有对象都来自 Object;所有对象从Object.prototype继承方法和属性,尽管它们可能被覆盖。

symbol 类型用于创建对象的唯一标识符。

8. typeof运算符

typeof 运算符返回参数的类型。当我们想要分别处理不同类型值的时候,或者想快速进行数据类型检验时,非常有用。

它支持两种语法形式:

  1. 作为运算符:typeof x
  2. 函数形式:typeof(x)
typeof undefined // "undefined"
typeof 0 // "number"
typeof 10n // "bigint"
typeof true // "boolean"
typeof "foo" // "string"
typeof Symbol("id") // "symbol"
typeof Math // "object"  (1)
typeof null // "object"  (2)
typeof alert // "function"  (3)


最后三行可能需要额外的说明:

  1. Math 是一个提供数学运算的内建 object
  2. typeof null 的结果是 "object"。这其实是不对的。官方也承认了这是 typeof 运算符的问题,现在只是为了兼容性而保留了下来。当然,null 不是一个 objectnull 有自己的类型,它是一个特殊值。再次强调,这是 JavaScript 语言的一个错误
  3. typeof alert 的结果是 "function",因为 alert 在 JavaScript 语言中是一个函数。

9. 小结

JavaScript 中有八种基本的数据类型(前七种为基本数据类型,也称为原始类型,而 object 为复杂数据类型)。

  • number 用于任何类型的数字:整数或浮点数,在 ±2 范围内的整数。
  • bigint 用于任意长度的整数。
  • string 用于字符串:一个字符串可以包含一个或多个字符,所以没有单独的单字符类型。
  • boolean 用于 truefalse
  • null 用于未知的值 —— 只有一个 null 值的独立类型。
  • undefined 用于未定义的值 —— 只有一个 undefined 值的独立类型。
  • symbol 用于唯一的标识符。
  • object 用于更复杂的数据结构。

我们可以通过 typeof 运算符查看存储在变量中的数据类型。

  • 两种形式:typeof x 或者 typeof(x)
  • 以字符串的形式返回类型名称,例如 "string"
  • typeof null 会返回 "object" —— 这是 JavaScript 编程语言的一个错误,实际上它并不是一个 object

三、类型转换

大多数情况下,运算符和函数会自动将赋予他们的值转换为正确的类型。但在某些情况下,我们需要将值显式地转换为我们期望的类型。

我们需要知道的是,转换一般分为显式转换隐式转换

1. 字符串转换

我们可以通过显式地调用 String(value) 来将 value 转换为字符串类型:

value = String(value); // 现在,值是一个字符串形式的 "true"
alert(typeof value); // string


同时我们常见的隐式转换成字符串的方法是可以将数字类型和字符串类型进行运算:

let a = 'str';
let b = 123;

console.log(a+b); //str123

上述的过程中,会先将数字类型转换为字符串类型,也就是将 123 转换成 "123",再进行字符串的拼接操作。

字符串转换最明显。false 变成 "false"null 变成 "null" 等。

2. 数字型转换

在算术函数和表达式中,会自动进行 number 类型转换。
比如,将运算符用于非 number 类型:

alert( "6" / "2" ); // 3, string 类型的值被自动转换成 number 类型后进行计算


我们也可以使用 Number(value) 显式地将这个 value 转换为 number 类型。

let str = "123";
let num = Number(str); // 变成 number 类型 123
alert(typeof num); // number


如果该字符串不是一个有效的数字,转换的结果会是 NaN

let age = Number("an arbitrary string instead of a number");
alert(age); // NaN,转换失败


number 类型转换规则:

转换结果
undefined NaN
null 0
true 和 false 1 and 0
string 去掉首尾空格后的纯数字字符串中含有的数字。如果剩余字符串为空,则转换结果为 0。否则,将会从剩余字符串中“读取”数字。当类型转换出现 error 时返回 NaN


请注意 nullundefined 在这有点不同:null 变成数字 0undefined 变成 NaN。(译注:此外,字符串转换为 number 类型时,除了 undefinednullboolean 三种特殊情况,只有字符串是由空格和数字组成时,才能转换成功,否则会出现 error 返回 NaN。)

3. 布尔型转换

布尔(boolean)类型转换是最简单的一个。
它发生在逻辑运算中,但是也可以通过调用 Boolean(value) 显式地进行转换。

转换规则如下:

  • 直观上为“空”的值(如 0、空字符串、nullundefinedNaN)将变为 false
  • 其他值变成 true
转换结果
0, null, undefined, NaN, "" false
其他值 true


需要注意的是,字符串的 "0" 在 JavaScript 中,值为 true 。应该说,在 JavaScript 中,非空的字符串总是 true

alert( Boolean(1) ); // true
alert( Boolean(0) ); // false
alert( Boolean("hello") ); // true
alert( Boolean("") ); // false

4. 小结

有三种常用的类型转换:转换为 string 类型、转换为 number 类型和转换为 boolean 类型。
字符串转换 —— 转换发生在输出内容的时候,也可以通过 String(value) 进行显式转换。原始类型值的 string 类型转换通常是很明显的。
数字型转换 —— 转换发生在进行算术操作时,也可以通过 Number(value) 进行显式转换。
布尔型转换 —— 转换发生在进行逻辑操作时,也可以通过 Boolean(value) 进行显式转换。

四、条件运算符:if 和 '?'

有时我们需要根据不同条件执行不同的操作。
我们可以使用 if 语句和条件运算符 ?(也称为“问号”运算符)来实现。

1. “if” 语句

if(...) 语句计算括号里的条件表达式,如果计算结果是 true,就会执行对应的代码块。

let trueBoolean = true
if (trueBoolean) alert( 'You are right!' );


如果有多个语句要执行,我们必须将要执行的代码块封装在大括号内:

if (trueBoolean) {
 alert( 'You are right!' );
  alert( 'This is True!' );
}

2. “else” 语句

if 语句有时会包含一个可选的 “else” 块。如果判断条件不成立,就会执行它内部的代码。

let trueBoolean = true
if (trueBoolean) {
 alert( 'true' )
}else{
 alert( 'false' )
};

3. 多个条件:“else if”

有时我们需要测试一个条件的几个变体。我们可以通过使用 else if 子句实现。

let num = 5

if (num > 5) {
 alert( '数值大于5' )
}else if (num < 5){
 alert( '数值小于5' )
}else{
 alert( '数值等于5' )
};

上述代码中,JavaScript 会按顺序依次判断条件,条件不符合时则会转到下一个条件继续判断。

4. 条件运算符 ‘?’

有时我们需要根据一个条件去赋值一个变量。而条件运算符可以让我们更简短的达到目的。

这个运算符通过问号 ? 表示。有时它被称为三元运算符,被称为“三元”是因为该运算符中有三个操作数。实际上它是 JavaScript 中唯一一个有这么多操作数的运算符。

let result = condition ? value1 : value2;


上述代码中,如果 condition 为真,则返回 value1,否则返回 value2

五、循环:while 和 for

循环 是一种重复运行同一代码的方法。

1. “while” 循环

语法如下:

while (condition) {
  // 代码
  // 所谓的“循环体”
}

conditiontrue 时,执行循环体的 code

循环体的单次执行叫作 一次迭代

任何表达式或变量都可以是循环条件,而不仅仅是比较。在 while 中的循环条件会被计算,计算结果会被转化为布尔值。
例如:

let i = 3;
while (i) { // 当 i 变成 0 时,条件为 false,循环终止
  alert( i );
  i--;
}


单行循环体不需要大括号
如果循环体只有一条语句,则可以省略大括号 {…}

let i = 3;
while (i) alert(i--);

2.“do…while” 循环

使用 do..while 语法可以将条件检查移至循环体 下面

do {
  // 循环体
} while (condition)


循环首先执行循环体,然后检查条件,当条件为真时,重复执行循环体。

let i = 0;
do {
  alert( i );
  i++;
} while (i < 3);


需要清楚了解的是, whiledo...while 有本质区别。
do...while 无论条件是否为真,都至少会执行一次循环体
需要根据实际场景来考虑使用哪种方式

3.“for” 循环

for 循环比起前两个会更复杂,但它是最常使用的循环形式。

for (begin; condition; step) {
  // ……循环体……
}

我们可以根据下表来了解对应参数所代表的含义。

语句段
begin i = 0 进入循环时执行一次。
condition i < 3 在每次循环迭代之前检查,如果为 false,停止循环。
body(循环体) alert(i) 条件为真时,重复运行。
step i++ 在每次循环体迭代后执行。


例如:

for (let i = 0; i < 3; i++) { // 结果为 0、1、2
  alert(i);
}


内联变量声明
这里“计数”变量 i 是在循环中声明的。这叫做“内联”变量声明。这样的变量只在循环中可见。

for (let i = 0; i < 3; i++) {
  alert(i); // 0, 1, 2
}
alert(i); // 错误,没有这个变量。

3.1 省略语句段

for 循环的任何语句段都可以被省略。

let i = 0; // 我们已经声明了 i 并对它进行了赋值
for (; i < 3; i++) { // 不再需要 "begin" 语句段
  alert( i ); // 0, 1, 2
}


同时我们也可以移除 step 语句段:

let i = 0;
for (; i < 3;) {
  alert( i++ );
}

该循环与 while (i < 3) 等价。

实际上我们可以删除所有内容,从而创建一个无限循环:

for (;;) {
  // 无限循环
}


请注意 for 的两个 ; 必须存在,否则会出现语法错误。

4. 跳出循环

通常条件为假时,循环会终止。
但我们随时都可以使用 break 指令强制退出。

let sum = 0;
while (true) {
  let value = +prompt("Enter a number", '');
  if (!value) break; // (*)
  sum += value;
}
alert( 'Sum: ' + sum );


根据需要,"无限循环 + break" 的组合非常适用于不必在循环开始/结束时检查条件,但需要在中间甚至是主体的多个位置进行条件检查的情况。

5. 继续下一次迭代

continue 指令是 break 的“轻量版”。它不会停掉整个循环。而是停止当前这一次迭代,并强制启动新一轮循环(如果条件允许的话)。
如果我们完成了当前的迭代,并且希望继续执行下一次迭代,我们就可以使用它。

for (let i = 0; i < 10; i++) {
  //如果为真,跳过循环体的剩余部分。
  if (i % 2 == 0) continue;
  alert(i); // 1,然后 3,5,7,9
}


对于偶数的 i 值,continue 指令会停止本次循环的继续执行,将控制权传递给下一次 for 循环的迭代(使用下一个数字)。因此 alert 仅被奇数值调用。

continue 指令利于减少嵌套。
显示奇数的循环可以像下面这样:

for (let i = 0; i < 10; i++) {
  if (i % 2) {
    alert( i );
  }
}

在副作用方面,它多创建了一层嵌套(大括号内的 alert 调用)。如果 if 中代码有多行,则可能会降低代码整体的可读性。


## 6. 小结 我们学习了三种循环:
  • while —— 每次迭代之前都要检查条件。
  • do..while —— 每次迭代后都要检查条件。
  • for (;;) —— 每次迭代之前都要检查条件,可以使用其他设置。

通常使用 while(true) 来构造“无限”循环。这样的循环和其他循环一样,都可以通过 break 指令来终止。
如果我们不想在当前迭代中做任何事,并且想要转移至下一次迭代,那么可以使用 continue 指令。

六、"switch" 语句

switch 语句可以替代多个 if 判断。
switch 语句为多分支选择的情况提供了一个更具描述性的方式。

1. 语法

switch 语句有至少一个 case 代码块和一个可选的 default 代码块。
例如:

switch(x) {
  case 'value1':  // if (x === 'value1')
    ...
    [break]
  case 'value2':  // if (x === 'value2')
    ...
    [break]
  default:
    ...
    [break]
}
  • 比较 x 值与第一个 case(也就是 value1)是否严格相等,然后比较第二个 casevalue2)以此类推。
  • 如果相等,switch 语句就执行相应 case 下的代码块,直到遇到最靠近的 break 语句(或者直到 switch 语句末尾)。
  • 如果没有符合的 case,则执行 default 代码块(如果 default 存在)。
  • 需要注意的是,在与 case 做比较时,比较是严格相等,所以我们必须考虑两者的类型是否相同。


任何表达式都可以成为 switch/case 的参数
例如:

let a = "1";
let b = 0;
switch (+a) {
  case b + 1:
    alert("this runs, because +a is 1, exactly equals b+1");
    break;
  default:
    alert("this doesn't run");
}

2. “case” 分组

共享同一段代码的几个 case 分支可以被分为一组,在使用的时候,分在一组的分支里面,在前头的分支不写执行代码和结束语句 break 即可。

let a = 3;
switch (a) {
  case 4:
    alert('Right!');
    break;
  case 3: // (*) 下面这两个 case 被分在一组
  case 5:
    alert('Wrong!');
    alert("Why don't you take a math class?");
    break;
  default:
    alert('The result is strange. Really.');
}


switch/case 有通过 case 进行“分组”的能力,其实是 switch 语句没有 break 时的副作用。因为没有 breakcase 3 会从 (*) 行执行到 case 5

七、函数

函数是程序的主要“构建模块”。函数使该段代码可以被调用很多次,而不需要写重复的代码。

1. 函数声明

使用 函数声明 创建函数。

function showMessage() {
  alert( 'Hello everyone!' );
}


function 关键字首先出现,然后是 函数名,然后是括号之间的 参数 列表(用逗号分隔,在上述示例中为空),最后是花括号之间的代码(即“函数体”)。

2. 局部变量

在函数中声明的变量只在该函数内部可见。

function showMessage() {
  let message = "Hello, I'm JavaScript!"; // 局部变量
  alert( message );
}
showMessage(); // Hello, I'm JavaScript!
alert( message ); // <-- 错误!变量是函数的局部变量

3. 外部变量

函数对外部变量拥有全部的访问权限。函数也可以修改外部变量。

let userName = 'John';
function showMessage() {
  userName = "Bob"; // (1) 改变外部变量
  let message = 'Hello, ' + userName;
  alert(message);
}
alert( userName ); // John 在函数调用之前
showMessage();
alert( userName ); // Bob,值被函数修改了


如果在函数内部声明了同名变量,那么函数会 遮蔽 外部变量。

任何函数之外声明的变量,例如上述代码中的外部变量 userName,都被称为 全局 变量。
全局变量在任意函数中都是可见的(除非被局部变量遮蔽)。
减少全局变量的使用是一种很好的做法。现代的代码有很少甚至没有全局变量。大多数变量存在于它们的函数中。但是有时候,全局变量能够用于存储项目级别的数据。
在我们正常开发过程中,如果不是非必要的,尽量要避免使用全局变量。

4. 参数

我们可以使用参数(也称“函数参数”)来将任意数据传递给函数。

function showMessage(from, text) { // 参数:from 和 text
  alert(from + ': ' + text);
}
showMessage('Ann', 'Hello!'); // Ann: Hello! (*)
showMessage('Ann', "What's up?"); // Ann: What's up? (**)

参数会有默认值,如果上述代码未提供默认值的,默认值是 undefined
当然,我们也可以她通过赋值来指定默认值:

function showMessage(from, text = "no text given") {
  alert( from + ": " + text );
}
showMessage("Ann"); // Ann: no text given


默认参数的计算
在 JavaScript 中,每次函数在没带个别参数的情况下被调用,默认参数会被计算出来。

5. 返回值

函数可以将一个值返回到调用代码中作为结果。

function sum(a, b) {
  return a + b;
}
let result = sum(1, 2);
alert( result ); // 3


指令 return 可以在函数的任意位置。当执行到达时,函数停止,并将值返回给调用代码(分配给上述代码中的 result)。
当我们只使用 return 但没有返回值时,会导致函数立即退出。

空值的return或没有return 的函数返回值为 undefined

6. 小结

  • 作为参数传递给函数的值,会被复制到函数的局部变量。
  • 函数可以访问外部变量。但它只能从内到外起作用。函数外部的代码看不到函数内的局部变量。
  • 函数可以返回值。如果没有返回值,则其返回的结果是 undefined


为了使代码简洁易懂,建议在函数中主要使用局部变量和参数,而不是外部变量。

函数命名:

  • 函数名应该清楚地描述函数的功能。当我们在代码中看到一个函数调用时,一个好的函数名能够让我们马上知道这个函数的功能是什么,会返回什么。
  • 一个函数是一个行为,所以函数名通常是动词。

八、函数表达式

在 JavaScript 中,函数不是“神奇的语言结构”,而是一种特殊的值。
除了函数声明以外,还有另一种可以创建函数的语法,称为 函数表达式。
通常写成这样:

let sayHi = function() {
  alert( "Hello" );
};

在这里,函数被创建并像其他赋值一样,被明确地分配给了一个变量。不管函数是被怎样定义的,都只是一个存储在变量 sayHi 中的值。

1. 回调函数

我们写一个包含三个参数的函数 ask(question, yes, 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 的两个参数值 showOkshowCancel 可以被称为 回调函数 或简称 回调

同时我们可以直接在 ask(...) 调用内进行函数声明。这两个函数没有名字,所以叫 匿名函数。这样的函数在 ask 外无法访问。

一个函数是表示一个“行为”的值。

2. 函数表达式 vs 函数声明

让我们来总结一下函数声明和函数表达式之间的主要区别。

语法 函数名 可用时机
函数表达式 在主代码流中声明为单独的语句的函数 可选 在代码执行到达时被创建,并且仅从那一刻起可用
函数声明 在一个表达式中或另一个语法结构中创建的函数。 必选 在函数声明被定义之前,它就可以被调用。

严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见。

函数声明的方法定义的函数并不是真正的声明,他们仅仅可以出现在全局中或者嵌套在其它函数中。

3. 小结

  • 函数是值。它们可以在代码的任何地方被分配,复制或声明。
  • 如果函数在主代码流中被声明为单独的语句,则称为“函数声明”。
  • 如果该函数是作为表达式的一部分创建的,则称其“函数表达式”。
  • 在执行代码块之前,内部算法会先处理函数声明。所以函数声明在其被声明的代码块内的任何位置都是可见的。
  • 函数表达式在执行流程到达时创建。

九、箭头函数

创建函数还有另外一种非常简单的语法,并且这种方法通常比函数表达式更好,它被称为箭头函数。

let func = (arg1, arg2, ...argN) => expression


上述代码等价于下属代码:

let func = function(arg1, arg2, ...argN) {
  return expression;
};


箭头函数可以像函数表达式一样使用。

let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
  () => alert('Hello') :
  () => alert("Greetings!");
welcome();


甚至我们还可以使用多行的箭头函数

let sum = (a, b) => {  // 花括号表示开始一个多行函数
  let result = a + b;
  return result; // 如果我们使用了花括号,那么我们需要一个显式的 “return”
};
alert( sum(1, 2) ); // 3


对于一行代码的函数来说,箭头函数是相当方便的。它具体有两种:

  1. 不带花括号:(...args) => expression — 右侧是一个表达式:函数计算表达式并返回其结果。
  2. 带花括号:(...args) => { body } — 花括号允许我们在函数中编写多个语句,但是我们需要显式地 return 来返回一些内容。

总结

本文概括了大部分常见的 JavaScript 基础知识,让我重新复习了一遍相关基础知识。虽然文章本身不是非常高端的内容,但是重要的是,本文是我重新编辑文章的第一步,并且不会是最后一步。之后我也会在学习和产出中,慢慢的将文章编写的更好。

参考文章:

《现代 JavaScript 教程 - JavaScript 基础知识》