title: JavaScript基础认识与基本用法
date: 2023-01-02 22:02:13
tags: javaScript
categories: 前端学习
description: Js的基本认识及使用
top_img: https://skyme-1307417630.cos.ap-chengdu.myqcloud.com/SOS_sea.jpg
cover: https://skyme-1307417630.cos.ap-chengdu.myqcloud.com/20220411_140045.jpg

JavaScript输出

JavaScript 可以通过不同的方式来输出数据:

  • 使用 window.alert() 弹出警告框。

  • 使用 document.write() 方法将内容写到 HTML 文档中。

  • 使用 innerHTML 写入到 HTML 元素。

  • 使用 console.log() 写入到浏览器的控制台。

document.getElementById("demo").innerHTML = "段落已修改。";

document.write(Date());

语法

JavaScript 字面量

在编程语言中,一般固定值称为字面量,如 3.14。

  • 数字(Number)字面量 可以是整数或者是小数,或者是科学计数(e)。

    3.14、1001

  • 字符串(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 来定义变量, 使用等号来为变量赋值:

var x, length
x = 5
length = 6

变量可以通过变量名访问

JavaScript 操作符

类型

实例

描述

赋值,算术和位运算符

= + - * /

在 JS 运算符中描述

条件,比较及逻辑运算符

== != < >

在 JS 比较运算符中描述

JavaScript 关键字

和其他任何编程语言一样,JavaScript 保留了一些关键字为自己所用。

如:var 关键字告诉浏览器创建一个新的变量

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 通过对象字面量赋值

JavaScript 函数

JavaScript 语句可以写在函数内,函数可以重复引用:

引用一个函数 = 调用函数(执行函数内的语句)。

function myFunction(a, b) {
  return a * b;                // 返回 a 乘以 b 的结果
}

JavaScript 字母大小写

JavaScript 对大小写是敏感的。

JavaScript 字符集

JavaScript 使用 Unicode 字符集。

语句

JavaScript 语句向浏览器发出的命令。语句的作用是告诉浏览器该做什么。

JavaScript 语句

document.getElementById("demo").innerHTML = "你好 Dolly";

分号 ;

在 JavaScript 中,用分号来结束语句是可选的(可以使用也可以不使用,尽量使用)

JavaScript 代码

JavaScript 代码是 JavaScript 语句的序列。

浏览器按照编写顺序依次执行每条语句。

JavaScript 代码块

JavaScript 可以分批地组合起来。

代码块以左花括号开始,以右花括号结束。

代码块的作用是一并地执行语句序列。

function myFunction()
{
    document.getElementById("demo").innerHTML="你好Dolly";
    document.getElementById("myDIV").innerHTML="你最近怎么样?";
}

JavaScript 语句标识符

和常规大多数编程语言一样(更像java一些):break、for、continue、throw、catch等都是语句标识符

注释

单行注释

//这是注释 即可

多行注释

/* 下面的这些代码会输出
一个标题和一个段落 
并将代表主页的开始 */

变量

一条语句,多个变量

您可以在一条语句中声明很多变量。该语句以 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;

ES6及之后版本的声明模式

ES2015(ES6) 新增加了两个重要的 JavaScript 关键字: letconst

  • let 声明的变量只在 let 命令所在的代码块内有效。

  • const 声明一个只读的常量,一旦声明,常量的值就不能改变。

区别:

  • 在 ES6 前,JS 只有两种作用域: 全局变量函数内的局部变量

    这就意味着:使用 var 关键字声明的变量不具备块级作用域的特性,它在 {} 外依然能被访问到。

    var x = 10;
    // 这里输出 x 为 10
    { 
        var x = 2;
        // 这里输出 x 为 2
    }
    // 这里输出 x 为 2
  • 而 ES6 可以使用 let 关键字来实现 块级作用域 。let 声明的变量只在 let 命令所在的代码块 {} 内有效,在 {} 之外不能访问。

    var x = 10;
    // 这里输出 x 为 10
    { 
        let x = 2;
        // 这里输出 x 为 2
    }
    // 这里输出 x 为 10

    所以主要区别在于:

    • 使用 var 关键字:

    var i = 5;
    for (var i = 0; i < 10; i++) {
        // 一些代码...
    }
    // 这里输出 i 为 10  
    • 使用 let 关键字:

    let i = 5;
    for (let i = 0; i < 10; i++) {
        // 一些代码...
    }
    // 这里输出 i 为 5

    局部变量

    在函数体外或代码块外使用 varlet 关键字声明的变量也有点类似。

    它们的作用域都是 全局的

    全局变量

    在函数体内使用 varlet 关键字声明的变量有点类似。

    它们的作用域都是 局部的

HTML 代码中使用全局变量

在 JavaScript 中, 全局作用域是针对 JavaScript 环境。

在 HTML 中, 全局作用域是针对 window 对象。

  • 使用 var 关键字声明的全局作用域变量属于 window 对象

  • 使用 let 关键字声明的全局作用域变量不属于 window 对象

重置变量

  • 使用 var 关键字声明的变量在任何地方都可以修改

  • 在相同的作用域或块级作用域中,不能使用 let 关键字来重置 var 关键字声明的变量

  • 在相同的作用域或块级作用域中,不能使用 let 关键字来重置 let 关键字声明的变量

  • 在相同的作用域或块级作用域中,不能使用 var 关键字来重置 let 关键字声明的变量

  • let 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的

变量提升

JavaScript 中,var 关键字定义的变量可以在使用后声明,也就是变量可以先使用再声明

const 关键字

const定义常量与使用let 定义的变量相似:

  • 二者都是块级作用域

  • 且都不能和它所在作用域内的其他变量或函数拥有相同的名称

两者还有以下两点区别:

  • const声明的常量必须初始化,而let声明的变量不用

  • const 定义常量的值不能通过再赋值修改,也不能再次声明。而 let 定义的变量值可以修改。

const 的本质: const 定义的变量并非常量,并非不可变,它定义了一个常量引用一个值。使用 const 定义的对象或者数组,其实是可变的。下面的代码并不会报错:

// 创建常量对象
const car = {type:"Fiat", model:"500", color:"white"};
 
// 修改属性:
car.color = "red";
 
// 添加属性
car.owner = "Johnson";

但是我们不能对常量对象重新赋值:

const car = {type:"Fiat", model:"500", color:"white"};

car = {type:"Volvo", model:"EX60", color:"red"};    // 错误

以下实例修改常量数组:

const cars = ["Saab", "Volvo", "BMW"];

cars = ["Toyota", "Volvo", "Audi"];    // 错误

数据类型

值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol。

引用数据类型(对象类型):对象(Object)、数组(Array)、函数(Function),还有两个特殊的对象:正则(RegExp)和日期(Date)。

JavaScript 拥有动态类型

JavaScript 拥有动态类型。这意味着相同的变量可用作不同的类型

JavaScript 对象

对象由花括号分隔。在括号内部,对象的属性以名称和值对的形式 (name : value) 来定义。属性由逗号分隔:

var person={
firstname : "John",
lastname  : "Doe",
id        :  5566
};

上面例子中的对象 (person) 有三个属性:firstname、lastname 以及 id。

Undefined 和 Null

Undefined 这个值表示变量不含有值。

可以通过将变量的值设置为 null 来清空变量。

声明变量类型

当您声明新变量时,可以使用关键词 "new" 来声明其类型:

var carname=new String;
var x=      new Number;
var y=      new Boolean;
var cars=   new Array;
var person= new Object;

JavaScript 变量均为对象。当您声明一个变量时,就创建了一个新的对象。

访问对象方法

你可以使用以下语法创建对象方法:

var person = {
    firstName: "John",
    lastName : "Doe",
    id : 5566,
    fullName : function() 
	{
       return this.firstName + " " + this.lastName;
    }
};

你可以使用以下语法访问对象方法:

person.fullName();

函数

函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。

JavaScript 函数语法

函数就是包裹在花括号中的代码块,前面使用了关键词 function:

function functionname()
{
    // 执行代码
}

当调用该函数时,会执行函数内的代码。

可以在某事件发生时直接调用函数(比如当用户点击按钮时),并且可由 JavaScript 在任何位置进行调用。

带有返回值的函数

与java一致

function myFunction()
{
    var x=5;
    return x;
}

var myVar=myFunction();   //用于接受函数返回值

JavaScript 变量的生存期

JavaScript 变量的生命期从它们被声明的时间开始。

局部变量会在函数运行以后被删除。

全局变量会在页面关闭后被删除。

向未声明的 JavaScript 变量分配值

如果您把值赋给尚未声明的变量,该变量将被自动作为 window 的一个属性。

这条语句:

carname="Volvo";

将声明 window 的一个属性 carname。

非严格模式下给未声明变量赋值创建的全局变量,是全局对象(即window)的可配置属性,可以删除。

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); // 已经删除 报错变量未定义

HTML 中的全局变量

在 HTML 中, 全局变量是 window 对象,所以window 对象可以调用函数内对局部变量。

在 HTML 中, 所有全局变量都会成为 window 对象的变量

注意:所有数据变量都属于 window 对象。

<body>

<p>
在 HTML 中, 所有全局变量都会成为 window 变量。
</p>
<p id="demo"></p>
    
<script>
myFunction();
document.getElementById("demo").innerHTML =
	"我可以显示 " + window.carName;
function myFunction() 
{
    carName = "Volvo";
}
</script>

</body>

事件

HTML 事件是发生在 HTML 元素上的事情。

当在 HTML 页面中使用 JavaScript 时, JavaScript 可以触发这些事件。

HTML 事件

HTML 事件可以是浏览器行为,也可以是用户行为。

以下是 HTML 事件的实例:

  • HTML 页面完成加载

  • HTML input 字段改变时

  • HTML 按钮被点击

通常,当事件发生时,你可以做些事情。

在事件触发时 JavaScript 可以执行一些代码。

HTML 元素中可以添加事件属性,使用 JavaScript 代码来添加 HTML 元素。

如:

在以下实例中,按钮元素中添加了 onclick 属性 (并加上代码):

<body>

<button onclick="getElementById('demo').innerHTML=Date()">现在的时间是?</button>
<p id="demo"></p>

</body>

以上实例中,JavaScript 代码将修改 id="demo" 元素的内容,将id = ”demo“的元素 变为 时间的文本信息。

在下一个实例中,代码将修改自身元素的内容 (使用 this.innerHTML):

<body>

<button onclick="this.innerHTML=Date()">现在的时间是?</button>

</body>

即点击后:按钮本身 变为 时间的文本信息。

常见的HTML事件

事件

描述

onchange

HTML 元素改变

onclick

用户点击 HTML 元素

onmouseover

鼠标指针移动到指定的元素上时发生

onmouseout

用户从一个 HTML 元素上移开鼠标时发生

onkeydown

用户按下键盘按键

onload

浏览器已完成页面的加载

【更多事件列表: JavaScript 参考手册 - HTML DOM 事件

JavaScript 可以做什么?

事件可以用于处理表单验证,用户输入,用户行为及浏览器动作:

  • 页面加载时触发事件

  • 页面关闭时触发事件

  • 用户点击按钮执行动作

  • 验证用户输入内容的合法性

  • 等等 ...

可以使用多种方法来执行 JavaScript 事件代码:

  • HTML 事件属性可以直接执行 JavaScript 代码

  • HTML 事件属性可以调用 JavaScript 函数

  • 你可以为 HTML 元素指定自己的事件处理程序

  • 你可以阻止事件的发生。

  • 等等 ...

特殊字符处理(转义)

在 JavaScript 中,字符串写在单引号或双引号中。

因为这样,以下实例 JavaScript 无法解析:

"We are the so-called "Vikings" from the north."

字符串 "We are the so-called " 被截断。

如何解决以上的问题呢?可以使用反斜杠 () 来转义 "Vikings" 字符串中的双引号,如下:

"We are the so-called \"Vikings\" from the north."

反斜杠是一个转义字符。 转义字符将特殊字符转换为字符串字符

转义字符 \ 可以用于转义撇号,换行,引号,等其他特殊字符。

下表中列举了在字符串中可以使用转义字符转义的特殊字符:

代码

输出

'

单引号

"

双引号

\

反斜杠

\n

换行

\r

回车

\t

tab(制表符)

\b

退格符

\f

换页符

严格模式

"use strict" 的目的是指定代码在严格条件下执行。

严格模式下你不能使用未声明的变量。

严格模式通过在脚本或函数的头部添加 use strict; 表达式来声明。

为什么使用严格模式:

  • 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;

  • 消除代码运行的一些不安全之处,保证代码运行的安全;

  • 提高编译器效率,增加运行速度;

  • 为未来新版本的 Javascript 做好铺垫。

"严格模式"体现了Javascript更合理、更安全、更严谨的发展方向,包括IE 10在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱它。

另一方面,同样的代码,在"严格模式"中,可能会有不一样的运行结果;一些在"正常模式"下可以运行的语句,在"严格模式"下将不能运行。掌握这些内容,有助于更细致深入地理解Javascript。

表单

JavaScript 表单验证

以下实例代码用于判断表单字段(fname)值是否存在, 如果不存在,就弹出信息,阻止表单提交:

function validateForm() {
    var x = document.forms["myForm"]["fname"].value;
    if (x == null || x == "") {
        alert("需要输入名字。");
        return false;
    }
}

以上 JavaScript 代码可以通过 HTML 代码来调用:

<form name="myForm" action="demo_form.php" onsubmit="return validateForm()" method="post">
名字: <input type="text" name="fname">
<input type="submit" value="提交">
</form>

HTML 表单自动验证

HTML 表单验证也可以通过浏览器来自动完成。

如果表单字段 (fname) 的值为空, required 属性会阻止表单提交:

<form action="demo_form.php" method="post">
  <input type="text" name="fname" required="required">
  <input type="submit" value="提交">
</form>

数据验证

数据验证用于确保用户输入的数据是有效的。

典型的数据验证有:

  • 必需字段是否有输入?

  • 用户是否输入了合法的数据?

  • 在数字字段是否输入了文本?

大多数情况下,数据验证用于确保用户正确输入数据。

数据验证可以使用不同方法来定义,并通过多种方式来调用。

  • 服务端数据验证是在数据提交到服务器上后再验证。

  • 客户端数据验证是在数据发送到服务器前,在浏览器上完成验证。

HTML 约束验证

HTML5 新增了 HTML 表单的验证方式:约束验证(constraint validation)。

约束验证是表单被提交时浏览器用来实现验证的一种算法。

HTML 约束验证基于:

  • HTML 输入属性

  • CSS 伪类选择器

  • DOM 属性和方法

    简单来说就是:html、css提供了一些属性来用于表单验证

表单验证

JavaScript 可用来在数据被送往服务器前对 HTML 表单中的这些输入数据进行验证。

表单数据经常需要使用 JavaScript 来验证其正确性:

  • 验证表单数据是否为空?

  • 验证输入是否是一个正确的email地址?

  • 验证日期是否输入正确?

  • 验证表单输入内容是否为数字型?

  • 。。等等自定义的需要验证的内容

    例如:

    //html调用js中方法
    <form name="myForm" action="demo-form.php" onsubmit="return validateForm()" method="post">
    姓: <input type="text" name="fname">
    <input type="submit" value="提交">
    </form>
        
        
    //js中定义方法
    function validateForm()
    {
      var x=document.forms["myForm"]["fname"].value;
      if (x==null || x=="")
      {
        alert("姓必须填写");
        return false;
      }

    表单验证API

方法中的this

面向对象语言中 this 表示当前对象的一个引用。

但在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变。

  • 在方法中,this 表示该方法所属的对象。

  • 如果单独使用,this 表示全局对象。

  • 在函数中,this 表示全局对象。

  • 在函数中,在严格模式下,this 是未定义的(undefined)。

  • 在事件中,this 表示接收事件的元素。

  • 类似 call() 和 apply() 方法可以将 this 引用到任何对象。

对象方法中绑定

下面实例中,this 是 person 对象,person 对象是函数的所有者:

var person = {
  firstName  : "John",
  lastName   : "Doe",
  id         : 5566,
  myFunction : function() {
    return this;
  }
};

下面实例中:this.firstName 表示 this (person) 对象的 firstName 属性:

var person = {
  firstName: "John",
  lastName : "Doe",
  id       : 5566,
  fullName : function() {
    return this.firstName + " " + this.lastName;
  }
};

显式函数绑定

(和上面一个意思,只不过对象方法中绑定是隐性的,返回一个对象)

在 JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。

在下面实例中,当我们使用 person2 作为参数来调用 person1.fullName 方法时, this 将指向 person2, 即便它是 person1 的方法:

var person1 = {
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
}
var person2 = {
  firstName:"John",
  lastName: "Doe",
}
person1.fullName.call(person2);  
// 返回 "John Doe"

JSON

JSON 本质上可以说:是 JS 对象的字符串表示法。它使用文本表示一个 JS 对象的信息,(JSON)本质是一个字符串。

更多详情见:【 JSON 教程

  • JSON 是用于存储和传输数据的格式

  • JSON 通常用于服务端向网页传递数据

  • JSON是独立的语言

JSON 使用 JavaScript 语法,但是 JSON 格式仅仅是一个文本。

文本可以被任何编程语言读取及作为数据格式传递。

JSON 格式化后为 JavaScript 对象

JSON 格式在语法上与创建 JavaScript 对象代码是相同的。

由于它们很相似,所以 JavaScript 程序可以很容易的将 JSON 数据转换为 JavaScript 对象

JSON 语法规则

  • 数据为 键/值 对。

  • 数据由逗号分隔。

  • 大括号保存对象

  • 方括号保存数组

JSON 数据 - 一个名称对应一个值

JSON 数据格式为 键/值 对,就像 JavaScript 对象属性。

键/值对包括字段名称(在双引号中),后面一个冒号,然后是值:

"name":"Runoob"

JSON 数组

JSON 数组保存在中括号内。

就像在 JavaScript 中, 数组可以包含对象:

"sites":[
    {"name":"Runoob", "url":"www.runoob.com"}, 
    {"name":"Google", "url":"www.google.com"},
    {"name":"Taobao", "url":"www.taobao.com"}
]

在以上实例中,对象 "sites" 是一个数组,包含了三个对象。

每个对象为站点的信息(网站名和网站地址)。

JSON 字符串转换为 JavaScript 对象

通常我们从服务器中读取 JSON 数据,并在网页中显示数据。

简单起见,我们网页中直接设置 JSON 字符串

首先,创建 JavaScript 字符串,字符串为 JSON 格式的数据:

var text = '{ "sites" : [' +
'{ "name":"Runoob" , "url":"www.runoob.com" },' +
'{ "name":"Google" , "url":"www.google.com" },' +
'{ "name":"Taobao" , "url":"www.taobao.com" } ]}';

然后,使用 JavaScript 内置函数 JSON.parse() 将字符串转换为 JavaScript 对象:

var obj = JSON.parse(text);

最后,在你的页面的 js 部分使用新的 JavaScript 对象:

var text = '{ "sites" : [' +
    '{ "name":"Runoob" , "url":"www.runoob.com" },' +
    '{ "name":"Google" , "url":"www.google.com" },' +
    '{ "name":"Taobao" , "url":"www.taobao.com" } ]}';
    
obj = JSON.parse(text);
document.getElementById("demo").innerHTML = obj.sites[1].name + " " + obj.sites[1].url;

相关函数:

函数

描述

JSON.parse()

用于将一个 JSON 字符串转换为 JavaScript 对象。

JSON.stringify()

用于将 JavaScript 值转换为 JSON 字符串。

异步编程

简单来理解就是:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。

什么时候用异步编程

现在有一个按钮,如果我们设置它的 onclick 事件为一个死循环,那么当这个按钮按下,整个网页将失去响应。

为了避免这种情况的发生,我们常常用子线程来完成一些可能消耗时间足够长以至于被用户察觉的事情,比如读取一个大文件或者发出一个网络请求。因为子线程独立于主线程,所以即使出现阻塞也不会影响主线程的运行。但是子线程有一个局限:一旦发射了以后就会与主线程失去同步,我们无法确定它的结束,如果结束之后需要处理一些事情,比如处理来自服务器的信息,我们是无法将它合并到主线程中去的。

为了解决这个问题,JavaScript 中的异步操作函数往往通过 回调函数 来实现异步任务的结果处理。

回调函数

回调函数就是一个函数,它是在我们启动一个异步任务的时候就告诉它:等你完成了这个任务之后要干什么。这样一来主线程几乎不用关心异步任务的状态了,他自己会善始善终。

function print() {
    document.getElementById("demo").innerHTML="RUNOOB!";
}
setTimeout(print, 3000);

这段程序中的 setTimeout 就是一个消耗时间较长(3 秒)的过程,它的第一个参数是个回调函数,第二个参数是毫秒数,这个函数执行之后会产生一个子线程,子线程会等待 3 秒,然后执行回调函数 "print",在命令行输出 "RUNOOB!"。

当然,JavaScript 语法十分友好,我们不必单独定义一个函数 print ,我们常常将上面的程序写成:

setTimeout(function () {
    document.getElementById("demo").innerHTML="RUNOOB!";
}, 3000);

注意:既然 setTimeout 会在子线程中等待 3 秒,在 setTimeout 函数执行之后主线程并没有停止,所以:

setTimeout(function () {
    document.getElementById("demo1").innerHTML="RUNOOB-1!";
}, 3000);
document.getElementById("demo2").innerHTML="RUNOOB-2!";

这段程序的执行结果是:

RUNOOB-1!
RUNOOB-2!

异步 AJAX

除了 setTimeout 函数以外,异步回调广泛应用于 AJAX 编程。有关于 AJAX 详细请参见:Ajax教程

XMLHttpRequest 常常用于请求来自远程服务器上的 XML 或 JSON 数据。一个标准的 XMLHttpRequest 对象往往包含多个回调:

var xhr = new XMLHttpRequest();
 
xhr.onload = function () {
    // 输出接收到的文字数据
    document.getElementById("demo").innerHTML=xhr.responseText;
}
 
xhr.onerror = function () {
    document.getElementById("demo").innerHTML="请求出错";
}
 
// 发送异步 GET 请求
xhr.open("GET", "https://www.runoob.com/try/ajax/ajax_info.txt", true);
xhr.send();

XMLHttpRequest 的 onload 和 onerror 属性都是函数,分别在它请求成功和请求失败时被调用。如果你使用完整的 jQuery 库,也可以更加优雅的使用异步 AJAX:

$.get("https://www.runoob.com/try/ajax/demo_test.php",function(data,status){
    alert("数据: " + data + "\n状态: " + status);
});

JavaScript Promise

Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务。

构造 Promise

现在我们新建一个 Promise 对象:

new Promise(function (resolve, reject) {
    // 要做的事情...
});

通过新建一个 Promise 对象好像并没有看出它怎样 "更加优雅地书写复杂的异步任务"。我们之前遇到的异步任务都是一次异步,如果需要多次调用异步函数呢?

例如,如果我想分三次输出字符串,第一次间隔 1 秒,第二次间隔 4 秒,第三次间隔 3 秒:

setTimeout( function () {
    console.log("First");
    setTimeout( function () {
        console.log("Second");
        setTimeout( function () {
            console.log("Third");
        }, 3000 );
    }, 4000 );
}, 1000 );

这段程序实现了这个功能,但是它是用 "函数瀑布" 来实现的。可想而知,在一个复杂的程序当中,用 "函数瀑布" 实现的程序无论是维护还是异常处理都是一件特别繁琐的事情,而且会让缩进格式变得非常冗赘。

现在我们用 Promise 来实现同样的功能:

new Promise(function (resolve, reject) {
    setTimeout(function () {
        console.log("First");
        resolve();
    }, 1000);
}).then(function () {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log("Second");
            resolve();
        }, 4000);
    });
}).then(function () {
    setTimeout(function () {
        console.log("Third");
    }, 3000);
});

Promise 将嵌套格式的代码变成了顺序格式的代码。

Promise类 的使用

下面我们通过剖析这段 Promise "计时器" 代码来讲述 Promise 的使用:

Promise 构造函数只有一个参数,是一个函数,这个函数在构造之后会直接被异步运行,所以我们称之为起始函数起始函数包含两个参数 resolve 和 reject。

当 Promise 被构造时,起始函数会被异步执行:

new Promise(function (resolve, reject) {
    console.log("Run");
});

这段程序会直接输出 Run

resolve 和 reject 都是函数,其中调用 resolve 代表一切正常,reject 是出现异常时所调用的:

new Promise(function (resolve, reject) {
    var a = 0;
    var b = 1;
    if (b == 0) reject("Divide zero");
    else resolve(a / b);
}).then(function (value) {
    console.log("a / b = " + value);
}).catch(function (err) {
    console.log(err);
}).finally(function () {
    console.log("End");
});

这段程序执行结果是:

a / b = 0
End

Promise 类有 .then() .catch() 和 .finally() 三个方法,这三个方法的参数都是一个函数,.then() 可以将参数中的函数添加到当前 Promise 的正常执行序列,.catch() 则是设定 Promise 的异常处理序列,.finally() 是在 Promise 执行的最后一定会执行的序列。 .then() 传入的函数会按顺序依次执行,有任何异常都会直接跳到 catch 序列:

new Promise(function (resolve, reject) {
    console.log(1111);
    resolve(2222);
}).then(function (value) {
    console.log(value);
    return 3333;
}).then(function (value) {
    console.log(value);
    throw "An error";
}).catch(function (err) {
    console.log(err);
});

执行结果:

1111
2222
3333
An error

resolve() 中可以放置一个 参数 ,用于向下一个 then 传递一个值,then 中的函数也可以返回一个值传递给 then。但是,如果 then 中返回的是一个 Promise 对象,那么下一个 then 将相当于对这个返回的 Promise 进行操作,这一点从刚才的计时器的例子中可以看出来。

reject() 参数中一般会传递一个异常给之后的 catch 函数用于处理异常。

但是请注意以下两点:

  • resolve 和 reject 的作用域只有起始函数,不包括 then 以及其他序列;

  • resolve 和 reject 并不能够使起始函数停止运行,别忘了 return。

Promise 函数

上述的 "计时器" 程序看上去比函数瀑布还要长,所以我们可以将它的核心部分写成一个 Promise 函数:

function print(delay, message) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(message);
            resolve();
        }, delay);
    });
}

然后我们就可以放心大胆的实现程序功能了:

print(1000, "First").then(function () {
    return print(4000, "Second");
}).then(function () {
    print(3000, "Third");
});

这种 返回值为一个 Promise 对象 的函数称作 Promise 函数,它常常用于开发基于异步操作的库

异步函数

异步函数(async function)是 ECMAScript 2017 (ECMA-262) 标准的规范。

在 Promise 中我们编写过一个 Promise 函数:

function print(delay, message) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(message);
            resolve();
        }, delay);
    });
}

然后我们用不同的时间间隔输出了三行文本:

print(1000, "First").then(function () {
    return print(4000, "Second");
}).then(function () {
    print(3000, "Third");
});

我们可以通过定义异步函数并在其中使用await( )函数指令,将这段代码变得更好看:

async function asyncFunc() {
    await print(1000, "First");
    await print(4000, "Second");
    await print(3000, "Third");
}
asyncFunc();

异步函数 async function 中可以使用 await 指令,await 指令后必须跟着一个 Promise,异步函数会在这个 Promise 运行中暂停,直到其运行结束再继续运行。

异步函数实际上原理与 Promise 原生 API 的机制是一模一样的,只不过更便于程序员阅读。

处理异常的机制将用 try-catch 块实现:

async function asyncFunc() {
    try {
        await new Promise(function (resolve, reject) {
            throw "Some error"; // 或者 reject("Some error")
        });
    } catch (err) {
        console.log(err);
        // 会输出 Some error
    }
}
asyncFunc();

如果 Promise 有一个正常的返回值,await 语句也会返回它:

async function asyncFunc() {
    let value = await new Promise(
        function (resolve, reject) {
            resolve("Return value");
        }
    );
    console.log(value);
}
asyncFunc();

程序会输出:

Return value

JS函数

Function() 构造函数

在以上实例中,我们了解到函数通过关键字 function 定义。

函数同样可以通过内置的 JavaScript 函数构造器(Function())定义:

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

函数提升(Hoisting)

在之前的教程中我们已经了解了 "hoisting(提升)"。

提升(Hoisting)是 JavaScript 默认将当前作用域提升到前面去的行为。

提升(Hoisting)应用在变量的声明与函数的声明。

因此,函数可以在声明之前调用:

myFunction(5);

function myFunction(y) {
    return y * y;
}

使用表达式定义函数时无法提升。

函数可作为一个值使用

JavaScript 函数作为一个值使用:

function myFunction(a, b) {
    return a * b;
}

var x = myFunction(4, 3);

JavaScript 函数可作为表达式使用:

function myFunction(a, b) {
    return a * b;
}

var x = myFunction(4, 3) * 2;

函数是对象

JavaScript 函数描述为一个对象更加准确。

JavaScript 函数有 属性方法

arguments.length 属性返回函数调用过程接收到的参数个数:

function myFunction(a, b) {
    return arguments.length;
}

toString() 方法将函数作为一个字符串返回:

function myFunction(a, b) {
    return a * b;
}

var txt = myFunction.toString();

箭头函数

ES6 新增了箭头函数,极大简化了函数的声明及使用:

// ES5
var x = function(x, y) {
     return x * y;
}
 
// ES6
const x = (x, y) => x * y;

当只有一个参数时,圆括号是可选的:

(单一参数) => {函数声明}
单一参数 => {函数声明}

没有参数的函数应该写成一对圆括号:

() => {函数声明}

当我们使用箭头函数的时候,箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的。

箭头函数是不能提升的,所以需要在使用之前定义。

使用 const 比使用 var 更安全,因为函数表达式始终是一个常量。

如果函数部分只是一个语句,则可以省略 return 关键字和大括号 {}

函数参数

JavaScript 函数对参数的值没有进行任何的检查。

ES6 函数可以自带参数

ES6 支持函数带有默认参数,就判断 undefined 和 || 的操作:

function myFunction(x, y = 10) {
    // y is 10 if not passed or undefined
    return x + y;
}
 
myFunction(0, 2) // 输出 2
myFunction(5); // 输出 15, y 参数的默认值

函数调用

JavaScript 函数有 4 种调用方式。

每种方式的不同在于 this 的初始化。

this 关键字

一般而言,在Javascript中,this指向函数执行时的当前对象。

作为一个函数调用

function myFunction(a, b) {
    return a * b;
}
myFunction(10, 2);           // myFunction(10, 2) 返回 20

以上函数不属于任何对象。但是在 JavaScript 中它始终是默认的全局对象。

在 HTML 中默认的全局对象是 HTML 页面本身,所以函数是属于 HTML 页面。

在浏览器中的页面对象是浏览器窗口(window 对象)。以上函数会自动变为 window 对象的函数。

myFunction() 和 window.myFunction() 是一样的:

function myFunction(a, b) {
    return a * b;
}
window.myFunction(10, 2);    // window.myFunction(10, 2) 返回 20

这是调用 JavaScript 函数常用的方法, 但不是良好的编程习惯

全局变量,方法或函数容易造成命名冲突的bug。

全局对象

当函数没有被自身的对象调用时 this 的值就会变成全局对象。

在 web 浏览器中全局对象是浏览器窗口(window 对象)。

该实例返回 this 的值是 window 对象:

function myFunction() {
    return this;
}
myFunction();                // 返回 window 对象

函数作为全局对象调用,会使 this 的值成为全局对象。 使用 window对象作为一个变量容易造成程序崩溃。

函数作为方法调用

var myObject = {
    firstName:"John",
    lastName: "Doe",
    fullName: function () {
        return this.firstName + " " + this.lastName;
    }
}
myObject.fullName();         // 返回 "John Doe"

函数作为对象方法调用,会使得 this 的值成为对象本身。

使用构造函数调用函数

// 构造函数:
function myFunction(arg1, arg2) {
    this.firstName = arg1;
    this.lastName  = arg2;
}
 
// This    creates a new object
var x = new myFunction("John","Doe");
x.firstName;       // 返回 "John"

构造函数的调用会创建一个新的对象。新对象会继承构造函数的属性和方法。

构造函数中 this 关键字没有任何的值。 this 的值在函数调用实例化对象(new object)时创建。

作为函数方法调用函数

在 JavaScript 中, 函数是对象。JavaScript 函数有它的属性和方法。

call()apply() 是预定义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象本身。

function myFunction(a, b) {
    return a * b;
}
myObject = myFunction.call(myObject, 10, 2);     // 返回 20

function myFunction(a, b) {
    return a * b;
}
myArray = [10, 2];
myObject = myFunction.apply(myObject, myArray);  // 返回 20

两个方法都使用了对象本身作为第一个参数。 两者的区别在于第二个参数: apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始)。

在 JavaScript 严格模式(strict mode)下, 在调用函数时第一个参数会成为 this 的值, 即使该参数不是一个对象。

在 JavaScript 非严格模式(non-strict mode)下, 如果第一个参数的值是 null 或 undefined, 它将使用全局对象替代。

通过 call() 或 apply() 方法你可以设置 this 的值, 且作为已存在对象的新方法调用。

函数闭包

闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。

直观的说就是形成一个不销毁的栈环境。

JavaScript 类(class)

我们使用 class 关键字来创建一个类,类体在一对大括号 {} 中,我们可以在大括号 {} 中定义类成员的位置,如方法或构造函数。

每个类中包含了一个特殊的方法 constructor(),它是类的构造函数,这种方法用于创建和初始化一个由 class 创建的对象。

创建一个类的语法格式如下:

class Runoob {
  constructor(name, url) {
    this.name = name;
    this.url = url;
  }
}

以上实例创建了一个类,名为 "Runoob"。

类中初始化了两个属性: "name" 和 "url"。

定义好类后,我们就可以使用 new 关键字来创建对象:

et site = new Runoob("菜鸟教程",  "https://www.runoob.com");

创建对象时会自动调用构造函数方法 constructor()

构造方法是一种特殊的方法:

  • 构造方法名为 constructor()。

  • 构造方法在创建新对象时会自动执行。

  • 构造方法用于初始化对象属性。

  • 如果不定义构造方法,JavaScript 会自动添加一个空的构造方法。

严格模式 "use strict"下的类声明

类声明和类表达式的主体都执行在严格模式下。比如,构造函数,静态方法,原型方法,getter 和 setter 都在严格模式下执行。

如果你没有遵循严格模式,则会出现错误:

class Runoob {
  constructor(name, year) {
    this.name = name;
    this.year = year;
  }
  age() {
    // date = new Date();  // 错误
    let date = new Date(); // 正确
    return date.getFullYear() - this.year;
  }
}

类方法和关键字

和java类似(结合java理解)

类方法:
constructor()	构造函数,用于创建和初始化类

类关键字:
extends	继承一个类
static	在类中定义一个静态方法
super	调用父类的构造方法

JavaScript 类继承

与java类似(结合java理解)

getter 和 setter

类中我们可以使用 getter 和 setter 来获取和设置值,getter 和 setter 都需要在严格模式下执行。

getter 和 setter 可以使得我们对属性的操作变的很灵活。

类中添加 getter 和 setter 使用的是 get 和 set 关键字。

以下实例为 sitename 属性创建 getter 和 setter:

class Runoob {
  constructor(name) {
    this.sitename = name;
  }
  get s_name() {
    return this.sitename;
  }
  set s_name(x) {
    this.sitename = x;
  }
}
 
let noob = new Runoob("菜鸟教程");
 noob.sitename = "RUNOOB";
document.getElementById("demo").innerHTML = noob.s_name;

注意:即使 getter 是一个方法,当你想获取属性值时也不要使用括号。

getter/setter 方法的名称不能与属性的名称相同,在本例中属性的名称为 sitename。

很多开发者在属性名称前使用下划线字符 _ 将 getter/setter 与实际属性分开:

  • getter 是一种获得属性值的方法,setter是一种设置属性值的方法

  • getter负责查询值,它不带任何参数,setter则负责设置键值,值是以参数的形式传递,在他的函数体中,一切的return都是无效的

  • get/set访问器不是对象的属性,而是属性的特性,特性只有内部才用,因此在javaScript中不能直接访问他们,为了表示特性是内部值用两对中括号括起来表示如:[[Value]]

JavaScript 静态方法

静态方法是使用 static 关键字修饰的方法,又叫类方法,属于类的,但不属于对象,在实例化对象之前可以通过 类名.方法名 调用静态方法。

静态方法不能在对象上调用,只能在类中调用。