## JS介绍

JavaScript 是 web 开发人员必须学习的 3 门语言中的一门：

1. **HTML** 定义了网页的内容
2. **CSS** 描述了网页的布局
3. **JavaScript** 控制了网页的行为
JavaScript 是一种轻量级的编程语言。

JavaScript 是可插入 HTML 页面的编程代码。

插入 HTML 页面后，可由所有的现代浏览器执行。

直接写入 HTML 输出流、对事件的反应、改变 HTML 图像、能够改变任意 H TML 元素的大多数属性，而不仅仅是图片。

改变 HTML 样式

改变 HTML 元素的样式，属于改变 HTML 属性的变种。

在这段代码中，~function(e){...}(this) 表示使用了一个自执行的匿名函数（Immediately Invoked Function Expression，IIFE）。这个匿名函数接收一个参数 e，表示当前被点击的图片元素，然后对这个元素的 src 属性进行修改，实现灯泡的打开和关闭。

具体来说，当用户点击图片时，onclick 事件会触发，并将当前的图片元素作为参数传递给匿名函数。匿名函数中的代码会通过正则表达式判断当前图片的路径中是否包含 bulboff，如果包含，则将路径修改为 /images/pic_bulbon.gif，否则将路径修改为 /images/pic_bulboff.gif。这样就实现了灯泡的打开和关闭，用户可以通过点击图片来切换灯泡的状态。

需要注意的是，~function(e){...}(this) 中的波浪线 ~ 并不是必须的，它的作用是将匿名函数转换为一个表达式，从而避免将 function 关键字视为一个语句而出现语法错误。因此，这个波浪线可以省略，代码也可以写成 (function(e){...})(this) 的形式，效果是一样的。



JavaScript 已经由 ECMA（欧洲电脑制造商协会）通过 ECMAScript 实现语言的标准化。

ECMAScript 6 也称为 ECMAScript 2015。

ECMAScript 7 也称为 ECMAScript 2016。

### **JavaScript 用法**



HTML 中的 Javascript 脚本代码必须位于 **\<script>** 与 **\</script>** 标签之间。

Javascript 脚本代码可被放置在 HTML 页面的 **\<body>** 和 **\<head>** 部分中。



\<script> 标签

如需在 HTML 页面中插入 JavaScript，请使用 \<script> 标签。

\<script> 和 \</script> 会告诉 JavaScript 在何处开始和结束。

\<script> 和\</script> 之间的代码行包含了 JavaScript:

```html
<script>
alert("我的第一个 JavaScript"); 
</script>
```

**\<body> 中的 JavaScript**

在本例中，JavaScript 会在页面加载时向 HTML 的 \<body> 写文本：

**实例**

```html
<!DOCTYPE html>
<html>
<body>
.
.
<script>
document.write("<h1>这是一个标题</h1>");
document.write("<p>这是一个段落</p>");
</script>
.
.
</body>
</html>
```


**JavaScript 函数和事件**

上面例子中的 JavaScript 语句，页面加载时就执行。

通常，我们需要在某个事件发生时执行代码，比如当用户点击按钮时。

如果我们把 JavaScript 代码放入函数中，就可以在事件发生时调用该函数。



**在 \<head> 或者 \<body> 的JavaScript**

您可以在 HTML 文档中放入不限数量的脚本。

脚本可位于 HTML 的 \<body> 或 \<head> 部分中，或者同时存在于两个部分中。

通常的做法是把函数放入 \<head> 部分中，或者放在页面底部。这样就可以把它们安置到同一处位置，不会干扰页面的内容。



**\<head> 中的 JavaScript 函数**

在本例中，我们把一个 JavaScript 函数放置到 HTML 页面的 <head> 部分。

该函数会在点击按钮时被调用：

**实例**

```html
<script>
function myFunction()
{
    document.getElementById("demo").innerHTML="我的第一个 JavaScript 函数";
}
</script>
```



**\<body> 中的 JavaScript 函数**

在本例中，我们把一个 JavaScript 函数放置到 HTML 页面的 <body> 部分。

该函数会在点击按钮时被调用：

**实例**

```html
<!DOCTYPE html>
<html>
<body>
<h1>我的 Web 页面</h1>
<p id="demo">一个段落</p>
<button type="button" onclick="myFunction()">尝试一下</button>
<script>
function myFunction()
{
    document.getElementById("demo").innerHTML="我的第一个 JavaScript 函数";
}
</script>
</body>
</html>
```



**外部的 JavaScript**

也可以把脚本保存到外部文件中。外部文件通常包含被多个网页使用的代码。

外部 JavaScript 文件的文件扩展名是 .js。

如需使用外部文件，请在 \<script> 标签的 "src" 属性中设置该 .js 文件：

**实例**

```html
<!DOCTYPE html>
<html>
<body>
<script src="myScript.js"></script>
</body>
</html>
```

你可以将脚本放置于\<head> 或者 \<body>中，放在 \<script> 标签中的脚本与外部引用的脚本运行效果完全一致。

myScript.js 文件代码如下：
```javascript
function myFunction() { document.getElementById("demo").innerHTML="我的第一个 JavaScript 函数"; }
```
外部脚本不能包含 \<script> 标签。
您只能在 HTML 输出流中使用 \<strong>document.write\</strong>。
如果您在文档已加载后使用它（比如在函数中），会覆盖整个文档。

1、Console 窗口调试 JavaScript 代码

打开开发者工具后，我们可以在 Console 窗口调试 JavaScript代码，如下图：

![img](https://chenalna.oss-cn-hangzhou.aliyuncs.com/img/93B1E50A-D2D9-4FB4-B458-D50045FDE599.jpg)




上图中我们在 > 符号后输入我们要执行的代码 console.log("runoob")，按回车后执行。


 2、Chrome snippets 小脚本

我们也可以在 Chrome 浏览器中创建一个脚本来执行，在开发者工具中点击 Sources 面板，选择 Snippets 选项卡，在导航器中右击鼠标，然后选择 Create new snippet 来新建一个脚本文件：

 ![img](https://www.runoob.com/wp-content/uploads/2020/11/8C18C75F-6C15-4B7F-8C66-122D1D23C14E.jpg)

保存后，右击文件名，选择 "Run" 执行代码：

![img](https://www.runoob.com/wp-content/uploads/2020/11/0DBBF606-1F97-4861-B690-1DBED83A0E5E.jpg)

### JavaScript 输出

JavaScript 没有任何打印或者输出的函数。

 

**JavaScript 显示数据**

JavaScript 可以通过不同的方式来输出数据：

 

使用 window.alert() 弹出警告框。

使用 document.write() 方法将内容写到 HTML 文档中。

使用 innerHTML 写入到 HTML 元素。

使用 console.log() 写入到浏览器的控制台。

使用 window.alert()

你可以弹出警告框来显示数据：

```html
<script>

window.alert(5 + 6);

</script>

```

 

1. **JavaScript 核心（ECMAScript）**

JavaScript 语言核心只包含：

- 基本语法（变量、函数、循环等）
- 数据类型（String、Number、Object、Array 等）
- 内置对象（Math、Date、RegExp 等）

**语言核心没有提供任何输出功能**。理论上，一个纯 JavaScript 引擎无法输出任何内容。

2. **宿主环境提供的输出方法**

JavaScript 需要运行在某个环境中，这个环境会提供额外的 API：



#### **浏览器环境（Web API）**

javascript

```js
// 浏览器提供的输出方法
window.alert("弹窗");      // 浏览器 API
document.write("写入");    // DOM API
console.log("控制台");     // Console API
element.innerHTML = "内容"; // DOM API
```



#### **Node.js 环境**

javascript

```js
// Node.js 提供的输出方法
console.log("输出");       // Node.js 的 console 模块
process.stdout.write(""); // 标准输出
```


**操作 HTML 元素**

如需从 JavaScript 访问某个 HTML 元素，您可以使用 document.getElementById(id) 方法。

请使用 "id" 属性来标识 HTML 元素，并 innerHTML 来获取或插入元素内容：

```html
<p id="demo">我的第一个段落</p>
<script>

document.getElementById("demo").innerHTML = "段落已修改。";

</script>

```
以上 JavaScript 语句（在 \<script> 标签中）可以直接在 web 浏览器中执行：

document.getElementById("demo") 是使用 id 属性来查找 HTML 元素的 JavaScript 代码 。

innerHTML = "段落已修改。" 是用于修改元素的 HTML 内容(innerHTML)的 JavaScript 代码。

在本教程中

在大多数情况下，在本教程中，我们将使用上面描述的方法来输出：

上面的例子直接把 id="demo" 的 \<p> 元素写到 HTML 文档输出中：

**写到 HTML 文档**

出于测试目的，您可以将JavaScript直接写在HTML 文档中：

 

```html

<script>

document.write(Date());
```

使用 document.write() 可以向文档写入内容。
如果在文档已完成加载后执行 document.write，整个 HTML 页面将被覆盖。
document.write是直接写入到页面的内容流，如果在写之前没有调用document.open, 浏览器会自动调用open。每次写完关闭之后重新调用该函数，会导致页面被重写。

innerHTML则是DOM页面元素的一个属性，代表该元素的html内容。你可以精确到某一个具体的元素来进行更改。如果想修改document的内容，则需要修改document.documentElement.innerElement。


innerHTML很多情况下都优于document.write，其原因在于其允许更精确的控制要刷新页面的那一个部分。

console.log()的用处
主要是方便你调式javascript用的, 你可以看到你在页面中输出的内容。

相比alert他的优点是：

他能看到结构化的东西，如果是alert，弹出一个对象就是[object object],但是console能看到对象的内容。

console不会打断你页面的操作，如果用alert弹出来内容，那么页面就死了，但是console输出内容后你页面还可以正常操作。
```html

<h1>我的第一个 Web 页面</h1>
<p>我的第一个段落。</p>

<button onclick="myFunction()">点我</button>

<script>

function myFunction() {

  document.write(Date());

}

</script>
```

写到控制台

如果您的浏览器支持调试，你可以使用 console.log() 方法在浏览器中显示 JavaScript 值。

浏览器中使用 F12 来启用调试模式， 在调试窗口中点击 "Console" 菜单。

```html
 <script>

a = 5;

b = 6;

c = a + b;

console.log(c);

</script>
```
JavaScript 是一个程序语言。语法规则定义了语言结构。

JavaScript 语法

JavaScript 是一个脚本语言。


JavaScript 字面量

在编程语言中，一般固定值称为字面量，如 3.14。

 

数字（Number）字面量 可以是整数或者是小数，或者是科学计数(e)。


字符串（String）字面量 可以使用单引号或双引号:

"John Doe"
'John Doe'

表达式字面量 用于计算：

5 + 6

5 * 10

数组（Array）字面量 定义一个数组：

 

[40, 100, 1, 5, 25, 10]

对象（Object）字面量 定义一个对象：

{firstName:"John", lastName:"Doe", age:50, eyeColor:"blue"}

函数（Function）字面量 定义一个函数：

function myFunction(a, b) { return a * b;}

JavaScript 变量

在编程语言中，变量用于存储数据值。

 

JavaScript 使用关键字 var 来定义变量， 使用等号来为变量赋值：

```html
<script>
var length;
length = 6;
document.getElementById("demo").innerHTML = length;
</script>
```

变量可以通过变量名访问。在指令式语言中，变量通常是可变的。字面量是一个恒定的值。

JavaScript 操作符

JavaScript使用 算术运算符 来计算值:
(5 + 6) * 10

JavaScript使用赋值运算符给变量赋值：

```html
<p id="demo"></p>
<script>
var x, y, z;
x = 5;
y = 6;
z = (x + y) * 10;
document.getElementById("demo").innerHTML = z;
</script>
```



JavaScript语言有多种类型的运算符：



类型    实例    描述

赋值，算术和位运算符   = + - * /   在 JS 运算符中描述

条件，比较及逻辑运算符  == != < >    在 JS 比较运算符中描述

 

JavaScript 语句

在 HTML 中，JavaScript 语句用于向浏览器发出命令。

语句是用分号分隔：

 

x = 5 + 6;

y = x * 10;

 

JavaScript 关键字

JavaScript 关键字用于标识要执行的操作。

 

和其他任何编程语言一样，JavaScript 保留了一些关键字为自己所用。

 

var 关键字告诉浏览器创建一个新的变量：

 

var x = 5 + 6;

var y = x * 10;

JavaScript 同样保留了一些关键字，这些关键字在当前的语言版本中并没有使用，但在以后 JavaScript 扩展中会用到。


JavaScript 注释
双斜杠 // 后的内容将会被浏览器忽略：


JavaScript 数据类型

JavaScript 有多种数据类型：数字，字符串，数组，对象等等：

var length = 16;                 // Number 通过数字字面量赋值

var points = x * 10;               // Number 通过表达式字面量赋值

var lastName = "Johnson";             // String 通过字符串字面量赋值

var cars = ["Saab", "Volvo", "BMW"];       // Array 通过数组字面量赋值

var person = {firstName:"John", lastName:"Doe"}; // Object 通过对象字面量赋值

 

数据类型的概念

编程语言中，数据类型是一个非常重要的内容。

 

为了可以操作变量，了解数据类型的概念非常重要。

 

如果没有使用数据类型，以下实例将无法执行：

 

16 + "Volvo"

16 加上 "Volvo" 是如何计算呢?

"16Volvo"(字符串)


你可以在浏览器尝试执行以上代码查看效果。


JavaScript 函数

JavaScript 语句可以写在函数内，函数可以重复引用：

 

引用一个函数 = 调用函数(执行函数内的语句)。

 

function myFunction(a, b) {

  return a * b;                // 返回 a 乘以 b 的结果

}

 

JavaScript 字母大小写

JavaScript 对大小写是敏感的。

JavaScript 使用 Unicode 字符集。


JavaScript 语句向浏览器发出的命令。语句的作用是告诉浏览器该做什么。

 

## JavaScript 语句

JavaScript 语句是发给浏览器的命令。

 

下面的 JavaScript 语句向 id="demo" 的 HTML 元素输出文本 "你好 Dolly" ：

`document.getElementById("demo").innerHTML = "你好 Dolly";`



分号 ;

分号用于分隔 JavaScript 语句。

 

通常我们在每条可执行的语句结尾添加分号。

 

使用分号的另一用处是在一行中编写多条语句。

 

实例:

 

a = 5;

b = 6;

c = a + b;

以上实例也可以这么写:

 

a = 5; b = 6; c = a + b;

 您也可能看到不带有分号的案例。

在 JavaScript 中，用分号来结束语句是可选的。

 

**JavaScript 代码**

JavaScript 代码是 JavaScript 语句的序列。

 

浏览器按照编写顺序依次执行每条语句。



 

JavaScript 代码块

JavaScript 可以分批地组合起来。

代码块以左花括号开始，以右花括号结束。

代码块的作用是一并地执行语句序列。

 

```html
<p id="myPar">我是一个段落。</p>
<div id="myDiv">我是一个div。</div>
<p>
<button type="button" onclick="myFunction()">点击这里</button>
</p>
<script>
function myFunction(){
	document.getElementById("myPar").innerHTML="你好世界！";
	document.getElementById("myDiv").innerHTML="你最近怎么样?";
}
</script>
```



 

JavaScript 语句标识符

JavaScript 语句通常以一个 语句标识符 为开始，并执行该语句。

JavaScript 语句标识符 (关键字) ：

JavaScript 会忽略多余的空格



 对代码行进行折行

您可以在文本字符串中使用反斜杠对代码行进行换行。下面的例子会正确地显示：



```html
<script>
document.write("你好 \
世界!");
</script>
```

JavaScript 是脚本语言，浏览器会在读取代码时，逐行地执行脚本代码。而对于传统编程来说，会在执行前对所有代码进行编译。

**Js注释**

单行注释以 **//** 开头

多行注释以` /* `开始，以 `*/`结尾

## JS变量

在 JavaScript 中，变量用于存储数据，并可以在程序执行过程中动态更改。

在 JavaScript 中，变量可以存储各种类型的数据，如数字、字符串、对象、函数等。

变量名是标识符，用于引用存储在变量中的数据。

在 JavaScript 中，可以使用 **var**、**let** 和 **const** 关键字来声明变量。

- **`var`**：ES5 引入的变量声明方式，具有函数作用域。
- **`let`**：ES6 引入的变量声明方式，具有块级作用域。
- **`const`**：ES6 引入的常量声明方式，具有块级作用域，且值不可变。

JavaScript 变量还能保存其他数据类型，比如文本值 (name="Bill Gates")。

在 JavaScript 中，类似 "Bill Gates" 这样一条文本被称为字符串。

当您向变量分配文本值时，应该用双引号或单引号包围这个值。

当您向变量赋的值是数值时，不要使用引号。如果您用引号包围数值，该值会被作为文本来处理。

 

声明（创建） JavaScript 变量

在 JavaScript 中创建变量通常称为"声明"变量。

 

我们使用 var 关键词来声明变量：

 

var carname;

变量声明之后，该变量是空的（它没有值）。



如需向变量赋值，请使用等号：

 

carname="Volvo";

不过，您也可以在声明变量时对其赋值：

 

var carname="Volvo";

在下面的例子中，我们创建了名为 carname 的变量，并向其赋值 "Volvo"，然后把它放入 id="demo" 的 HTML 段落中：

 

实例

```html
var carname="Volvo";

document.getElementById("demo").innerHTML=carname;
```



var 声明特点：

变量可以重复声明（覆盖原变量）。

变量未赋值时，默认值为 undefined。

var 声明的变量会提升（Hoisting），但不会初始化。

 

 一个好的编程习惯是，在代码开始处，统一对需要的变量进行声明。

 

一条语句，多个变量

您可以在一条语句中声明很多变量。该语句以 var 开头，并使用逗号分隔变量即可：

 

var lastname="Doe", age=30, job="carpenter";

声明也可横跨多行：



var lastname="Doe",

age=30,

job="carpenter";

一条语句中声明的多个变量不可以同时赋同一个值:



var x, y, z = 1;

x, y 为 undefined， z 为 1。

 

**Value = undefined**

在计算机程序中，经常会声明无值的变量。未使用值来声明的变量，其值实际上是 undefined。

 

在执行过以下语句后，变量 carname 的值将是 undefined：

 

var carname;

 

重新声明 JavaScript 变量

如果重新声明 JavaScript 变量，该变量的值不会丢失：

 

在以下两条语句执行后，变量 carname 的值依然是 "Volvo"：

 

var carname="Volvo"; 

var carname;

 

JavaScript 算数

您可以通过 JavaScript 变量来做算数，使用的是 = 和 + 这类运算符：

 

实例

y=5;

x=y+2;

 



使用 let 和 const (ES6)

在 2015 年以前，我们使用 var 关键字来声明 JavaScript 变量。

 

在 2015 后的 JavaScript 版本 (ES6) 允许我们使用 const 关键字来定义一个常量，使用 let 关键字定义的限定范围内作用域的变量。

 

let

let 是 ES6 引入的新变量声明方式，推荐使用。

 

let 语法：

 

let variableName = value;

实例

let city = "北京";

let age = 30;

console.log(city, age); // 输出: 北京 30

const

const 用于定义常量，即一旦赋值后，变量的值不能再被修改。

const 语法：

const variableName = value;





let语法：



```
let var1 [= value1] [, var2 [= value2]] [, ..., varN [= valueN]];
```

let允许你声明一个作用域被限制在块级中的变量、语句或者表达式。在Function中局部变量推荐使用let变量，避免变量名冲突。

**作用域规则**

let 声明的变量只在其声明的块或子块中可用，这一点，与var相似。二者之间最主要的区别在于var声明的变量的作用域是整个封闭函数。

可以把 `var` 理解为：**在整个函数范围内，所有 `var` 声明都会被提升到函数顶部，并且同一个作用域内不能重复声明同名变量**。否则值会被覆盖。

- `var` 声明的变量**没有块级作用域**，只有函数作用域或全局作用域
- `if`、`for`、`while` 等代码块**不会**创建新的作用域
- 所以在 `if` 块内部重新声明 `var x`，实际上是在**重用**函数顶部的那个 `x` 变量

```javascript
function varTest() {
    var x = 1;
    if (true) {
        var x = 2;       // 同样的变量!
        console.log(x);  // 2
    }
    console.log(x);  // 2
}
//var 在整个函数作用域内只有一个变量实例

//所有对该变量的赋值都会按顺序执行，后面的赋值会覆盖前面的

//最终变量的值是最后一次赋值的结果。

//但如果有函数嵌套，情况不同
function outer() {
    var x = 1;
    
    function inner() {
        var x = 2;  // 这是新的变量，因为函数创建了新作用域
        console.log(x); // 2
    }
    
    inner();
    console.log(x); // 1（没有被覆盖）
}

function letTest() {
    let x = 1;
    if (true) {
        let x = 2;       // 不同的变量    
        console.log(x);  // 2  
    }
    console.log(x);  // 1
}
```





Javascript声明变量的时候，虽然用var关键字声明和不用关键字声明，很多时候运行并没有问题，但是这两种方式还是有区别的。可以正常运行的代码并不代表是合适的代码。

```
// num1为全局变量，num2为window的一个属性
var num1 = 1;
num2 = 2;
// delete num1;  无法删除
// delete num2;  删除
function model(){
var num1 = 1; // 本地变量
num2 = 2;     // window的属性
    // 匿名函数
    (function(){
        var num = 1; // 本地变量
        num1 = 2; // 继承作用域（闭包）
        num3 = 3; // window的属性
    }())
}
```



**JavaScript 允许重复定义函数**

JavaScript 没有重载这个概念，它仅依据函数名来区分函数。

后定义的同名函数覆盖之前的，与参数无关。

```
function test() {
    console.log("test");
}
test();     //输出 "test arg0 + undefined"

function test(arg1) {
    console.log("test arg" + arguments.length + " + " + arg1);
}
test(1,2);  //输出 "test arg2 + 1"
```

实参个数如果比形参少，那么剩下的默认赋值为 **undefined**，如果实参传的比形参数量多，那么是全部都会被传进去的，只不过没有对应的形参可以引用（但可以用 arguments 来获取剩下的参数）。`arguments` 是一个**类数组对象**，包含所有传入函数的实参，不管有没有对应的形参。

```
function test(arg1) {
    for(var i=0; i<arguments.length; i++) {
        console.log(arguments[i]);
    }
}
test(1,2); //输出 1 2
```







JavaScript 在代码执行前会先进行**预解析**，将所有声明提升到作用域顶部。提升的优先级是：



**函数声明 > 变量声明**

```js
// 原始代码
var a = 100;
function a() {
    return "function";
}
console.log(a);
console.log(a());
```



2. 预解析后的实际执行顺序

```js
// JavaScript 引擎预解析后的代码
function a() {          // 1. 函数声明首先被提升
    return "function";
}
var a;                  // 2. 变量声明被提升，但因为 a 已存在，被忽略，。已经存在的变量不会被重复声明

a = 100;                // 3. 变量赋值保留在原位置（覆盖了函数 a），变量赋值会覆盖函数声明

console.log(a);         // 4. 输出 100（此时 a 是数字，不是函数）
console.log(a());       // 5. 报错：a 不是函数
```



JS 中有两种函数，一种是普通函数，一种是函数对象。下面的这种就是“函数对象”，它实际上是声明一个匿名函数，然后将该函数的 init 方法赋值给该变量。

```
var a = 100;
var a = function() {
    return "function";
}
console.log(a);
/* 
输出
function() {
    return "function";
}
*/
console.log(a());   //输出 "function"
```



普通函数就是函数声明，函数对象就是函数表达式







**函数与内部变量重名**

定义普通函数，即在 window 变量下，定义一个 key，它的名字为该函数名，值为该函数的地址。函数内部的 this 指向 window 对象。

```
function a() {
    console.log(this);  //输出 window{...}
    this.a = 1;         //即 window.a = 1，此时window下的function a已经被该变量覆盖了。
    var a = 5;          //下面的这几个变量都是局部变量，仅在花括号范围内有效。  
    a = 10;
    var v = "value"
    return "function";
}
console.log(a);         //输出 function a {...}
console.log(a());       //输出 "function"，this.a = 1 → 在全局对象上设置属性 a = 1，覆盖了原来的函数
console.log(a);         //输出 1
console.log(v);
/*
输出
Uncaught ReferenceError: v is not defined
    (anonymous function) @ mycolor.html:15
*/
```



**同名声明赋值的变量：**逐条进行-后者覆盖前者。（同级别覆盖）

```
var a = 1; 
var a = 2;
console.log(a); // 输出结果：2
```

**同名声明赋值的-函数和变量：**逐条进行-后者覆盖前者。（同级别覆盖）

```
var a = 1; 
var a = function () {     
}
console.log(a); //输出结果： a 函数
```

\-----------------------------------------------------------------

**注：var f = function () {}** 和 **function f () {}** 的区别（有var就有内存）

```
var f = function () {console.log("有var")} //变量声明+函数表达式赋值
function f () {} // 函数声明
console.log(f); // 输出结果： 有var的f函数
```

\-----------------------------------------------------------------

**同名声明的变量：**赋值的级别高。（同名声明未赋值）

```
var a = 1; // 赋值
var a; // 未赋值
console.log(a); // 输出结果： 1
```

\-----------------------------------------------------------------

**声明和未声明的同名变量：**后者是重新赋值。（同名赋值未声明）

```
var a = 1; // 声明赋值
a =2 ; // 未声明变量
console.log(a);    // 输出结果： 2 
var f = function () {} // 声明赋值
f = function () {console.log("赋值")} // 未声明变量
console.log(f);   // 输出结果：function () {console.log("赋值")}
```

注意



```
var f = function () {console.log("有var")} // 变量声明+函数表达式赋值
function f () {} // 函数声明
console.log(f); // 输出结果： 有var的f函数
```

和

```
var f = function () {} // 变量声明+函数表达式赋值
//如果 var f = function f() {}，那就是命名函数表达式
f = function () {console.log("赋值")} // 未声明变量+函数表达式赋值
console.log(f);   // 输出结果：function () {console.log("赋值")}
```



| 代码                    | 类型                  | 变量提升               | 函数提升 | 作用域         | 严格模式行为 |
| :---------------------- | :-------------------- | :--------------------- | :------- | :------------- | :----------- |
| `var f = function() {}` | 变量声明 + 函数表达式 | `f` 提升为 `undefined` | 无       | 函数作用域     | ✅ 正常       |
| `function f() {}`       | 函数声明              | 无                     | 整体提升 | 函数作用域     | ✅ 正常       |
| `f = function() {}`     | 隐式全局赋值          | 无                     | 无       | 全局（或报错） | ❌ 报错       |





## **JavaScript 数据类型**

JavaScript是弱类型编程语言,定义变量都使用 var 定义,与 Java 这种强类型语言有区别.



基本类型（Primitive Types）值类型- 7 种

1. **`string`** - 字符串
2. **`number`** - 数字
3. **`boolean`** - 布尔值
4. **`null`** - 空值
5. **`undefined`** - 未定义
6. **`symbol`** - 符号（ES6 新增）
7. **`bigint`** - 大整数（ES2020 新增）

引用类型（Object Types）对象类型- 1 种基础 + 若干衍生

- **`object`** - 这是唯一的引用类型
  - 普通对象 `{}`
  - 数组 `[]`（本质是对象）
  - 函数 `function`（本质是对象，但 `typeof` 返回 `"function"`）
  - 日期 `Date`
  - 正则 `RegExp`
  - 等等...

*Symbol 是 ES6 引入了一种新的原始数据类型，表示独一无二的值。*



JavaScript 拥有动态类型

JavaScript 拥有动态类型。这意味着相同的变量可用作不同的类型：

 

实例

var x;        // x 为 undefined

var x = 5;      // 现在 x 为数字

var x = "John";   // 现在 x 为字符串

变量的数据类型可以使用 typeof 操作符来查看：

 // Number 通过数字字面量赋值 

 // Number 通过表达式字面量赋值

// String 通过字符串字面量赋值

// Array 通过数组字面量赋值 

// Object 通过对象字面量赋值

实例

typeof "John"        // 返回 string

typeof 3.14         // 返回 number

typeof false         // 返回 boolean

typeof [1,2,3,4]       // 返回 object

typeof {name:'John', age:34} // 返回 object

typeof [1,2,3,4] 返回 "object"，这是 JavaScript 早期设计的一个"缺陷"，数组本质上是特殊类型的对象。

typeof Symbol()	"symbol"	ES6新增符号类型

 typeof BigInt(10)	"bigint"

正确检测数组的方法：

**1、使用 isArray 方法**

```
var cars=new Array();
cars[0]="Saab";
cars[1]="Volvo";
cars[2]="BMW";
// 判断是否支持该方法
if (Array.isArray) {
    if(Array.isArray(cars)) {
        document.write("该对象是一个数组。") ;
    }
}
```

**2、使用 instanceof 操作符**

```
var cars=new Array();
cars[0]="Saab";
cars[1]="Volvo";
cars[2]="BMW";

if (cars instanceof Array) {
    document.write("该对象是一个数组。") ;
}
```

JavaScript 只有一种数字类型。数字可以带小数点，也可以不带：



var x1=34.00;   //使用小数点来写
var x2=34;     //不使用小数点来写

极大或极小的数字可以通过科学（指数）计数法来书写：



var y=123e5;   // 12300000
var z=123e-5;   // 0.00123






布尔（逻辑）只能有两个值：true 或 false。

var x=true;
var y=false;



下面的代码创建名为 cars 的数组：

var cars=new Array();
cars[0]="Saab";
cars[1]="Volvo";
cars[2]="BMW";

或者 (condensed array):

var cars=new Array("Saab","Volvo","BMW");

或者 (literal array):



var cars=["Saab","Volvo","BMW"];



JavaScript 字符串

字符串是存储字符（比如 "Bill Gates"）的变量。

字符串可以是引号中的任意文本。您可以使用单引号或双引号：

 

实例

var carname="Volvo XC60";

var carname='Volvo XC60';

您可以在字符串中使用引号，只要不匹配包围字符串的引号即可：

 

实例

var answer="It's alright";

var answer="He is called 'Johnny'";

var answer='He is called "Johnny"';

 

声明变量类型

当您声明新变量时，可以使用关键词 "new" 来声明其类型：

 

var carname=new String;

var x=   new Number;

var y=   new Boolean;

var cars=  new Array;

var person= new Object;





**Undefined 和 Null**

-  （1）undefined：是所有没有赋值变量的默认值，自动赋值。
-  （2）null：主动释放一个变量引用的对象，表示一个变量不再指向任何对象地址。



**2、何时使用null?**

当使用完一个比较大的对象时，需要对其进行释放内存时，设置为 null。

**3、null 与 undefined 的异同点是什么呢？**

**共同点**：都是原始类型，保存在栈中变量本地。

不同点：

（1）undefined——表示变量声明过但并未赋过值。

它是所有未赋值变量默认值，例如：

```
var a;    // a 自动被赋值为 undefined
```

（2）null——表示一个变量将来可能指向一个对象。

一般用于主动释放指向对象的引用，例如：

```
var emps = ['ss','nn'];
emps = null;     // 释放指向数组的引用
```

4、延伸——垃圾回收站

它是专门释放对象内存的一个程序。

-  （1）在底层，后台伴随当前程序同时运行；引擎会定时自动调用垃圾回收期；
-  （2）总有一个对象不再被任何变量引用时，才释放。

Undefined 这个值表示变量不含有值。

可以通过将变量的值设置为 null 来清空变量。





在 JavaScript 中 null 表示 "什么都没有"。

null是一个只有一个值的特殊类型。表示一个空对象引用。

 用 typeof 检测 null 返回是object。

```js
var person = null;           // 值为 null(空), 但类型为对象
var person = undefined;     // 值为 undefined, 类型为 undefined

在 JavaScript 中, undefined 是一个没有设置值的变量。

typeof 一个没有值的变量会返回 undefined。
var person;                  // 值为 undefined(空), 类型是undefined

null 和 undefined 的值相等，但类型不等：

typeof undefined             // undefined
typeof null                  // object
null === undefined           // false
null == undefined            // true
```





当您声明新变量时，可以使用关键词 "new" 来声明其类型：

```js
var carname=new String;
var x=   new Number;
var y=   new Boolean;
var cars=  new Array;
var person= new Object;

cars=null;
person=null;

var x,y;
if(x == null){
    document.write(x);
}
if(y == undefined){
    document.write(y);
}
```



**基本数据类型的值是按值访问的。**

基本类型的值是不可变的

基本类型的比较是它们的值的比较

```
var a = 1;
var b = true;
console.log(a == b);    // true
console.log(a === b);   // false
```

上面 a 和 b 的数据类型不同，但是也可以进行值的比较，这是因为在比较之前，自动进行了数据类型的 隐式转换。

- == : 只进行值的比较
- === : 不仅进行值得比较，还要进行数据类型的比较



基本类型的变量是存放在栈内存（Stack）里的

![img](https://www.runoob.com/wp-content/uploads/2019/05/3834493100-57c3ff4a5dac7_articlex.png)

**引用类型**

引用类型的值是可变的

引用类型的比较是引用的比较

```
var obj1 = {};    // 新建一个空对象 obj1
var obj2 = {};    // 新建一个空对象 obj2
console.log(obj1 == obj2);    // false
console.log(obj1 === obj2);   // false
```

因为 obj1 和 obj2 分别引用的是存放在堆内存中的2个不同的对象，这里的 `obj1` 和 `obj2` 并不是直接存储对象本身，而是存储**指向该对象的内存地址**（引用）。故变量 obj1 和 obj2 的值（引用地址）也是不一样的！

无论是 `==` 还是 `===`，当比较引用类型时，比较的都是**内存地址是否相同**：

引用类型的值是保存在堆内存（Heap）中的对象（Object）
与其他编程语言不同，JavaScript 不能直接操作对象的内存空间（堆内存）。





![图片描述](https://www.runoob.com/wp-content/uploads/2019/05/3309698956-57c41a89cddc7_articlex.png)



## **JavaScript 对象**

在 JavaScript中，几乎所有的事物都是对象

对象由花括号分隔。在括号内部，对象的属性以名称和值对的形式 (name : value) 来定义。属性由逗号分隔：





JavaScript  对象是变量的容器。

```
var people = {name : 'Tom', age : 21 , eat : function(){  }    }
```

也可先创建对象再追加属性和方法

```
var people = new Object();
people.name = 'Tom';   
people.age = 21;  
people.eat = function(){  }
person.method=function(){
  return this.name+this.sex;
}
```



第一种：




```
function Demo(){
    var obj=new Object();
    obj.name="张思";
    obj.age=12;
    obj.firstF=function(){
    }
    obj.secondF=function(){
    }
    return obj;
}

var one=Demo();
// 调用输出
document.write(one.age);
```

第二种：

```
function Demo(){
    this.name="张思";
    this.age=12;
    this.firstF=function(){
    }
    this.secondF=function(){
    }
}

var one=new Demo

// 调用输出
document.write(one.age);
```







### 对象定义

你可以使用字符来定义和创建 JavaScript 对象:



var person = {firstName:"John", lastName:"Doe", age:50, eyeColor:"blue"};

### 对象属性

可以说 "JavaScript 对象是变量的容器"。

但是，我们通常认为 "JavaScript 对象是键值对的容器"。

键值对通常写法为 **name : value** (键与值以冒号分割)。

键值对在 JavaScript 对象通常称为 **对象属性**。



 JavaScript 对象是属性变量的容器。

对象键值对的写法类似于：

- PHP 中的关联数组
- Python 中的字典
- C 语言中的哈希表
- Java 中的哈希映射
- Ruby 和 Perl 中的哈希表

### 访问对象属性

你可以通过两种方式访问对象属性:

person.lastName;

person["lastName"];

### 对象方法、访问对象方法

对象的方法定义了一个函数，并作为**对象的属性**存储。



你可以使用以下语法创建对象方法：

```
methodName : function() {
    // 代码 
}
```

对象方法通过添加 () 调用 (作为一个函数)。

该实例访问了 person 对象的 fullName() 方法:



name = person.fullName();



如果你要访问 person 对象的 fullName 属性，它将作为一个定义函数的字符串返回：

name = person.fullName;

```js
var person = {
    firstName: "John",
    lastName : "Doe",
    id : 5566,
    fullName : function() 
	{
       return this.firstName + " " + this.lastName;
    }
};
document.getElementById("demo1").innerHTML = "不加括号输出函数表达式："  + person.fullName;
document.getElementById("demo2").innerHTML = "加括号输出函数执行结果："  +  person.fullName();
```



不加括号输出函数表达式：function() { return this.firstName + " " + this.lastName; }

加括号输出函数执行结果：John Doe





JavaScript 对象是属性和方法的容器。



javaScript对象中属性具有唯一性（这里的属性包括方法），如果有两个重复的属性，则以最后赋值为准。比如同时存在两个play:

```
var person = {
    name: "小明",
    age: 18,
    sex: "男",
    play: "football",
    play: function () {
        return "like paly football";
    }
};
```



JavaScript 对象是键值对的容器，“键”必须为字符串，“值” 可以是 JavaScript 中包括 null 和 undefined 的任意数据类型。

键的类型不一定是字符串，而是字符串或符号，一般类型都是转换成字符串（对象数字等类型），但是符号不会被强制转换。

注意：如果把符号用作对象的属性 / 键值，那么它会以一种特殊的方式存储，使得这个属性不出现在枚举中，要通过原型链上的函数 **.getOwnPropertySymbols** 才能找到：

```js
var p = {
    foo: 16,
    [ Symbol( "bar" ) ]: "hello world",
    baz: true
};
Object.getOwnPropertyNames( p ); // [ "foo","baz" ]
//如果要取得对象的符号属性：
Object.getOwnPropertySymbols( p); // [ Symbol(bar) ]
```

对象的存储时键值对形式的存储，一般情况下 **[]** 适用于任何形式的取值，但是 **.** 并不一定适用于所有的情况，下边介绍两种情况的区别。

- 1、当所取的属性名是一个动态的值时，只能用[]不能用。
-  2、当所取得键名是一个特殊的字符串时，只能用[]不能用。

什么是特殊：当键名试一下情况下，键名是一个含有数字的字符串；当键名有特殊符号时；当键值是一个引号引起来的字符串时。

```js
let person = {
    name : '饺子',
    age : 18,
    //以下属加引号是为了将其视为整体，两种访问方式区别情况2也体现在这
    "123":"chinese", 
    "my-job" : "我的工作，中间是连字符",
    "my word" : "我的，中间含有一个空格",
    say(){
        console.log(this.name+"年龄是:" + this.age);
    } 
};

// 区别1：动态变量作为属性
// 这里定义一个myName,值为对象属性中的一个键名，在这里是直接定义的，但是在开发环境中可能是接手的其他的可变化的值
let myName = "name"
console.log(person.myName)        // undefined，使用.访问，不会取出myName的值，会在person对象上查找键名为"myName"的属性值，因为没有所以肯定也找不到了
console.log(person[myName])        // zhangsan，使用[]访问，中括号访问会把动态变量转换为实际的变量,这里就相当于person[name]

//区别2：特殊字符
//console.log(person.123)        // 报错
//console.log(person.my-job)    // 报错
//console.log(person.my word)    // 报错
console.log(person[123])        // undefined
console.log(person["123"])        // chinese，注意只有合理的数字才能省略引号
console.log(person["my-job"])   // 我的工作，中间是连字符
console.log(person["my word"])  // 我的，中间含有一个空格
```



## JavaScript 函数

函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。

```html
<script>

function myFunction()

{
  alert("Hello World!");

}

</script>
<body>
<button onclick="myFunction()">点我</button>
</body>
```

### JavaScript 函数语法

#### JS函数定义

函数就是包裹在花括号中的代码块，前面使用了关键词 function：


```
function functionname()

{

  // 执行代码

}
```
当调用该函数时，会执行函数内的代码。



函数声明后不会立即执行，会在我们需要的时候调用到。

  

分号是用来分隔可执行JavaScript语句。 由于函数声明不是一个可执行语句，所以不以分号结束。



可以在某事件发生时直接调用函数（比如当用户点击按钮时），并且可由 JavaScript 在任何位置进行调用。

JavaScript 对大小写敏感。关键词 function 必须是小写的，并且必须以与函数名称相同的大小写来调用函数。





 

调用带参数的函数

在调用函数时，您可以向其传递值，这些值被称为参数。

这些参数可以在函数中使用。

您可以发送任意多的参数，由逗号 (,) 分隔：

myFunction(argument1,argument2)

当您声明函数时，请把参数作为变量来声明：

function myFunction(var1,var2)

{

代码

}

变量和参数必须以一致的顺序出现。第一个变量就是第一个被传递的参数的给定的值，以此类推。

```html
<p>点击这个按钮，来调用带参数的函数。</p>

<button onclick="myFunction('Harry Potter','Wizard')">点击这里</button>

<script>

function myFunction(name,job){

  alert("Welcome " + name + ", the " + job);

}

</script>
```


上面的函数在按钮被点击时会提示 "Welcome Harry Potter, the Wizard"。

函数很灵活，您可以使用不同的参数来调用该函数，这样就会给出不同的消息：

```html
<button onclick="myFunction('Harry Potter','Wizard')">点击这里</button>

<button onclick="myFunction('Bob','Builder')">点击这里</button>
```

根据您点击的不同的按钮，上面的例子会提示 "Welcome Harry Potter, the Wizard" 或 "Welcome Bob, the Builder"。

带有返回值的函数

有时，我们会希望函数将值返回调用它的地方。


通过使用 return 语句就可以实现。


在使用 return 语句时，函数会停止执行，并返回指定的值。


语法

function myFunction()

{

  var x=5;

  return x;

}

上面的函数会返回值 5。

注意： 整个 JavaScript 并不会停止执行，仅仅是函数。JavaScript 将继续执行代码，从调用函数的地方。


函数调用将被返回值取代：

var myVar=myFunction();

myVar 变量的值是 5，也就是函数 "myFunction()" 所返回的值。

即使不把它保存为变量，您也可以使用返回值：

document.getElementById("demo").innerHTML=myFunction();

"demo" 元素的 innerHTML 将成为 5，也就是函数 "myFunction()" 所返回的值。

您可以使返回值基于传递到函数中的参数：


计算两个数字的乘积，并返回结果：


function myFunction(a,b)
{

  return a*b;

}


document.getElementById("demo").innerHTML=myFunction(4,3);

"demo" 元素的 innerHTML 将是：
12

在您仅仅希望退出函数时 ，也可使用 return 语句。返回值是可选的：

 

#### 函数表达式

JavaScript 函数可以通过一个表达式定义。

函数表达式可以存储在变量中：

 在函数表达式存储在变量后，变量也可作为一个函数使用：

```js
var x = function (a, b) {return a * b};
var z = x(4, 3);
```

以上函数实际上是一个 **匿名函数** (函数没有名称)。

函数存储在变量中，不需要函数名称，通常通过变量名来调用。

 上述函数以分号结尾，因为它是一个执行语句。



#### Function() 构造函数

在以上实例中，我们了解到函数通过关键字 **function** 定义。

函数同样可以通过内置的 JavaScript 函数构造器（Function()）定义。

```js
var myFunction = new Function("a", "b", "return a * b");
	var x = myFunction(4, 3);

var myFunction = function (a, b) {return a * b};
	var x = myFunction(4, 3);
```


**局部 JavaScript 变量**

在 JavaScript 函数内部声明的变量（使用 var）是局部变量，所以只能在函数内部访问它。（该变量的作用域是局部的）。

 

您可以在不同的函数中使用名称相同的局部变量，因为只有声明过该变量的函数才能识别出该变量。

 

只要函数运行完毕，本地变量就会被删除。

 

**全局 JavaScript 变量**

在函数外声明的变量是全局变量，网页上的所有脚本和函数都能访问它。

 

**JavaScript 变量的生存期**

JavaScript 变量的生命期从它们被声明的时间开始。

 

局部变量会在函数运行以后被删除。

 

全局变量会在页面关闭后被删除。

向未声明的 JavaScript 变量分配值

如果您把值赋给尚未声明的变量，该变量将被自动作为 window 的一个属性。


这条语句：

carname="Volvo";

将声明 window 的一个属性 carname。

非严格模式下给未声明变量赋值创建的全局变量，是全局对象的可配置属性，可以删除。
```js
var var1 = 1; // 不可配置全局属性
var2 = 2; // 没有使用 var 声明，可配置全局属性

console.log(this.var1); // 1
console.log(window.var1); // 1
console.log(window.var2); // 2

delete var1; // false 无法删除
console.log(var1); //1

delete var2; 
console.log(delete var2); // true
console.log(var2); // 已经删除 报错变量未定义
```

methodName: function() { }
这是对象方法的写法，通常出现在对象字面量中：

```javascript
const obj = {
  methodName: function() {
    console.log('method called');
  }
};
```
作为对象的属性存在

必须通过对象调用：obj.methodName()

this 指向调用该方法的对象

不会被提升


这是函数声明（Function Declaration）：

```javascript
function functionname() {
  console.log('function called');
}
```

// 可以在声明前调用（提升）
functionname(); // 正常工作

特点：

会被提升（hoisting），可以在声明前调用

创建全局或作用域内的命名函数

this 由调用方式决定（非箭头函数）

适合定义独立、可复用的函数


这是函数表达式（Function Expression），赋值给变量：

```javascript
const Test = function() {
  console.log('function called');
};

// 不能在赋值前调用
// Test(); // 报错：Test is not defined
```
特点：

不会被提升（只有变量声明提升，赋值不提升）

可以是匿名函数

严格模式下，变量名不会添加到函数作用域

适合作为回调函数或条件定义

核心区别对比表

| 特性      | 对象方法             | 函数声明       | 函数表达式             |
| :-------- | :------------------- | :------------- | :--------------------- |
| 提升      | ❌                    | ✅ (完整提升)   | ⚠️ (变量提升，值不提升) |
| 命名      | 作为属性名           | 有函数名       | 通常匿名               |
| 作用域    | 对象内部             | 当前作用域     | 当前作用域             |
| this 绑定 | 动态（指向调用对象） | 动态           | 动态                   |
| 使用场景  | 对象行为             | 独立功能、递归 | 回调、条件定义         |



函数内未声明即使用的变量情况：

function func(){
  undefined_var=110
}
在 func() 被第一次调用之前， undefined_var 变量是不存在的即 undefined。func() 被调用过之后，undefined_var 成为全局变量。

- **属性和方法：**定义在全局作用域中的变量和函数都会变成 window 对象的属性和方法，因此可以在调用时省略 window，直接使用变量名或函数名。

### 箭头函数



```js
// 传统定义函数方式

function Test () {
  //
}

const Test = function () {
  //
}


(参数1, 参数2, …, 参数N) => { 函数声明 }

(参数1, 参数2, …, 参数N) => 表达式(单一)
// 相当于：(参数1, 参数2, …, 参数N) =>{ return 表达式; }
```

当只有一个参数时，圆括号是可选的：

```
(单一参数) => {函数声明}
单一参数 => {函数声明}
```

没有参数的函数应该写成一对圆括号:

```
() => {函数声明}
```

```js
// 使用箭头函数定义函数时可以省略 function 关键字
const Test = (...params) => {
  //
}

// 该函数只有一个参数时可以简写成：
const Test = param => {
  return param;
}
```





有的箭头函数都没有自己的 **this**。 不适合定义一个 **对象的方法**。

当我们使用箭头函数的时候，箭头函数会默认帮我们绑定外层 this 的值，所以在箭头函数中 this 的值和外层的 this 是一样的。

箭头函数是不能提升的，所以需要在使用之前定义。

使用 **const** 比使用 **var** 更安全，因为函数表达式始终是一个常量。

如果函数部分只是一个语句，则可以省略 return 关键字和大括号 {}，这样做是一个比较好的习惯:

函数声明有**提升**（hoisting）特性，可以在声明前调用；而箭头函数（赋值给变量）没有提升，必须在定义后使用。

```js
function Test () {
  //
}

const Test = () => {
  //
}

const Test = function () {
  //
}

const Test = () => {
  //
}

// 或用 switch 写
switch(condition) {
    case 1: document.write(1); break
    case 2: document.write(2); break
    case 3: document.write(3); break
}

let obj = {
  '1' : () => { document.write(1) },   // 匿名箭头函数
  '2' : () => { document.write(2) },   // 匿名箭头函数
  '3' : () => { document.write(3) },   // 匿名箭头函数
}
obj[condition]()
```

**箭头函数只能替代"函数表达式赋值"这一种写法**，对于"函数声明"，箭头函数无法保留其提升特性，除非你接受改变代码调用顺序。



## 自调用函数

函数表达式可以 "自调用"。

自调用表达式会自动调用。

如果表达式后面紧跟 () ，则会自动调用。

不能自调用声明的函数。

通过添加括号，来说明它是一个函数表达式：

```js
(function () {
  var x = "Hello!!";   // 我将调用自己
})();
```

以上函数实际上是一个 **匿名自我调用的函数** (没有函数名)。

而**函数声明不能直接跟 `()` 进行调用**，因为它不是一个表达式，不产生值。



JavaScript 解析器在遇到 `function` 关键字在**语句起始位置**时，会将其解析为**函数声明**。

而加上括号 `( ... )` 后，函数不再处于“语句起始位置”，被解析为**函数表达式**，表达式的结果是一个函数对象，后面可以跟 `()` 调用。



- **第一段**：相当于先定义一个匿名函数（作为值），然后立刻调用它。
- **第二段**：相当于试图声明一个函数，但又想同时调用它，这在 JavaScript 语法中不允许。





箭头形式

匿名自调用表达式:

```js
(()=>{})()
```



单独拿出来（语句开头）

```
function () {
  var x = "Hello!!";   
};  // ❌ 语法错误：函数声明缺少函数名
```



**为什么报错？**

- `function` 是这一行的**第一个词**
- 解析器认为这是**函数声明**
- 函数声明必须有名字：`function name() {}`
- 没有名字 → 语法错误



自调用（语句开头 + ()）



```js
function () {
  var x = "Hello!!";  
}();  // ❌ 语法错误
```



**为什么还是报错？**

- 解析过程：
  1. 看到 `function` 在行首 → 判定为**函数声明**
  2. 发现没有函数名 → **语法错误**
  3. 代码在解析阶段就失败了，根本没机会执行到 `()`

**关键**：解析器在**编译阶段**就判定这是函数声明，发现缺少函数名，直接报错，不会把它当作表达式处理。



赋值给变量（不在语句开头）

```
a = function () {
  var x = "Hello!!";   
}();  // ✅ 可以执行
```



**为什么可以？**

- 这一行的**第一个词**是 `a`（标识符），不是 `function`
- 解析器遇到 `function` 时，它已经在**表达式上下文**中（赋值表达式的右侧）
- 所以解析为**函数表达式**
- 函数表达式可以没有名字
- 后面跟 `()` 是合法的立即调用

| 概念           | 说明                     | 示例                     |
| :------------- | :----------------------- | :----------------------- |
| **有名函数**   | 有标识符名称             | `function add(){}`       |
| **匿名函数**   | 没有标识符名称           | `function(){}`           |
| **函数声明**   | 语法形式，必须是**有名** | `function add(){}`       |
| **函数表达式** | 语法形式，可以是**匿名** | `const x = function(){}` |

## **JavaScript 作用域**

在 JavaScript 中, 对象和函数同样也是变量。

**在 JavaScript 中, 作用域为可访问变量，对象，函数的集合。**

**JavaScript 函数作用域:** 

作用域在函数内修改。



| 特性           | 函数作用域       | 块作用域        |
| :------------- | :--------------- | :-------------- |
| **作用范围**   | 整个函数内部     | `{}` 代码块内部 |
| **适用关键字** | `var`            | `let`、`const`  |
| **是否隔离**   | 函数级别隔离     | 块级别隔离      |
| **变量泄露**   | 会泄露到整个函数 | 不会泄露到块外  |





二、函数作用域（Function Scope）

特点：在函数内声明的变量，在整个函数内部都有效



```js
function demo() {
    if (true) {
        var x = 10;  // 函数作用域
    }
    console.log(x); // 10 - 变量泄露到了整个函数
}

demo();
console.log(x); // ❌ ReferenceError - 函数外访问不到
```



变量泄露示例

```js
function test() {
    for (var i = 0; i < 3; i++) {
        var message = "循环内";
    }
    console.log(i);       // 3 - i 泄露出来了
    console.log(message); // "循环内" - message 也泄露了
}

test();
```

块作用域”Blkock Scope“

特点：在 `{}` 内声明的变量，只在 `{}` 内有效

```js
function demo() {
    if (true) {
        let x = 10;  // 块作用域
        const y = 20; // 块作用域
    }
    console.log(x); // ❌ ReferenceError - 块外访问不到
    console.log(y); // ❌ ReferenceError - 块外访问不到
}
```



```
var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10
```

上面代码中，变量i是var命令声明的，在全局范围内都有效，所以全局只有一个变量i。每一次循环，变量i的值都会发生改变，而循环内被赋给数组a的函数内部的console.log(i)，里面的i指向的就是全局的i。也就是说，所有数组a的成员里面的i，指向的都是同一个i，导致运行时输出的是最后一轮的i的值，也就是 10。

循环过程中，**函数并没有执行**，只是被创建并存储到数组中。

循环结束后

```js
// 循环结束时的状态
i = 10;  // i 变成了 10

// a 数组的内容
a[0] = function() { console.log(i); }  // 指向 i
a[1] = function() { console.log(i); }  // 指向 i
a[2] = function() { console.log(i); }  // 指向 i
// ...
a[9] = function() { console.log(i); }  // 指向 i

a[6]();  // 执行 a[6] 这个函数
// 函数执行时，查找 i 的值，此时全局 i = 10
```



闭包引用的是变量本身

```js
function() {
    console.log(i);  // 这里的 i 是引用，不是值拷贝
}
```



**关键理解**：函数内部的 `i` 不是保存循环时的值，而是**保存对变量 `i` 的引用**。当函数执行时，才去查找 `i` 的当前值。







使用 let（块级作用域）



```js
var a = [];
for (let i = 0; i < 10; i++) {
    a[i] = function() {
        console.log(i);
    };
}
a[6](); // 输出 6 ✅

// 迭代0的作用域（还在内存中）
{
    let i = 0;  // 这个 i 仍然存在
    a[0] = function() { console.log(i); }  // 指向这个 i
}

// 迭代1的作用域
{
    let i = 1;  // 这个 i 仍然存在
    a[1] = function() { console.log(i); }  // 指向这个 i
}
...
```



**原理**：每次循环都会创建一个新的块级作用域，每个 `i` 都是独立的。

。你可能会问，如果每一轮循环的变量i都是重新声明的，那它怎么知道上一轮循环的值，从而计算出本轮循环的值？这是因为 JavaScript 引擎内部会记住上一轮循环的值，初始化本轮的变量i时，就在上一轮循环的基础上进行计算。

另外，for循环还有一个特别之处，就是设置循环变量的那部分是一个父作用域，而循环体内部是一个单独的子作用域。

```js
for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc
```

上面代码正确运行，输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域，有各自单独的作用域。

**为什么函数能记住自己的** i？

因为**闭包**的特性：函数会保留它被创建时的作用域链。每次迭代创建的新环境，随着函数一起被保留下来，不会被垃圾回收。

**JavaScript 局部作用域**

变量在函数内声明，变量为局部变量，具有局部作用域。

局部变量：只能在函数内部访问。

**实例**

// 此处不能调用 carName 变量
function myFunction() { var carName = "Volvo"; // 函数内可调用 carName 变量 }

因为局部变量只作用于函数内，所以不同的函数可以使用相同名称的变量。

局部变量在函数开始执行时创建，函数执行完后局部变量会自动销毁。



补充一下 return 的知识（与后面的闭包一节有联系！）：

```
var add = (function () {
    var counter = 0;
    return function () {return counter += 1;}  //这里return了一个内嵌方法，即add指向一个方法
                                                
})();
```

上面的 return 为什么不是返回一个数值呢？不懂的请学习闭包一节，笔记里有人解释。

再补充：

```
x=x+5; 
return x; 
```



**JavaScript 全局变量**

变量在函数外定义，即为全局变量。

全局变量有 **全局作用域**: 网页中所有脚本和函数均可使用。 

**实例**

var carName = " Volvo"; // 此处可调用 carName 变量 
function myFunction() { // 函数内可调用 carName 变量 }


如果变量在函数内没有声明（没有使用 var 关键字），该变量为全局变量。

以下实例中 carName 在函数内，但是为全局变量。

**实例**

// 此处可调用 carName 变量 function myFunction() 
{ carName = "Volvo"; // 此处可调用 carName 变量 }




**JavaScript 变量生命周期**

JavaScript 变量生命周期在它声明时初始化。

局部变量在函数执行完毕后销毁。

全局变量在页面关闭后销毁。



**函数参数**

函数参数只在函数内起作用，是局部变量。



**HTML 中的全局变量**

在 HTML 中, 全局变量是 window 对象，所以 window 对象可以调用函数内的未声明（未加 var)的局部变量。

**注意：**所有全局变量都属于 window 对象。

**实例**

//此处可使用 window.carName
function myFunction() { carName = "Volvo"; }

 



**你知道吗?**

你的全局变量，或者函数，可以覆盖 window 对象的变量或者函数。   
如果你显式地用 var 在全局声明一个变量，它可以覆盖 window 对象上原本就存在的属性。

局部变量，包括 window 对象可以覆盖全局变量和函数。
在局部作用域（比如函数内部）中，如果定义了一个与全局变量同名的变量，在该作用域内，局部变量会覆盖全局变量。

，意思是：即使你拿 window 这个对象作为媒介，也不能穿透局部作用域的覆盖。

例如：

```javascript
var message = "全局的";

function test() {
    var message = "局部的";
    console.log(message);      // 输出 "局部的"（局部覆盖了全局）
    console.log(window.message); // 输出 "全局的"（通过 window 依然可以访问到全局的）
}
```

test();
这里的“包括 window 对象可以覆盖”可能是指：即便你试图在函数内部通过 window.message 去修改全局变量，但如果局部存在一个同名变量，你在直接写 message 时依然只能访问到局部的。


这段描述其实是在说 JavaScript 的就近查找规则：

全局作用域：你声明的变量/函数会挂到 window 上，如果和 window 原有的属性重名，就覆盖它。

局部作用域：优先使用自己作用域内的变量，无论外面是否有个同名的全局变量（哪怕那个全局变量原本是 window 的内置属性）。

在 JavaScript 中，函数内部的局部变量通常不可以直接被外部作用域访问，但有几种方式可以将函数内的局部变量暴露给外部作用域，具体如下：

- **通过全局对象：**在函数内部，可以通过将局部变量赋值给 window 对象的属性来使其成为全局可访问的。例如，使用 **window.a     = a;** 语句，可以在函数外部通过 **window.a** 访问到这个局部变量的值。
- **定义全局变量：**在函数内部不使用 **var**、**let** 或 **const** 等关键字声明变量时，该变量会被视为全局变量，从而可以在函数外部访问。但这种做法通常不推荐，因为它可能导致意外的副作用和代码难以维护。
- **返回值：**可以通过在函数内部使用 **return** 语句返回局部变量的值，然后在函数外部接收这个返回值。这样，虽然局部变量本身不会被暴露，但其值可以通过函数调用传递到外部。
- **闭包：**JavaScript 中的闭包特性允许内部函数访问外部函数的局部变量。即使外部函数执行完毕后，其局部变量仍然可以被内部函数引用。

```js
function createCounter() {
    let count = 0;  // 局部变量
    
    // 返回内部函数，形成闭包
    return function() {
        count++;  // 内部函数可以访问外部函数的变量
        console.log(`当前计数: ${count}`);
        return count;
    };
}

let counter = createCounter();
counter();  // "当前计数: 1"
counter();  // "当前计数: 2"
counter();  // "当前计数: 3"

console.log(count);  // ReferenceError: count is not defined（无法直接访问）
```
闭包的另一个经典例子：
```js
javascript
function createPerson(name) {
    // name 是局部变量
    
    return {
        getName: function() {
            return name;  // 通过闭包访问
        },
        setName: function(newName) {
            name = newName;  // 通过闭包修改
        }
    };
}

let person = createPerson("张三");
console.log(person.getName());  // "张三"
person.setName("李四");
console.log(person.getName());  // "李四"
console.log(name);              // ReferenceError: name is not defined
```



ECMAScript 规范：

> ```
> for (let i = 0; i < 10; i++) {}
> ```
>
> 会为每次迭代创建一个新的**词法环境**（Lexical Environment），每次迭代的 `i` 都是这个新环境中的新绑定。

javascript

```
// 规范中的等价代码（简化）
{
    let i = 0;
    for (; i < 10; ) {
        {
            let i = i;  // 每次迭代创建新的绑定
            // 循环体
        }
        i++;
    }
}
```



作用域

作用域是指程序源代码中定义变量的区域。

作用域规定了如何查找变量，也就是确定当前执行代码对变量的访问权限。

JavaScript 采用词法作用域(lexical scoping)，也就是静态作用域。这意味着**函数的作用域在定义时就已经确定，而不是在调用时确定**。

让我们认真看个例子就能明白之间的区别：

**"函数定义在哪，变量就去哪找"**

```js
// 函数定义的位置决定了它的作用域链
// 而不是调用位置
var value = 1;
function foo() {
    // 这个函数定义在全局
    // 所以它的作用域链只包含全局
      console.log(value);  // 这里的 value 在定义时就已经确定了作用域
}

function bar() {
    var value = 2;
    foo();  // 调用 foo
}

bar();  // 输出 1


全局环境
┌─────────────────────────┐
│ value = 1               │
│ foo = function          │
│ bar = function          │
│                         │
│ foo 的作用域链 ─────────┼─→ [全局环境]
│ bar 的作用域链 ─────────┼─→ [全局环境]
└─────────────────────────┘
         ↑
         │
    bar 环境（调用时创建）
    ┌─────────────────────────┐
    │ value = 2               │
    │ 作用域链：bar环境 → 全局│
    └─────────────────────────┘
         ↑
         │
    foo 环境（调用时创建）
    ┌─────────────────────────┐
    │ 没有 value              │
    │ 作用域链：foo环境 → 全局│ ← 直接跳到全局，不经过 bar！
    └─────────────────────────┘
另一种情况

var value = 1;

function bar() {
    var value = 2;
    
    function foo() {  // foo 在 bar 内部定义
        console.log(value);  // 作用域链包含 bar
    }
    
    foo();  // 输出 2
}

bar();  // 输出 2
```

假设JavaScript采用静态作用域，让我们分析下执行过程：

执行 foo 函数，先从 foo 函数内部查找是否有局部变量 value，如果没有，就根据书写的位置，查找上面一层的代码，也就是 value 等于 1，所以结果会打印 1。

假设JavaScript采用动态作用域，让我们分析下执行过程：

执行 foo 函数，依然是从 foo 函数内部查找是否有局部变量 value。如果没有，就从调用函数的作用域，也就是 bar 函数内部查找 value 变量，所以结果会打印 2。

前面我们已经说了，JavaScript采用的是静态作用域，所以这个例子的结果是 1。

 

 


## JavaScript 声明提升

JavaScript 中，函数及变量的声明都将被提升到函数的最顶部。

JavaScript 中，变量可以在使用后声明，也就是变量可以先使用再声明。

以下两个实例将获得相同的结果：

```js

x = 5; // 变量 x 设置为 5

elem = document.getElementById("demo"); // 查找元素
elem.innerHTML = x;                     // 在元素中显示 x

var x; // 声明 x
```



```js

var x; // 声明 x
x = 5; // 变量 x 设置为 5

elem = document.getElementById("demo"); // 查找元素
elem.innerHTML = x;                     // 在元素中显示 x
```



要理解以上实例就需要理解 "hoisting(声明提升)"。

提升（Hoisting）是 JavaScript 默认将当前作用域提升到前面去的行为。

提升（Hoisting）应用在变量的声明与函数的声明。

声明提升：函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部。


JavaScript 初始化不会提升，JavaScript 只有声明的变量部分会提升，初始化部分不会。

以下两个实例结果结果不相同：

```js

var x = 5;
var y = 7;

elem = document.getElementById("demo");
elem.innerHTML = x + " " + y;  // 输出 "5 7"

实例2
var x = 5; // 初始化 x

elem = document.getElementById("demo"); // 查找元素
elem.innerHTML = x + " " + y;           // 显示 x 和 y



var y = 7; // 初始化 y

实例 2 的 y 输出了 undefined，这是因为变量声明 (var y) 提升了，但是初始化(y = 7) 并不会提升，所以 y 变量是一个未定义的变量。

实例 2 类似以下代码:

var x = 5; // 初始化 x
var y;     // 声明 y

elem = document.getElementById("demo"); // 查找元素
elem.innerHTML = x + " " + y;           // 显示 x 和 y

y = 7;    // 设置 y 为 7
```



在头部声明你的变量
对于大多数程序员来说并不知道 JavaScript 声明提升。

如果程序员不能很好的理解声明提升，他们写的程序就容易出现一些问题。

为了避免这些问题，通常我们在每个作用域开始前声明这些变量，这也是正常的 JavaScript 解析步骤，易于我们理解。

JavaScript 严格模式(strict mode)不允许使用未声明的变量。
在下一个章节中我们将会学习到 "严格模式(strict mode)" 。

其实主要理解 js 的解析机制就行。

遇到 script 标签的话 js 就进行预解析，将变量 var 和 function 声明提升，但不会执行 function，然后就进入上下文执行，上下文执行还是执行预解析同样操作，直到没有 var 和 function，就开始执行上下文。如:

a=5;
show();
var a;
function show(){};
预解析：

function show(){};
var a;
a=5;
show();
需要注意都是函数声明提升直接把整个函数提到执行环境的最顶端。


了以上的函数声明方式外，还可以使用匿名函数的方式。

```js
声明：

var 变量名称=function(形参列表){
  //函数体
}
调用：

变量名称(实参列表)
注意：使用匿名函数的方式不存在函数提升，因为函数名称使用变量表示的，只存在变量提升。例：

var getName=function(){
  console.log(2);
}

function getName(){
  console.log(1);
}

getName();
//结果为2
可能会有人觉得最后输出的结果是 1。但是 getName 是一个变量，因此这个变量的声明也将提升到顶部，而变量的赋值依然保留在原来的位置。需要注意的是，函数优先，虽然函数声明和变量声明都会被提升，但是函数会首先被提升，然后才是变量。

//函数、变量声明提升后
function getName(){    //函数声明提升到顶部
  console.log(1);
}

var getName;    //变量声明提升
getName = function(){    //变量赋值依然保留在原来的位置
  console.log(2);
}

getName();    // 最终输出：2
```



```js
1    function jsFun6(){  //函数声明和函数表达式的区别
2        
3        test1();//函数声明提升，在执行代码之前会先读取函数声明，不会报错
4        function test1(){//函数声明方式创建函数
5            alert("测试1");
6        }
7        
8        //test2();报错，函数还不存在
9        console.log(test2)//不会报错，变量提升只是提升变量的声明，并不会把赋值也提升上来，输出undefined
10        var test2=function(){
11            alert("测试2");
12        };//使用函数表达式创建一个匿名函数(实际是以变量test3命名的函数)
13        test2();//不会报错，以创建函数
14                    //// 执行过程：
// 步骤1：创建函数对象
// 步骤2：将函数对象赋值给 test2
// 步骤3：函数不会执行，需要手动调用 test2()
15        var test3=function(){
16            alert("测试3");
17        }();//加了括号右边立即执行,consol.log(test3)是undefined
18        //var test3 =function() { ... }();IIFE
//          ^^^^^^^^^^^^^^^^^^^^^^
//          先计算右边这个表达式
//          右边表达式的结果是函数执行后的返回值
//          然后将这个返回值赋给 test3
19        var test4 = 12;// ！注意看，一旦变量被赋值后，将会输出变量
20        //函数提升优先级高于变量提升，所以函数先提升，然后变量提升覆盖之前的函数声明，表                
21        //现为变量
22        function test4() {
23            alert("测试4");               
24        }
25        console.log(test4); //12
26    
27        var test5="test5_1";
28        (function(){
29            //js中的变量搜索顺序：找变量时，先找局部变量，如果没有局部变量；再找全局变量。
30        alert(test5);//此时的test5为局部变量的提升，undefined
31        var test5="test5_2";
32        })();
33                    
34    }
35    jsFun6();
```
首先 JS 在执行之前会有一个预编译过程，变量提升和函数提升就是发生在这里。

在执行 jsFun6 函数时首先会创建一个 AO 对象（Activetion Object 执行期上下文）。

AO{

}
然后会将形参和变量声明作为 AO 对象的属性名，属性的值为 undefined。

我们可以看到 jsFun6 函数里没有形参，但是有以下变量声明：

第10行的 var test2
第15行的 var test3
第19行的 var test4
第27行的 var test5
所以 AO 对象现在是这样：

AO{
    test2：undefined
    test3：undefined
    test4：undefined
    test5：undefined
}
然后会将实参的值传递给形参（当前没有形参所以略过）。

再找到函数声明作为AO的属性名，属性的值为函数声明的函数体（这里就是函数声明提升）。

我们可以看到 jsFun6 函数里有以下的函数声明：

第4行 function test1(){}
第22行 function test4(){}
这里的第 10 行和第 15 行是函数表达式不是函数声明，是函数表达式赋值。


所以 AO 对象现在是这样：

AO{
    test2：undefined,
    test3：undefined,
    test4：function test4(){alert("测试4");},
    test5：undefined,
    test1：function test1(){alert("测试1");}
}
这里需要注意因为之前 AO 对象里已经有 test4 属性了所以 AO 对象里原来由变量声明时得到的 test4 属性会被现在函数声明的 test4 覆盖。

到这里预编译就完成了开始执行。

执行时会先去查找 AO 对象，如果没找到就会去 GO(Global Object)对象里查找(AO 对象相当于局部变量，GO 对象相当于全局变量)。

当执行第 3 行时在 AO 里找到 test1 执行，弹出提示框显示提示信息“测试1”。

第 4 行已经提升了所以可以略过，下面其他的类似行与此相同。

执行第 8 行时, 在 AO 里找到 test2，并作为函数运行，由于 AO 里的 test2 的值是 undefined 所以执行会报错。

第 9 行输出 test2 所以输出的是 undefined。

第 10 行将函数体赋值给 test2，当前 AO 变成：

AO{
    test2：function(){alert("测试2");},
    test3：undefined,
    test4：function test4(){alert("测试4");},
    test5：undefined,
    test1：function test1(){alert("测试1");}
}
所以第 13 行执行时在 AO 里能找到 test2 并作为函数运行，弹出提示框显示提示信息“测试2”。

AO 显示的是函数对象在控制台/调试器中的字符串表示形式，这个格式由 JavaScript 引擎的 toString() 方法决定。



```js
1. 函数声明 的 toString()
2. 匿名函数表达式 的 toString()
function test4() {
    alert("测试4");
}

console.log(test4.toString());
// 输出：
// function test4() {
//     alert("测试4");
// }

var test2 = function() {
    alert("测试2");
};

console.log(test2.toString());
// 输出：
// function() {
//     alert("测试2");


```


第 15 行由于用于赋值的函数体后面加了 () 变成立即执行函数所以函数会立即运行（这里会弹出提示框显示提示信息“测试3”）然后将返回值赋值给 test3，由于函数没有返回值所以是 undefined，也就导致了 AO 里的 test3 的属性值还是 undefined（大家可以在第18行输出一下 test3，会发现还是输出 undefined）。

第 19 行将 12 赋值给 test4，将原来 test4 的属性值覆盖了，AO 对象变为：

AO{
    test2：function(){alert("测试2");},
    test3：undefined,
    test4：12,
    test5：undefined,
    test1：function test1(){alert("测试1");}
}
所以在第 25 行会输出 12（如果在这里再把 test4 作为函数去运行就会报错）。

第 27 行将 test5_1 赋值给 test5。

第 28 行是立即执行函数，会立即执行。

在执行之前会进行预编译所以这个函数又会创建一个 属于他自己的 AO 对象，为了区分我们将他标识为 AO2。

AO2{

}
然后将形参和变量声明作为 AO 对象的属性名，属性的值为 undefined。

AO2{
    test5：undefined
}
然后进行实参形参统一，以及函数声明提升（由于没有所以略过）。
执行第 30 行时在 AO2 里找到了 test5，属性值为 undefined。

所以会弹出提示框显示提示信息 “undefined”。

执行第 31 行是将 test5_2 赋值给 test5。

所以 AO2 变成了：

AO2{
    test5：test5_2
}

变量的声明和函数的声明提升，提升的时机发生在预解析过程中。
预解析过程也就是创建 AO（Activation Object） 的过程。

创建AO过程：

 创建 AO 对象。
 将形参和函数内变量声明作为对象的属性名，属性值统一为 undefined。
 将实参赋值给形参。
 找函数内的函数声明作为对象的属性名，属性值为函数体。


 计算机执行的时候会把未声明就使用的变量隐式的放到代码的最顶端。需要注意的是变量虽然发生了提升，但是给变量赋的值是不会随之提升的，所以就会得到结果undefined。

什么是函数提升？

   与变量提升的意思差不多，先使用函数，后再声明函数，这种违背逻辑的事情在JavaScript中是允许的，这门语言就是这么灵活。

   与变量提升不同的是，函数的返回值也会随之提升，所以你会发现在\<script>标签中的任何地方都能调用函数并且使用函数的返回值。



**函数是对象**

在 JavaScript 中使用 **typeof** 操作符判断函数类型将返回 "function" 。

但是JavaScript 函数描述为一个对象更加准确。

JavaScript 函数有 **属性** 和 **方法**。

arguments.length 属性返回函数调用过程接收到的参数个数：



function myFunction(a, b) {
  return arguments.length;
}

toString() 方法将函数作为一个字符串返回:



function myFunction(a, b) {
  return a * b;
}

var txt = myFunction.toString();
