第01节:JavaScript概述

一、JavaScript概述

1995年,JavaScript问世,主要目的是处理表单验证。起初命名为LiveScript,后来因为java语言盛行,更名为JavaScript,目的是希望借着Java的火爆流行起来(JavaScript的开发者一定想不到JavaScript在20多年后的今天会如此盛行)。

1997年,欧洲计算机制造商协会发布了ECMAScript,在接下来的几年里,web浏览器厂商就开始将ECMAScript作为JavaScript实现的标准。

2009年,Node.js问世,JavaScript这门语言逐步在后台占据一席之地,目前,前端开发的大量工具都基于node.js。

2015年,ECMAScript2015(ES6)正式发布,使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

后续版本统称ES2015+,ES2015是完全向下兼容的

中文JavaScript文档

二、JavaScript是做什么的

在前端,通过javascript可以实现更多的页面交互,与后台的数据交互,以及更为丰富的网页效果。

在后台,借助node的运行环境,使用javascript一门语言,即可完成服务器端开发,我们会在后续的章节中讲解如何使用JavaScript做服务端开发,本章主要内容仍然面向前端开发。。

三、hello world

JavaScript放在<script></script>这个标签里

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<script>
		alert("hello world");
	</script>
</body>
</html>

四、控制台

chrome浏览器中的console选项,他是JavaScript的控制台工具,我们可以在其中输出JavaScript程序,也可以在其中看到程序运行结果。例如我们在网页中编写如下代码:

console.log('hello world');

可以看到控制台中会输出hello world

第02节:变量与数据类型

一、变量的基本概念

变量可以理解为是一个存储数据的容器

代码如下所示:

var n = 100;
var s = "hello world";
var s1="hello",s2="world";

通过var声明一个变量,var后面的英文字母就是变量名,变量名是自定义的,在一定的规则下我们可以随意命名(下一部分我们来讲命名规则)。

每行结尾的分号并不是必须写的,但是为了代码更加规范,我们要求每行代码的结尾都要写分号,用以表示本行结束(注意必须是英文半角的分号)。

二、变量的命名规范

代码如下所示:

var age = 17;
var num1 = 198;
var num2 = 200;
var price = 25.6;
var _name = "小明";
var $fruit = "苹果";
var firstName = "Lily";
var message = "I love javascript";
  • 变量名要见名知意
  • 变量名可以是字母、下划线、$,还有数字;但是不能以数字开头
  • 小写字母开头,多个单词,第二个单词首字母大写(驼峰命名)
  • 不可以与关键字、保留字重复

三、数据类型

JavaScript有六种数据类型,如下表所示:

类型名称 说明
数值 100;3.14 不管是整数还是小数,都是数值型。
字符串 "hello";"100" 双引号或单引号中的值是字符串。
布尔 true;false 布尔值只有两个值,代表真和假。
null 空值只有null,后续讲解。
未定义 undefined 未定义值只有undefined,后续讲解
对象 {} 未初始化时可以暂时赋值为null

关于undefined

//尽量不要让变量值是undefined
var v;//undefined
var a = undefined;//不要直接给变量赋undefined,因为不赋值就是

//为变量初始化时尽量这样写
var str = "";//初始化字符串
var num = 0;
var obj = null;

第03节:表达式与运算符

一、表达式概述

字面量

赋值符号=右边某种数据类型的值,就是字面量

例如字符串"hello world"或是数字100都是字面量

表达式

通过运算符将变量、字面量组合起来,就是表达式。

每一个表达式都有一个固定返回值(表达式的结果)

"hello" + "world" //"helloworld"
100 + 200 //300

二、运算符

算数运算符

运算符 描述 示例 结果
+ 加法 20 + 10 7
- 减法 20 - 10 10
* 乘法 20 * 10 200
/ 除法 20 / 10 2
% 求于 11 % 2 1
++ 自增1 ++7 8
-- 自减 --7 6
var num = 1;
console.log(num++);  //输出1
console.log(++num);  //输出2
console.log(num++ + ++num);//输出4

自增和自减运算符如果写在变量后面,那么表达式的返回值是变量本身,然后变量自增或自减,运算符写在变量前面,那么变大时的返回值直接就是变量自增或自减后的结果。

比较运算符

比较运算符的返回值是布尔值

运算符 描述 示例 结果
> 大于 20 > 10 true
< 小于 20 < 10 false
>= 大于等于 20 >= 10 true
<= 小于等于 20 <= 10 false
== 等于(会进行类型转换,不比较数据类型,效率低) 20 == "20" true
!= 不等(会进行类型转换,不比较数据类型,效率低) 20 != 10 false
=== 恒等于(不进行类型转换,会比较类型,效率高) 20 === "20" false
!== 非恒等于(不进行类型转换,会比较类型,效率高) 20 !== "20" true

逻辑运运算符

运算符 描述 示例 结果
&& 逻辑与 true && false false
|| 逻辑或 true || false true
! 逻辑非 !true false

赋值运算符

运算符 描述 示例 等同于
= 赋值 x = 10
+= 加并赋值 x += y x = x + y
-= 减并赋值 x -= y x = x - y
*= 乘并赋值 x *= y x = x * y
/= 除并赋值 x /= y x = x / y

第04节:条件语句

一、概述

语句执行流程有三种:顺序执行、条件执行、循环执行。

条件语句表示的就是按照条件判断执行哪些代码(或不执行哪些代码)。

二、if语句

if语句是最基本的条件控制语句,它让JavaScript程序可以选择执行顺序,我们可以通过一个布尔值来控制一行语句是否执行,if语句有多种形式,下面我们一一介绍: 示例代码如下:

if(true)  
    console.log("执行代码");

在上面的代码中,if括号内的值如果是true,则执行第二行代码,如果是false,则不执行第二行代码。

if后面的括号内一般不会直接写一个布尔值,而是写一个表达式, 示例代码如下:

var num1 = 10;
var num2 = 20;
if(num1<num2)   //如果改成num1>num2,则不会输出下面的文字
    console.log("num1小于num2")

上面我们将的两个例子都是通过判断条件来执行一行代码,但是大多数情况,我们需要执行多行代码,那么我们需要在if后面加上一对花括号,并且,为了让代码块更直观,我们在以后的代码中,都会写if后面的花括号。 示例代码如下:

var num1 = 10;
var num2 = 20;
if(num1<num2){
    console.log("判断num1是否小于num2");
    console.log("num1小于num2");
} 

通过上面的例子,我们通过判断条件确定是否执行某一个代码块,下面我们通过if...else...语句实现:在两个代码块中,选择一个来执行 示例代码如下:

    var num1 = 10;
    var num2 = 20;
    if(num1<num2){      
    //num1如果小于num2,表达式为true,输出if语句后的代码;num1如果大于num2,表达式为false,输出else语句后的内容
        console.log("num1小于num2");
    }else{
        console.log("num1大于num2");
    }

if...else语句可以判断两种情况下,需要执行哪些代码,如果需要判断的条件是三种情况,我们可以使用if...else if...语句 示例代码如下:

var num1 = 10;
var num2 = 20;
if(num1<num2){
    console.log("num1小于num2");
}else if(num1>num2){
    console.log("num1大于num2");
}else if(num1===num2){
    console.log("num1等于num2");
}

我们可以通过修改num1和num2的值来判断输出哪一行语句。

通过控制运算符来实现数学运算

var num1 = 10;
var num2 = 20;
var sign = "+";  //通过修改操作符,输出不同的结果
var result = 0;  //result用来存储计算的结果,现在设置一个初始值0
if(sign === "+"){
    result = num1 + num2;
    console.log(result)
}else if(sign === "-"){
    result = num1 - num2;
    console.log(result)
}else if(sign === "*"){
    result = num1 * num2;
    console.log(result)
}else if(sign === "/"){
    result = num1 / num2;
    console.log(result)
}else{
    console.log("请输入正确的运算符")
}

三、switch语句

if语句在程序执行的过程中创建一条分支,并且可以使用if...else if...语句来处理多条分支,然而当所有的分支都依赖于同一个表达式的值时,重复计算多条if语句中的条件是非常浪费时间的做法,switch语句正合适处理这种情况

var num = 0;   //通过修改num的值控制执行哪行语句
switch(num){
    case 0:
        console.log("num的值是零");  //当n===0,执行
        break;
    case 1:
        console.log("num的值是一");  //当n===1,执行
        break;
    case 2:
        console.log("num的值是二");  //当n===2,执行
        break;
    case 3:
        console.log("num的值是三");  //当n===3,执行
        break;
    default:                         
        console.log("其他");         //当n的值不是0,1,2,3,执行
        break;
}

我们了解switch语句的语法,下面我们使用switch语句改写demo03,实现通过控制运算符来实现数学运算

        var num1 = 10 
        var num2 = 20
        var operator = " + "

        switch(operator){
            case " + ":
            console.log("num1+num2=" + (num1 + num2) )
            break;
            case " - ":
            console.log("num1-num2=" + (num1 - num2) )
            break;
            case " * ":
            console.log("num1*num2=" + (num1 * num2) )
            break;
            case " / ":
            console.log("num1/num2=" + (num1 / num2) )
            break;
            default:
            console.log("其他")
            break;

四、条件运算符

如果是简单的判断,我们可以使用条件运算符

表达式?第一个值:第二个值

如果表达式为true,表达式的返回值是第一个值,如果表达式为false,那么表达式的返回值是第二个值,示例代码如下所示示例示例代码如下:

var num1 = 10;
var num2 = 20;
var result = num1 > num2 ? 100 : 200;
//如果num1大于num2,条件表达式的值为100,若num1小于等于num2时,条件表达式的值为200;
console.log(result);

第05节:循环语句

条件语句的代码可以被想象成是一条条分支的路径,而循环语句的代码则是程序路径的一个回路,可以让一部分代码重复执行。JavaScript中的循环语句有for语句和while语句。

一、for语句

for语句的语法如下:

for(初始值;布尔值;计数器){
    //语句块
}

在for语句中,如果布尔值是true,就会一直执行语句块中的内容,为了防止死循环,需要有一个计数器,当数值达到指定值,布尔值就会变成false,循环便停止了,下面的示例代码使用for循环输出0~9九个数字 示例代码如下:

for(var i = 0;i<10;i++){  
    // i的初始值是0
    // 判断i是否小于10,如果小于10则执行花括号中的代码
    // 每次执行完花括号中的代码后,i的值加1
    console.log(i);
}

通过上面的例子我们进一步理解了for语句的用法,下面我们来做一个联系,利用for循环语句输出100以内所有正整数的加和 示例代码如下:

var sum = 0;                 //sum用来存储循环过程中正整数的加和
for(var i = 1;i<=100;i++){
    sum += i;
}
console.log(sum); //这时候输出的就应该是5050

二、while语句

while语句语法如下所示:

while(bool){
    //bool为true,循环执行
}

当bool为true的时候,花括号中的内容会循环执行。为了防止死循环,需要在循环的过程实现类似for计数器的功能,让循环在有限的次数内定制,下面我们使用while语句输出0~9是个数字 示例代码如下:

var n = 0;
while(n<10){
    console.log(n);
    n++;
}

在每次循环的过程中都会让n的值加1,这样当n的值等于10,循环便停止,下面我来使用while语句输出100以内所有正整数的加和 示例代码如下:

var n = 0;
var sum = 0;
while(n<=100){
    sum += n;
    n++;
}
console.log(sum); 

三、continue

continue可以结束本次循环,直接进入到下一次循环,例如我们用for循环语句来实现输出0 ~ 5,7 ~ 9九个数字(跳过6) 示例代码如下:

for(var i = 0;i<10;i++){
    if(i===6){
        continue;
    }
    console.log(i);
}

上面的代码通过判断,实现当i的值为6的时候,跳过本次循环,直接接入下一次循环。下面我们使用continue来实现计算100以内所有不能被7整除的正整数加和 示例代码如下:

var sum = 0;
for(var i = 0;i<=100;i++){
    if(i%7===0){
        continue;
    }
    sum += i;
}
console.log(sum);

四、break

在学switch语句中,我们已经接触到了break,它可以让分支语句在结束一个case之后,跳出switch语句,break同样可以用在循环语句当中,当代码执行到break时,直接结束循环 示例代码如下:

for(var i = 0;i<10;i++){
    if(i===6){
        break;  
    }
    console.log(i);
}

如上面的代码所示,当控制带输出5之后,循环结束。

第06节:函数基础

一、函数的基本概念

函数是一个可执行的语句块,定义的时候不执行,调用的时候执行,使用"函数名()"的形式可以调用函数

语法如下:

function fun(){   //定义函数,函数名为fun
    //函数体
}
fun();            //调用函数

我们先来编写第一个最简单的函数,当这个函数执行的时候会在控制台输出"hello function" 示例代码如下:

function fun(){
    console.log("hello function")
}
fun(); 

在上面的代码中我们定义了一个函数,并调用了一次,这样就会在控制台输出一次“hello function”。我们在编程的过程中,很多代码是需要多次使用的,我们可以把它们写在一个函数中,这样我们每次希望执行这些代码的时候,只需要调用这个函数,而不是复制-粘贴多次代码。

二、参数

下面编写一个函数sum,输出10和20两个数之和 示例代码如下:

function sum(){
    var num1 = 10;
    var num2 = 20;
    var result = num1 + num2;
    console.log(result);
}
sum();

在上面例子中我们调用sum函数,可以成功在控制台输出计算结果,但是函数内部的代码是固定的,虽然可以多次使用,但是每次使用输出的都是10和20的加和,为了让函数更加灵活,我们希望实现一个函数可以计算任意两个数的加和,那么我们就需要了解函数是如何传递参数的。

下面一个简单的例子演示函数如何传递参数 示例代码如下:

function fun(str){
    console.log("hello" + str);
}
fun("world");

在定义函数的括号中,我们添加了一个参数str,这个参数叫做形参。它在函数内部像一个变量一样。但是在函数调用之前他是没有值的。当调用函数的时候,调用函数的括号中我们也添加了一个参数"world",这个参数叫做实参,他可以是任意数据类型的值。函数被调用后,形参str被赋予了实参"world"的值,然后执行console.log便会在控制台输出"helloworld"

上面的函数有一个形参和一个实参,函数可以传递多个参数,用逗号间隔 示例代码如下:

function sum(num1,num2){
    console.log(num1 + num2);
}
sum(10,20);

当我们调用函数的时候,实参和形参是一一对应的,10对应的是num1,20对应的是num2,函数执行后会在控制台输出10和20的加和。

下面我们来编写一个函数,它有一个正整数参数n,当我们调用函数时,函数会输出包括n在内,1~n所有正整数的加和 示例代码如下:

function sum(n){
    var sum = 0;
    for(var i = 0;i<=n;i++){
        sum += i;
    }
    console.log(sum);
}
sum(100);
sum(567);

我们通过一个函数规定了一种计算方式,我们使用函数的只要输入一个值,函数就会计算出一个准确的结果。

三、返回值

在上面的例子中,我们输入了一个参数,函数就可以在控制台输出我们希望得到的结果,但是在实际开发中,很多情况我们要的不是在控制台输出得到的结果,而单纯的只是为了获取这个值,那么我们就需要用到函数的返回值。

在函数中,我们可以通过return关键字指定一个返回值,函数有了return,当函数被调用的时候就可以把调用的结果赋值给另一个变量了 示例代码如下:

function fun1(){
    
}

function fun2(){
    return "hello fun";
}

var str1 = fun1();
var str2 = fun2();

console.log(str1);  //输出undefined
console.log(str2);  //输出"hello fun"

在上面的例子中,函数fun1没有返回值,所有将fun1调用的结果赋值给str1,str1的值为undefined,函数fun2有返回值,返回值是"hello fun",所以当fun2被调用后,将函数运行的结果赋值给str2,str2的值就是"hello fun"

下面我们来编写一个函数,让函数来计算四则运算的结果

function count(num1,sign,num2){
    var result = 0;
    switch(sign){
        case "+":result = num1 + num2;break;
        case "-":result = num1 - num2;break;
        case "*":result = num1 * num2;break;
        case "/":result = num1 / num2;break;
        default:console.log("请输入真确的操作符")
    }
    return result;
}
console.log(count(10,"*",20));

四、函数表达式

var functionName=function(arg){
      //函数体
}

这种形式看起来好像是常规的变量赋值语句,即创建一个函数并将它赋值给变量functionName。这种情况下创建的函数叫做匿名函数。因为function关键字后面没有标识符。

函数表达式与其他表达式一样,在使用之前必须先赋值;如下面代码就会导致错误;

helloworld(); //错误,还未赋值,函数不存在

var helloworld=function(){
    console.log("hello world");
}

有了函数表达式,我们就可以给函数表达式赋值了;如下面代码:

var helloworld; //声明
if(condition){ //条件
   helloworld=function(){ //赋值
       console.log("hello world"); 
   }
}
else{
    helloworld=function(){ //赋值
       console.log("你好,世界");
    }
}

五、函数声明提升

func()
function func () {
}

上例不会报错,正是因为 ‘函数声明提升’,即将函数声明提升(整体)到作用域顶部(注意是函数声明,不包括函数表达式),实际提升后结果同下:

// 函数声明提升
function func () {
}
func()

==函数表达式没有函数声明提升==

六、作用域

在 JavaScript 中, 对象和函数同样也是变量。 在 JavaScript 中, 作用域为可访问变量,对象,函数的集合。 JavaScript 函数作用域: 作用域在函数内修改。

局部作用域

变量在函数内声明,变量为局部作用域。 局部变量:只能在函数内部访问。

// 此处不能调用 carName 变量
function myFunction() {
    var carName = "Volvo";
    // 函数内可调用 carName 变量
}

因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量。 局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。

全局变量

变量在函数外定义,即为全局变量。 全局变量有 全局作用域: 网页中所有脚本和函数均可使用。

var carName = " Volvo";
 
// 此处可调用 carName 变量
function myFunction() {
    // 函数内可调用 carName 变量
}

如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量。 以下实例中 carName 在函数内,但是为全局变量。

// 此处可调用 carName 变量
 
function myFunction() {
    carName = "Volvo";
    // 此处可调用 carName 变量
}

第07节:对象

一、对象的基本概念

对象是属性的集合,是六种数据类型之一。

二、自定义对象

我们可以通过一对花括号来创建一个对象 如下所示:

var obj = {};

在花括号中,我们可以为对象定义属性,下面我们来写一个猫的对象 示例代码如下:

var cat = {   
       //定义一个对象cat,它有两个属性,name和age
    name:"喵喵",
    age:2
}
//有两种方法可以获取到对象的属性值:1、对象名.属性名;2、对象名["属性名"]
console.log(cat.name);    
console.log(cat["name"]);

创建一个空的对象

var student = null;

三、方法

如果对象的属性值是函数,那么我们叫这个属性为这个对象的方法

var cat = {
    name:"喵喵",
    age:2,
    sayName:function(){
        console.log("我是喵喵");
      }
}
cat.sayName();

上面的代码可以在控制台输出“我是喵喵”,其中sayName是cat的方法。

方法简写

var cat = {
    name:"喵喵",
    age:2,
    sayName(){
        console.log("我是喵喵");
    }
}
cat.sayName();

四、this关键字

在上面的例子中,我们可以给cat的那么属性重新赋值,代码如下 示例代码如下:

var cat = {
    name:"喵喵",
    age:2,
    sayName:function(){
        console.log("我是喵喵")
    }
}
cat.name = "小白";
console.log(cat.name);    //输出"小白"
cat.sayName();            //输出"我是喵喵"

因为猫的名字已经改变,但是sayName方法里面的名字并没有一同变化,我们可以通过this关键字实现修改了名字,方法里面的名字也会改变。

在对象的方法中使用this,可以指向这个对象本身, 示例代码如下:

var cat = {
    name:"喵喵",
    age:2,
    sayName:function(){
        console.log("我是"+this.name)
    }
}
cat.sayName();            //输出“我是喵喵”
cat.name = "小白";
console.log(cat.name);    //输出"小白"
cat.sayName();            //输出“我是小白”

五、方法传参

给对象的方法传递参数和给函数传递参数是一样的,下面我们来定义一个会算数的猫 示例代码如下:

var cat = {
    name:"喵喵",
    age:2,
    sayName:function(){
        console.log("我是"+this.name)
    },
    count:function(num1,num2){
        console.log(num1+num2);
    }
}

cat.sayName();
cat.count(10,20);

我们在之前代码的基础上,有添加了一个count方法可以让猫可以计算两个数的加和,我们只要传入实参,猫就能计算结果。

六、对象展开运算符

let xiaoMing = {name:"小明",age:2}
let superXiaoMing = {weight:"1000kg",fly:true}
let superHero = {
    ...xiaoming,
    ...sukperXiaoming
}
console.log(superHero)

七、对象的分类

  1. 自定义对象:封装
  2. 内置对象:(例如Date,获取当前时间)
  3. 宿主对象(document)
  4. 第三方库对象(jQuery,Vue)

第08节:数组

一、数组的基本概念

数组是一个特殊的对象,对象的概念是属性的集合,而数组是元素的有序集合。我们可以通过一个中括号来定义一个数组

var numlist = [1,3,5,7,9];
//new + 构造函数
var list = new Array(1,3,5,"7","9");

在上面的代码中我们定义了一个数组,数组有5个元素,五个元素都是数值型。我们可以通过数组的变量名配合中括号来获取数组的元素

var numlist = [1,3,5,7,9];
console.log(numlist[0]);   //输出数组的第一个元素
console.log(numlist[4]);   //输出数组的第五个元素

中括号中的数字叫做数组的下标,我们可以通过下标获取数组的元素,要注意的是下标是从零开始的。

数组是特殊的对象,它有自己的属性和方法,其中最常用的属性就是length,它可以获取数组元素的个数。

var numlist = [1,3,5,7,9];
console.log(numlist.length) // 输出5

数组中的元素可以是任意类型的,但是我们一般将数组中的元素设置成相同数据类型,下面是一个字符串类型的数组,我们通过下标分别输出数组的所有元素。 示例代码如下:

var friends = ["小明","小亮","小红"];
console.log(friends[0]);  //小明
console.log(friends[1]);  //小亮
console.log(friends[2]);  //小红
console.log(friends.length);  //长度为3

二、遍历数组

  1. while
var friends = ["小明","小亮","小红","张三","李四","王五"];
var i=0;
while(i<friends.length){
    console.log(friends[i++]);
}
  1. for
var friends = ["小明","小亮","小红","张三","李四","王五"];
for(var i = 0;i<friends.length;i++){
    console.log(friends[i]);     //输出数组中的所有元素
}
  1. for in(i是下标)
var friends = ["小明","小亮","小红","张三","李四","王五"];
for(var i in friends){
    console.log(friends[i]);
}
  1. for of(i是元素)
var friends = ["小明","小亮","小红","张三","李四","王五"];
for(var i of friends){
    console.log(i);
}
  1. map方法
var friends = ["小明","小亮","小红","张三","李四","王五"];
friends.map(function(value,index){
    console.log("下标为"+index+"的元素是"+value);
})

三、数组的常用方法

map方法

map()方法创建一个新的数组,元素为使用此数组中的每个元素调用参数所提供的函数的结果

var list = ["a","b","c","d","e"]
list.map(function(value,index){
    console.log(value);
})

push方法

push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。示例代码如下:

var list = ["a","b","c","d","e"]
list.push("f")
console.log(list)

sort方法(排序)

var list = [5,3,6,7,2]
list.sort()
console.log(list)//2,3,5,6,7

join方法

用于把数组中的所有元素放入一个字符串,参数为间隔符,默认为","

var list = ["a","b","c"]
var str = list.join();
console.log(str);//输出a,b,c
str = list.join("");
console.log(str);//输出abc
str = list.join("+");
console.log(str);//输出a+b+c

split方法

是string的方法,可以把一个字符串拆分成数组,参数是间隔符,参数为空时会把字符串转换为只有一个元素的数组,数组元素是一个字符串

var str = "a b";
var list=str.split("");
console.log(list);//["a"," ","b"]

filter方法(过滤器)

创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

const list = [1,2,3].filter((v)= > {
     console.log(v);  //输出为1,2,3
     return v>1;  //返回到新数组中
})
console.log(list);  //输出为[2,3];

find方法

find()方法返回数组中第一个满足回调函数测试的第一个元素的值。否则返回undefined;示例代码如下:

const arr1 = [1, 2, 3, 4, 6, 9];
let found = arr1.find(e => e > 5); // 6

第9节:内置对象

一、内置对象概述

javascript为我们提供了很多内置对象,这些内置对象为我们提供了很多语言的基本功能。我们之前学过的数组就是JavaScript的内置对象,除了数组我们还应该了解的内置对象有:Math、Date、RegExp。

需要说明的是document对象是DOM提供的对象,不属于JavaScript内置对象,window对象是BOM中的对象,同样不属于JavaScript内置对象。

二、Math

Math对象不像数组那样需要我们手动去创建,我们在JavaScript程序中直接写Math代表的就是Math对象。我们可以通过Math对象直接获取圆周率

var pi = Math.PI;
console.log(pi);

Math对象提供了很多方法来简化我们的数学运算,下面简单列举几个方法

var pi = Math.PI;   
var num1 = Math.floor(pi);  //向下取整
var num2 = Math.ceil(pi);   //向上取整
var num3 = Math.round(pi);  //四舍五入
var num4 = Math.abs(-pi);   //获取绝对值
var num5 = Math.random();   //获取[0,1)随机数
console.log(num1);
console.log(num2);
console.log(num3);
console.log(num4);
console.log(num5);

在Math对象的方法中,floor和random两个方法比较常用,我们可以通过这两个方法获取我们想要的随机数范围。例如我们想要1~10的随机数,代码如下

var number = Math.floor(Math.random()*10 + 1);
console.log(number);

下面我们来实现一个猜数字的游戏,JavaScript随机生成一个1~100之间的数字,我们通过文本输入框输入我们所猜的数字,猜的数字不管是大于结果,还是小于结果,还是等于结果,都会有相应的提示 代码如下

<input type="text" id="number">
<button id="guess">猜数字</button>
<script>
    var target = Math.floor(Math.random()*100+1); //生成1~100的随机数。
    var btn = document.querySelector("#guess");
    var number = document.querySelector("#number");
    btn.onclick = function(){
        var value = Number(number.value);
        if(value > target){
            alert("大于结果");
        }else if(value < target){
            alert("小于结果");
        }else if(value === target){
            alert("回答正确");
        }
    }
</script>

三、Date

Date对象是JavaScript用于处理日期和时间的对象,我们可以通过Date对象获取当前的时间,需要说明的是Date对象获取的时间是本机的时间

var dateNow = new Date();
var year = dateNow.getFullYear();    //获取年,不能用getYear()方法,此方法已经被废弃
var month = dateNow.getMonth();      //获取月份 从0开始,一月份返回的值是0
var date = dateNow.getDate();        //获取日期
var hours = dateNow.getHours();      //获取小时
var minutes = dateNow.getMinutes();  //获取分钟
var seconds = dateNow.getSeconds();  //获取秒
var day = dateNow.getDay();          //获取星期
console.log(year);
console.log(month);
console.log(date);
console.log(hours);
console.log(minutes);
console.log(seconds);
console.log(day);

我们可以通过JavaScript将当前的时间显示在网页上

var h1 = document.querySelector("h1");
var dateNow = new Date();
var hours = dateNow.getHours();      
var minutes = dateNow.getMinutes();  
var seconds = dateNow.getSeconds(); 
var strTimeNow = hours + ":" + minutes + ":" + seconds;
h1.innerHTML = strTimeNow;

上面的例子我们成功将当前的日期显示在h1标签中,但是我们显示的时间是获取的那个时间点,显示的时间是静止不动的,我们可以通过计时器方法让我们显示的时间与实际时间同步

var h1 = document.querySelector("h1");
function getTime(){     
    var dateNow = new Date();
    var hours = dateNow.getHours();      
    var minutes = dateNow.getMinutes();  
    var seconds = dateNow.getSeconds(); 
    var strTimeNow = hours + ":" + minutes + ":" + seconds;
    h1.innerHTML = strTimeNow;
}
getTime();
setInterval(getTime,1000);

我们将过去时间的代码放到了一个函数中,然后通过计时器方法每秒执行一次这个函数,这样我们显示出来的日期就想电子钟一样每秒与真实事件同步一次。

我们还可以通过参数创建一个指定时间的日期对象,我们修改一下

var dateNow = new Date("2017-5-1 17:30:20");  //创建指定日期和时间的对象
var year = dateNow.getFullYear();   
var month = dateNow.getMonth();     
var date = dateNow.getDate();       
var hours = dateNow.getHours();      
var minutes = dateNow.getMinutes(); 
var seconds = dateNow.getSeconds(); 
var day = dateNow.getDay();

我们在编写程序的时候,有的时候会希望获取一个唯一的时间点,我们可以使用getTime方法或得。 示例代码如下

var dateTarget = new Date("2017-5-1 17:30:20");
var dateNow = new Date();
var target = dateTarget.getTime();
var now = dateNow.getTime();
console.log(target);
console.log(now);

通过getTime方法可以获取时间戳,时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数,我们可以用时间戳表示一个不会重复的时间点。

课后练习

制作一个倒计时的功能,要求如下:

  1. 计算距离指定日期还有多少天,多少小时,多少分钟,多少秒。
  2. 在控制台输出这个时间。

第10节:正则表达式

一、正则表达式概述

正则表达式用于匹配字符串,例如我们想验证某一个字符串是否为邮箱格式,可以使用正则表达式判断;我们希望特换一片文章中的所有英文字母,可以使用正则表达式;我们想截取一片文章中的某些内容,也可以使用正则表达式。

正则表达式对象RegExp是JavaScript中的内置对象,我们可以像创建数组一样创建它。

var arr = new Array();   //创建数组
var reg = new RegExp();  //创建正则表达式

在开发中,我们一般用简写的方法创建正则表达式,同样和数组比较,代码如下

var arr = [1,2,3];   //创建数组
var reg = /123/;     //创建正则表达式

二、正则表达式语法

正则表达式可以用来匹配字符串,我们可以把正则表达式看做是一种规则,如果字符串中的内容符合这种规则,就会匹配,如果不符合这个规则,就不会匹配

var reg = /123/;  //创建了一个正则表达式,这个正则表达式可以匹配字符串"123"
var str = "123";  //创建字符串"123"
console.log(reg.test(str));  //test方法可以测试字符串是否成功匹配,匹配返回true,不匹配返回false

我们可以看到程序在控制台输出了true,因为reg定义的时候就是为了匹配字符串123的,我们再来看下面的例子

var reg = /123/;
var str = "012345";
console.log(reg.test(str));  //仍然返回true

我们将字符串改成了"12345",返回结果仍然返回true,这是因为我们定义的正则表达式可以匹配任何包含"123"的字符串,为了证实这个说法,我们可以使用exec方法来输出匹配的内容

var reg = /123/;
var str = "012345";
console.log(reg.exec(str)); //exec方法返回一个数组,数组中包含匹配的内容,如果未匹配,返回null

这段代码在控制台输出了一个数组,数组的第一个元素是匹配的内容,大家可以看到匹配的内容是"123",数组还带有两个属性,index表示从字符串中第几个字母开始匹配,input表示被匹配的字符串的值。

有的时候,我们希望我们写的正则表达式只能匹配"123",如果是字符串包含"123"页不匹配,如果希望这样的话,我们需要改进我们的正则表达式

var reg = /^123$/;  //^表示开头,$表示结尾,两个符号之间是匹配的内容
var str = "012345";
console.log(reg.exec(str));

这样就可以只能匹配字符串"123",但是如果正则表达式是直接把要匹配的内容写在正则表达式里,那意义也不是很大,接下来我们用正则表达式强大的语法来匹配各种字符串。

设定匹配范围

正则表达式可以通过[]设定匹配的范围,代码如下

var reg = /[123]/;  //匹配123中的任意一个字符
var str = "02468";  
console.log(reg.exec(str));

通过指定范围,正则表达式成功匹配了字符串中的数字2。

匹配数字

正则表达式可以在匹配范围中定义[0-9]来设定匹配数字

var reg = /[0-9]/;  //匹配一位数字
var str = "02468";  
console.log(reg.exec(str));

虽然字符串中都是数字,但是一个[]只能表示匹配一位数字,所以这里匹配的是0

匹配字母

正则表达式可以在匹配范围中定义[a-z]来设定匹配字母

var reg = /[a-z]/;  //匹配一位字母
var str = "012345abcde";  
console.log(reg.exec(str));

与匹配数字类似,上面的正则表达式可以匹配一位字母。

匹配多位

上面的匹配内容匹配的都是一位数字或字母,我们可以通过+来指定匹配多位

var reg = /[a-z]+/;  //匹配多位字母
var str = "012345abcde";  
console.log(reg.exec(str));

上面的代码表示匹配多位字母,所有abcde都成功的被匹配

匹配指定位数

有的时候我们需要匹配指定位数的字符,可以通过{}指定匹配的位数

var reg = /[a-z]{3}/;  //匹配3位字母
var str = "012345abcde";  
console.log(reg.exec(str));

下面我们来定义一个正则表达式来匹配一个邮箱格式的字符串,我们先来确定一下邮箱的格式:

  • 5~12位的数字字母下划线开头
  • 后面接@
  • 后面接2~5位的数字和字母
  • 后面接.
  • 后面接com
var reg = /^[a-zA-Z0-9\_]{5,12}\@[a-zA-Z0-9]{2,5}\.com$/; 
var str = "test123@qq.com";    //邮箱  
console.log(reg.exec(str));

很多特殊符号在正则表达式都有特殊含义,为了取消它的特殊含义,我们需要在特殊符号之前加“\”将其转义。、

三、表单验证

通过上面的学习,我们已经对正则表达式有了初步的了解,下面我们来实现一个表单验证邮箱的功能,验证通过或者不通过,都要在文本框后面输出结果

<input type="text">
<span></span>
<script>
    var input = document.querySelector("input");
    var span = document.querySelector("span");
    var reg = /^[a-zA-Z0-9\_]{5,12}\@[a-zA-Z0-9]{2,5}\.com$/; 
    input.onblur = function(){
        if(reg.test(this.value)){
            span.innerHTML = "验证通过";
        }else{
            span.innerHTML = "验证失败";
        }
    }
</script>

四、总结

在实际项目中,我们通常会用插件去实现字符串验证的功能。所以,大家了解了正则表达式基本语法之后,再去学习那些深度的东西

^:开头
$:结尾
[]:范围
{}:位数
():分组
+:同{1,}
?:同{0,1}
.:任意一位字符
\:转义
\d:同[0-9]
\w:数字、字母、下划线
\s:空格、换行

//在正则表达式结尾加g表示全局匹配
//如下会把所有小写字母去掉
var str="1234qweqwr"
var reg = /[a-z]/g
var result=str.replace(reg,"");
console.log(result);

image-20220319093826385

课后练习

  1. 使用正则表达式,完成手机号验证;
  2. 使用正则表达式,完成邮箱验证;

第11节:常量变量与解构赋值

一、ECMAscript概述

ECMAscript简称ES,是JavaScript的标准,我们经常说的ES5,ES6等等,可以称作JavaScript的版本,我们在之前学过的所有JavaScript特性,都是基于ES5版本的,今天我们开始讲解的是ES6标准的特性。ES6已更名为ES2015,ES7等后续的版本,我们都可以统称为ES2015+

二、变量和常量

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。我们之前一直使用var定义变量,在ES6版本中,我们可以使用let定义变量,下面我们来说说var与let的区别

块级作用域

ES5只有全局作用域和函数作用域,没有块级作用域的概念,这带来了很多不合理的场景。 实例代码如下:

for(var i = 0;i<10;i++){
    console.log(i); // 0-9
}
console.log(i);     // 10

因为没有块级作用域,所以我们在for语句的外面仍然能获取i的值,在实际开发中,这是一个不可理喻的场景,我们希望的是这个i只在for语句内有效,所以再ES6中添加了块级作用域的概念,我们可以用let声明变量,问题就解决了 实例代码如下:

for(let i = 0;i<10;i++){
    console.log(i); // 0-9
}
console.log(i);     // 报错 i is not defined

因为用let声明变量,变量只在块级作用域下有效,所以再for语句之外输出i会报错。另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

不存在变量提升 定义:函数声明和变量声明总是被JavaScript解释器隐式地提升(hoist)到包含他们的作用域的最顶端。 通过定义我们可以知道,只有变量的声明和函数的声明存在变量提升这一说,那么在ES2015+出现之前,JavaScript声明变量的方式是通过关键字var实现的,声明函数自然通过function啦,ES6中我们用let,const来声明变量和常量。 实例代码如下:

    {
      console.log(a) // 报错,a is not defined
      let a=2;
    }

如果let存在变量提升,那么上述代码就相当于下面这样

    {
      let a;
      console.log(a) //undefined
      a=2;
    }

由此可见,let并不存在变量提升。

不允许重复声明

实例代码如下:

var a = 10;
var a = 20;

let b = 10;
let b = 20;  //报错

多次声明是没有意义的,在ES6中,我们使用let声明变量限制了不能多次声明,如果多次声明同一个变量会报错。

常量

在ES6中,不仅有变量,还增加了常量的概念,我们用const声明常量,一旦声明,它的值就不能再改变 实例代码如下:

const PI = 3.1415926;
PI = 3                   //报错

我们说常量不能再改变,说的是不能重新为这个常量赋值,但是如果常量存储的是一个对象,那我们是可以改变这个对象的属性的 实例代码如下:

const obj = {name:'小明'};
obj.name = '小红';
console.log(obj.name);   //小红

三、模板字符串

之前我们也可以使用JavaScript输出模版字符串,通常是下面这样的:

<h1 id="result"></h1>
<script src="jquery.js"></script>
    let person = {
        name:"name",
        age:"age"
    }
    $("#result").append(
    "He is <b>"+person.name+"</b>"+"and we wish to know his"+person.age+".That is all" 
    );

但是我们可以看到:这样的传统做法需要使用大量的“”(双引号)和 + 来拼接才能得到我们需要的模版。但是这样是十分不方便的。

于是ES6中提供了模版字符串,用`(反引号)标识,用${}将变量括起来。上面的例子可以用模版字符串写成下面这样:

        $("#result").append(
        `He is <b>${person.name}</b>and we wish to know his${person.age}.that is all`
        );

这样的做法就简洁了很多,我们不需要再使用大量的""和+来拼接字符串和变量。

上面代码中,模板字符串都是用反引号表示,如果在模板字符串中需要使用反引号,则需要在反引号前面用反斜杠转义。

var mes = `\`hello\` World!`; // `hello` World!

模板字符串是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

多行字符串

用单引号或是双引号表示的字符串在编辑的过程中只能在一行显示,若要在多行显示需要在每一行结尾添加一个斜杠,这样的编辑方式对开发者显然不够友好,我们可以使用模板字符串的功能换行编辑字符串 代码如下所示:

let str = `hello
world`;
console.log(str);

因为使用了模板字符串,所以hello world如上所示并没有报错,如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。

字符串中嵌入变量

我们在开发的过程中经常会遇到在字符串中嵌入变量的情况,以往我们都是使用字符串连接的方式。有了模板字符串,我们可以在字符串中添加变量或对象的属性,需要将变量名写在${}之中 代码如下所示:

const student = {
    name:"小明",
    age:2
}
console.log(`我是${student.name},我今年${student.age}岁了`);

这样的表达方式可以更友好地将代码呈现给开发者。

模板字符串调用函数

我们不仅可以将变量和对象的属性嵌入模板字符串,还可以将函数嵌入模板字符串,并显示出函数的返回值 代码如下所示:

let x = 'hello';
let y = 'world';
function fn(x,y){
    return x + y;
}
console.log(`程序员最常用的一句话是${fn(x,y)}`);

如果模板字符串中嵌入的值是需要通过计算而得到的,我们就可以使用上面的方法来完成这个功能。

四、解构赋值

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值,解构赋值主要包括数组的解构赋值、对象的解构赋值、字符串的解构赋值、函数参数的解构赋值。

数组的结构赋值

实例代码如下:

//传统赋值
var num1 = 1;
var num2 = 2;
var num3 = 3;

//数组的解构赋值
let [str1,str2,str3] = ["hello","world","javascript"];

console.log(str1);
console.log(str2);
console.log(str3);

对象的解构赋值 解构不仅可以用于数组,还可以用于对象。 实例代码如下:

let {num1,num2} = {num1:100,num2:10};
console.log(num1);
console.log(num2);

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

字符串的结构赋值 字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象 示例代码如下:

let [a,b,c,d,e] = "hello";
console.log(a);
console.log(b);
console.log(c);
console.log(d);
console.log(e);
// 类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello'; 
//{length : len} length是长度的意思,len 是 let 声明的一个常量
console.log(len) //长度为数字的5

函数参数的结构赋值 函数的参数也可以使用解构赋值。 实例代码如下:

function add([x, y]){
  return x + y;
}

add([1, 2]); // 3

上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。

五、课后练习

  1. 熟练使用本节讲解的基础语法;

第12节:函数进阶

一、概述

在此前的内容中,我们已经学习了函数的基本用法,例如如何定义函数,如何调用函数,以及函数的形参、实参、返回值等内容。本节内容会继续讲解ES2015为函数赋予的新特性。

  • 立即执行函数
  • 闭包
  • 函数默认值
  • 箭头函数
  • async函数

二、立即执行函数

立即执行函数顾名思义,就是声明之后立刻执行的函数,实例代码如下所示:

(function(){
    console.log("hello world");
})()

函数体被一个小括号包裹起来,然后后面紧跟着一个小括号,这样当函数被声明之后,就立刻被调用。

立即执行函数通常用来封装代码,例如下面的例子:

(function(){
    let a = 10;
    let b = 20;
    console.log(a + b)
})()

这段代码可以输出a + b的结果,但是立即执行函数外部是不能访问a和b的,因此成功达到了封装的目的。

在比较古老的js代码中,大家都是这种方法来封装代码的,但是ES2015自带模块化语法,因此这种封装的写法就不太常见了,大家只做了解即可。

三、闭包

闭包是定义在函数内部的函数,我们用立即执行函数编写一个闭包的例子

(function(){
    function add(a, b){
        return a + b
    }
    let result = add(10, 20)
    console.log(result)
})()

立即函数内部的add函数就是闭包,闭包的特点是具有全局性,闭包内部的this关键字会指向全局对象window。

闭包的特性:内部函数未执行完,外部函数即使执行完成,外部函数中的变量也不会被销毁。

四、函数默认值

在ES2015版本之前,我们可以通过下面的方法设置函数参数的默认值 代码如下所示:

function fun1(x,y){
    x = x || 100;
    y = y || 200;
    return x + y;
}

上面的实例代码,利用逻辑或运算符的能力重新给参数赋值,也就是说如果有参数传入,则赋值为传入的参数,如果没有,则为或运算的第二个值。

这种设置默认值的方式看起来很不友好,ES2015中新增了更直观的设置函数默认值的方法,实例代码如下所示:

//es2015+设置默认值
function fun2(x=100,y=200){
    return x + y;
}

五、箭头函数

在此前的课程中,我们学习了两种方法定义函数,

  • 函数声明
  • 函数表达式

在ES2015中增加了箭头函数的语法,可以使用=>来定义函数,我们来对比一下三种写法,实例代码如下所示:

//函数声明
function add(a, b){
    return a + b
}
//函数表达式
const add = function(a, b) {
    return a + b
}
//箭头函数
const add = (a, b) => {
    return a + b;
}
  • =>左侧的括号存放参数
  • =>右侧的花括号存放函数体。

箭头函数简写

以上展示了一个完整的箭头函数的语法,这其实还不能体现箭头函数的优势。

箭头函数第一个优势是,简写的箭头函数可以让代码更简洁

  • 如果函数只有一个形参,那么可以省略参数外面的括号。
  • 如果函数体只有一个表达式作为返回值,可以省略花括号和return关键字

实例代码如下所示

const fun = x=>x*x;
let result = fun(6);
console.log(result) //36;

箭头函数中的this

箭头函数不仅简化了函数的写法,而且让函数中的this指针变得更人性化。

此前我们学习过的,函数中的this指向谁,取决于是哪个对象调用了这个函数。

例如下面中的示例代码

const person = {
    sayMe: function () {
        console.log(this)
    },
    sayMeDelay: function () {
        (function(){
            console.log(this);
        })()
    }
}

person.sayMe();
person.sayMeDelay();

在实例代码中

  • sayMe方法输出的this是person这个对象,因为是person直接调用的sayMe。
  • sayMeDelay方法因为加入了一个延迟函数,导致setTimeout中的函数由windows对象调用,因此this指向windows对象。

但是在开发的过程中,我们通常希望sayMedelay内部

const person = {
    sayMe: function () {
        console.log(this)
    },
    sayMeDelay: function () {
        (() => {
            console.log(this);
        })()
    }
}

person.sayMe();
person.sayMeDelay();

在上面的代码中,我们定义了一个变量self用来存储指向person的this,这样我们就可以正确地输出lee。但是这样的写法看起来有给我们增加了一些复杂度,我们可以用箭头函数来解决这个问题,

const person = {
    firstName: "lee",
    sing: function () {
        console.log(this.firstName)
    },
    singDelay: function () {
        setTimeout(() => {
            console.log(this.firstName);
        }, 2000)
    }
}

person.sing();
person.singDelay();

在上面的代码中,成功地输出了两个lee,这是因为箭头函数中的this,不是调用函数时的this指向的对象,而是定义函数时this指向的对象,定义函数的时候,this指向的是person而不是window,所以这里可以正确地输出结果。

六、课后练习

  1. 说出箭头函数与普通函数的区别。
  2. 使用箭头函数延迟调用cat.sayName,输出cat对象的name属性。

第13节:面相对象

一、面相对象概述

首先面向对象是一种编程思想,是一种通过多个对象互相协作完成处理流程的编程思路【是对现实世界中一类事物的抽象,在编程中可以理解为是一种建立现实世界事物的模型】 推及到广义上,面向对象已经越了程序设计和软件开发,我认为面向对象又是一种思维方式,不局限于编程语言,甚至不局限编程本身,它把复杂的需求、业务逻辑抽丝剥茧、逐个分析。 主要分为:类的声明定义、对象的创建使用、面向对象拥有的特征【三大特征:封装、继承、多态】 封装:体现了对象对于敏感数据的保护特征 继承:体现了代码的复用和功能的扩展 多态:体现了不同操作环境中代码的多样性【程序的健壮性】 思路:大量的程序开发—软件的开发—解决问题—处理数据—CRUD【增删改查】

二、基于原型的面向对象

在ES2015版本之前,JavaScript是没有类的概念的,我们可以使用构造函数来模拟一个类,这在我们之前的课程中已经讲解过了,这里我们简单复习一下。

创建一个猫的构造函数

function Cat(name,age){
    this.name = name;
    this.age = age;
}

var cat = new Cat("miaomiao",2);
console.log(cat.name);

在上面的代码中,我们定义了一个构造函数Cat,通过var关键字创建了一个变量cat,cat有两个属性,name和age。我们可以直接输出cat.name;

我们可以通过原型属性为构造函数添加方法,例如我们给这个Cat添加一个shout方法,让这只猫可以叫 代码如下所示:

function Cat(name, age) {
    this.name = name;
    this.age = age;
}

Cat.prototype.shout = function(){
    console.log("喵喵喵!")
}

var cat = new Cat("miaomiao", 2);
cat.shout();

这样猫就有了shout方法,可以调用shout方法让猫叫。

通过上面的代码,我们可以模拟一个类的概念,但是这样的写法与真正面相对象语言的写法相比,确实更难以理解。所以ES2015中增加了类的概念。

三、ES2015中的面向对象

基于原型的继承方式,虽然实现了代码复用,但是行文松散且不够流畅,可阅读性差,不利于实现扩展和对源代码进行有效的组织管理。不得不承认,基于类的继承方式在语言实现上更健壮,且在构建可服用代码和组织架构程序方面具有明显的优势。所以,ES2015+中提供了基于类class的语法。但class本质上是ES2015+提供的一颗语法糖,正如我们前面提到的,JavaScript是一门基于原型的面向对象语言。

我们可以用class来定义一个类,然后可以在这个类中定义构造函数,方法和属性。 代码如下所示:

class Cat{
    constructor(name,age){
        this.name = name;
        this.age = age;
    }

    shout(){
        console.log("喵喵喵");
    }
}

let cat = new Cat("miaomiao",2);
cat.shout();

上面的这段代码与第二个案例的代码功能是完全一样的,但是通过class关键之定义一个猫的类,让这个类更像是一个整体,而非一个个零散的prototype组合起来的一个功能。

继承

  1. 使用prototype属性(ES5的方案,了解即可)
function Animal(name){
    this.name = name;
}
Animal.prototype.sayName = function(){
    console.log(this.name);
}

function Dog(name){
    this.name = name;
}
Dog.prototype = new Animal();

let dog = new Dog("二哈");
dog.sayName();
  1. ES6以后使用extends关键字(掌握)
class Animal{
    constructor(name) {
        this.name = name;
    }
    sayName(){
        console.log(this.name);
    }
}

class Dog extends Animal{
    constructor(name,age){
        super(name);
        this.age = age;
    }
    getAge(){
        console.log(this.age);
    }
}

let dog = new Dog("二哈",13);
dog.sayName();
dog.getAge();

课后练习

按要求实现功能:

  1. 拓展Date对象实现如下功能:实现dateFomate方法,返回值是 "xxxx年xx月xx日"。
Date.prototype.dateFormate = function (){
return `${this.getFullYear()}年${this.getMonth()+1}月${this.getDate()}日`}

let date = new Date();
console.log(date.dateFormate());
  1. 定义一个Person类,让Student类和Teacher类继承Person,为Person类添加getlnfotmation方法,是student类和Teacher类都可以通过此方法获取个人信息
class Person{
    constructor(name,age) {
        this.name = name;
        this.age = age;
    }
    getInformation(){
        return `我是${this.name},${this.age}岁`;
    }
}
class Student extends Person{

    constructor(name, age, id) {
        super(name, age);
        this.id = id;
    }

    getInformation() {
        return super.getInformation()+`,学号是${this.id}`;
    }
}

let s = new Student("小明",10,1332);
console.log(s.getInformation());

第14节:DOM(文档对象模型)

一、DOM的基本概念

本章节可谓是相当重要了,是让JavaScript灵动起来必不可少的一步

  • 文档对象模型
  • 定义了树状结构
  • 定义了接口,可以用来操作树状结构

二、样式操作

  1. docunment.getElementById():返回值是一个DOM节点
  2. docunment.getElementByClassName():返回值是一个DOM节点的集合
  3. dom节点的属性innerHTML可以获取、设置元素内的所有内容

我们可以通过DOM提供的querySelector方法来获取元素,参数为CSS选择器,然后进一步操作它的样式 示例代码如下:

<h1>DOM样式测试</h1>
<script>
    var h1 = document.querySelector("h1");  
    //querySelector的返回值是一个DOM对象,该方法可以通过选择器获取元素,若选择器找到多个元素,则返回第一个。
    h1.style.color = "red"; 
    //DOM对象的style属性可以设置元素内联样式。
</script>

若需要通过js设置多个元素的样式,可以使用querySelectorAll方法, 示例代码如下:

<ul>
    <li>香蕉</li>
    <li>苹果</li>
    <li>鸭梨</li>
</ul>
<script>
    var ali = document.querySelectorAll("li");
    //querySelectorAll方法的返回值是一个类数组的集合,里面保存的是获取的所有元素,所以如果希望为每一个元素设置样式,需要遍历这个集合。
    for(var i = 0;i<ali.length;i++){
        ali[i].style.color = "red";
    }
</script>

三、绑定事件

事件就是文档或者浏览器窗口发生的一些特定的交互瞬间,例如:用户点击网页会触发点击事件(onclick),用户在元素上移动会触发鼠标移动事件(onmouseover/onmouseenter),鼠标移出(onmouseout/onmouseleave)又恢复原本模样等。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box{
            width:100px;
            height:100px;
            background-color: red;
        }
    </style>
</head>
<body>
    <button>按钮</button>
    <div class="box"></div>
    <script>
        let btn = document.querySelector("button");
        let box = document.querySelector(".box");
        box.onmouseenter = function(){
            this.style.backgroundColor = "blue";
        }
        box.onmouseleave = function(){
            this.style.backgroundColor = "red";
        }

        // 事件监听函数
        btn.onclick = function(){
            box.style.backgroundColor = "yellow"
        }
    </script>
</body>
</html>

我们将一个函数赋值给一个事件,当这个事件被触发的时候,这个函数就会被执行。

四、操作属性

我们可以通过JavaScript获取和设置元素属性,例如input的value属性值,或者img的src属性。

首先我们来实现一个效果,在文本框中输入字符串,然后点击按钮用在控制台输出我们输入的字符串 示例代码如下:

<input type="text">
<button>输出</button>
<script>
    var input = document.querySelector("input");
    var btn = document.querySelector("button");
    btn.onclick = function(){
        var text = input.value;    //获取input的value属性
        console.log(text);
    }
</script>

我们还可以通过赋值的方式为一个元素设置属性,可以切换所示图片 示例代码如下:

<img src="images/0210_logo.jpg" alt="">
<button>切换图片</button>
<script>
    var img = document.querySelector("img");
    var btn = document.querySelector("button");
    btn.onclick = function(){
        img.src = "images/0210_img.jpg";
    }
</script>

当点击按钮的时候,通过赋值的方式把另一张图片的地址赋值给img标签的src属性,就实现了图片切换的效果

五、数学计算案例

下面我们来实现一个能完成数学计算的程序,页面有四个文本框和一个按钮,我们在第一个文本框输入一个数字,在第二个文本框输入一个操作符,第三个文本框再输入一个数字,然后当我们点击计算按钮的时候,会在第四个文本框计算出结果,这个例子和我们学习switch语句的时候写的例子很像,但是那时候我们没有可操作的页面,现在我们把计算功能写在一个函数中。 示例代码如下:

    <input type="text" id="num1">
    <input type="text" id="sign">
    <input type="text" id="num2">
    <input type="text" id="result">
    <button>计算</button>
    <script>
        var num1 = document.querySelector("#num1");
        var sign = document.querySelector("#sign");
        var num2 = document.querySelector("#num2");
        var result = document.querySelector("#result");
        var btn = document.querySelector("button");
        btn.onclick = function(){
            var n = Number(num1.value);    //将字符串类型转换成数字类型
            var m = Number(num2.value);
            var r = count(n,sign.value,m);
            result.value = r;
        }
        function count(n,s,m){    //n是第一个数字,s是操作符,m是第二个数
            switch(s){
                case "+":return n+m;break;
                case "-":return n-m;break;
                case "*":return n*m;break;
                case "/":return n/m;break;
            }
        }
    </script>

第15节:DOM操作

一、节点的分类

  • 元素节点(querySelector;querySelectorAll)
  • 属性节点(element.src;element.id)
  • 文本节点(innerHTML) 上一节课我们讲解了获取元素节点,操作属性节点。本节课我们讲解添加和删除元素节点和编辑文本节点。

二、文本节点

在html中我们有一个h1标签和一个按钮,h1标签内已经有了一段文本。当我们点击按钮的时候,在h1标签中插入“hello world”

<h1>内容:</h1>
<button>添加文本节点</button>
<script>
    var h1 = document.querySelector("h1");
    var btn = document.querySelector("button");

    btn.onclick = function(){
        var textNode = document.createTextNode("hello world");
        //createTextNode方法可以创建一个文本节点
        h1.appendChild(textNode);
        //appendChild方法可以将textNode节点添加到h1标签中。
    }
</script>

在DOM中还有另一个属性可以更方便地获取和设置文本节点,这个属性是innerHTML,我们写一个简单的例子来测试innerHTML属性 示例代码如下:示例连接

<h1>内容:</h1>
<button>添加文本节点</button>
<script>
    var h1 = document.querySelector("h1");
    var btn = document.querySelector("button");
    btn.onclick = function(){
        h1.innerHTML = "hello world";
        //设置h1的文本节点,innerHTML的内容会完全覆盖原节点的内容
    }
</script>

对比着两种方法,第一种方法需要创建文本节点,然后通过appendChild方法将节点追加到之前文本内容的后面,第二种方法则是直接用innerHTML覆盖之前文本节点的内容。如果要实现demo01的功能,需要改写一下事件内的代码示例连接

var str = h1.innerHTML;//获取文本内容
h1.innerHTML =str + "hello world"; //原文本内容与新文本内容连接

三、创建和添加元素节点

上一节我们讲解了如何获取和设置文本节点,本节讲解如何创建和添加元素节点。创建原始节点可以使用createElement方法,添加元素节点仍然可以用appendChild方法。接下来我们来一步一步完成一个任务列表的功能,html代码如下所示:

<button>添加节点</button>
<ul>
    <li>香蕉</li>
    <li>苹果</li>
    <li>鸭梨</li>
</ul>

我们要是先一个功能,当点击按钮的时候,在列表中添加一个li元素

var btn = document.querySelector("button");
var ul = document.querySelector("ul");
btn.onclick = function(){
    var li = document.createElement("li"); //创建一个元素节点,li元素
    ul.appendChild(li); //在ul元素中添加li元素
}

在这个案例中,我们已经成功地在ul标签中添加了li元素,但是li元素并没有文本节点,我们进一步改进点击事件中的内容

var li = document.createElement("li"); 
li.innerHTML = "鸭梨";
ul.appendChild(li); 

通过上面的代码,我们已经可以在ul中添加带有文本节点的li元素了,但是文本节点是固定的“鸭梨”,我们还可以进一步通过一个文本框,让用户自己填写要插入的内容

    <script>
		var btn = document.querySelector("button");
		var ul = document.querySelector("ul");
		var text = document.querySelector("input");
		btn.onclick = function(){
			var li = document.createElement("li"); //创建一个元素节点,li元素
			ul.appendChild(li); //在ul元素中添加li元素
			li.innerHTML = text.value
		}
	</script>

四、删除元素节点

我们可以通过removeChild方法删除元素,下面的例子我们来实现点击按钮,删除h1标签的效果

<button>删除</button>
<div class="box">   
    <h1>待删除的内容</h1>
</div>
<script>
    var btn = document.querySelector("button");
    var box = document.querySelector(".box");
    var h1 = document.querySelector("h1");
    btn.onclick = function(){
        box.removeChild(h1);
    }
</script>

通过上面的代码可以知道,删除一个元素需要知道他的父级元素,然后通过父级元素的removeChild方法删除子级元素,那么如果不确定删除的元素的父级是哪个元素,我们该如何获取元素的父级元素呢,可以使用parentNode方法,我们直接来改写上面的代码

<button>删除</button>
<div>   
    <h1>待删除的内容</h1>
</div>
<script>
    var btn = document.querySelector("button");
    var h1 = document.querySelector("h1");
    btn.onclick = function(){
        var box = h1.parentNode;
        box.removeChild(h1);
    }
</script>

下面我们来实现一个删除水果列表中水果的功能

<ul>
    <li><span>香蕉</span> <span class="del">删除</span></li>
    <li><span>苹果</span> <span class="del">删除</span></li>
    <li><span>鸭梨</span> <span class="del">删除</span></li>
    <li><span>芒果</span> <span class="del">删除</span></li>
    <li><span>草莓</span> <span class="del">删除</span></li>
</ul>

我们要实现点击删除按钮的时候,删除span父级的li元素

var aDel = document.querySelectorAll(".del");
for(var i = 0;i<aDel.length;i++){
    aDel[i].onclick = function(){
        var parent = this.parentNode
        parent.parentNode.removeChild(parent)
    }
}

第16节:事件流

一、绑定事件

想要给一个元素绑定事件,我们有两种方法:使用内联事件或事件监听器。在之前的课程中,我们一直使用的是内联事件来为元素绑定事件,例如一个按钮的点击事件,代码如下

btn.onclick = function(){}   //绑定鼠标单击事件

我们还可以用使用事件监听器为元素绑定事件,代码如下

btn.addEventListener("click",function(){});

下面我们用两种方法为按钮绑定事件

<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<script>
    var btn1 = document.querySelector("#btn1");
    var btn2 = document.querySelector("#btn2");

    btn1.onclick = function(){
        console.log("我是按钮1");
    }

    btn2.addEventListener("click",function(){
        console.log("我是按钮2");
    })
</script>

两种方法都能实现相同的效果,能成功的为按钮绑定了点击事件,但区别是使用addEventLitener可以无限制的为元素绑定事件,而内联事件后面的会覆盖前面的

var btn1 = document.querySelector("#btn1");
var btn2 = document.querySelector("#btn2");

btn1.onclick = function(){
    console.log("我是按钮1");
}
btn1.onclick = function(){
    console.log("我是按钮1,第二次绑定");
}

btn2.addEventListener("click",function(){
    console.log("我是按钮2");
})

btn2.addEventListener("click",function(){
    console.log("我是按钮2,第二次绑定");
})

第一个按钮第二次绑定的事件覆盖了第一次绑定的事件,第二个按钮两次绑定的事件都能被触发。

二、事件冒泡与事件捕获

image-20220319162026687

接下来我们用事件监听器为三个div元素绑定点击事件,最外层的div宽高是300px,中间的div宽高都是200px,最内层的div宽高都是100px,那么思考一下,点击最内层的div,事件会如何触发,是只触发最内层的div,还是从内到外依次触发,还是从外到内依次触发

<div class="box1">
    <div class="box2">
        <div class="box3"></div>
    </div>
</div>
var box1 = document.querySelector(".box1");
var box2 = document.querySelector(".box2");
var box3 = document.querySelector(".box3");

box1.addEventListener("click",function(){
    console.log("我是box1")
})
box2.addEventListener("click",function(){
    console.log("我是box2")
})
box3.addEventListener("click",function(){
    console.log("我是box3")
})

通过上面的例子我们可以看到,事件是从最内层开始触发,然后依次向外,输出的顺序是box3-box2-box1。导致这种顺序的原因是因为:事件流有事件捕获阶段和事件冒泡阶段,事件捕获阶段是从最外层元素开始一层一层进入到事件目标(也就是我们点击的那个元素),到达事件目标后,进入事件冒泡阶段,事件从最内层流向最外层,事件默认情况下在冒泡阶段触发,所以我们看到的是先输出box3,最后输出box1。

我们也可以将事件设置为捕获阶段触发

box1.addEventListener("click",function(){
    console.log("我是box1")
},true)
box2.addEventListener("click",function(){
    console.log("我是box2")
},true)
box3.addEventListener("click",function(){
    console.log("我是box3")
},true)

只要在添加事件方法中添加第三个参数为true,事件就会在捕获阶段被触发,这样输出的顺序就变成了box1-box2-box3。但是在日常开发中,我们几乎不用做此修改,让事件在冒泡阶段触发就可以了。

阻止事件冒泡

使用e.stopPropagation();可以阻止事件冒泡,即内层元素的事件触发不会导致外层元素的事件触发

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .big{
            width:300px;
            height:300px;
            background-color: red;
        }
        .medium{
            width:200px;
            height:200px;
            background-color: yellow;
        }
        .small{
            width:100px;
            height:100px;
            background-color: blue;
        }
    </style>
</head>
<body>
    <div class="big">
        <div class="medium">
            <div class="small"></div>
        </div>
    </div>
    <script>
        let big = document.querySelector(".big");
        let medium = document.querySelector(".medium");
        let small = document.querySelector(".small");
        big.addEventListener("click",function(){
            console.log("Hi,I'm big");
        })
        medium.addEventListener("click",function(){
            console.log("Hi,I'm medium");
        })
        small.addEventListener("click",function(e){
            console.log("Hi,I'm small");
            e.stopPropagation();
        })
    </script>
</body>
</html>

阻止事件的默认行为

可以使用e.preventDefault()或者return fasle;

例如阻止a标签默认点击跳转到新页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <a href="http://baidu.com">baidu</a>
    <script>
        let a = document.querySelector("a");
        a.onclick = function(e){
            console.log('hello world')
            // e.preventDefault();
            return false;
        }
    </script>
</body>
</html>

三、事件委托

利用事件流的原理,通过e.target()我们可以实现事件委托,事件委托可以简单的理解为将子级的事件委托给父级来处理,我们先来看一个简单的例子

<div class="btnBox">
    <button class="btn1">按钮1</button>
    <button class="btn2">按钮2</button>
</div>

网页中有两个按钮,他们的父级是一个div标签,现在我们希望给这两个按钮绑定事件,当我们点击按钮的时候输出按钮的文本内容,按照我们之前学过的知识,可以有如下写法

//第一种写法
var btn1 = document.querySelector(".btn1");
var btn2 = document.querySelector(".btn2");
btn1.addEventListener("click",function(){
    console.log(this.innerHTML)
})
btn2.addEventListener("click",function(){
    console.log(this.innerHTML)
})

这种方法简单易懂,但是存在重复,两个按钮触发事件执行的代码完全一样,我们可以获取到所有按钮,再通过遍历绑定事件

//第二种写法
var btnArray = document.querySelectorAll("button");
for(var i = 0;i<btnArray.length;i++){
    btnArray[i].addEventListener("click",function(){
        console.log(this.innerHTML)
    })
}

通过遍历我们优化了代码,但是仍然存在问题,首先,如果按钮的数量特别多,每一个按钮都绑定依次事件会非常影响程序的性能,其次,就算不考虑性能,通过这种方法绑定事件,如果使用js新增了一个按钮,这个按钮因为初始化的时候没有绑定事件,所以无法点击。为了解决上述问题,我们可以使用事件委托的方式来实现上面的功能

var btnBox = document.querySelector(".btnBox");
btnBox.addEventListener("click",function(event){
    var target = event.target;    //通过事件对象获取事件目标
    console.log(target.innerHTML);
})

在事件监听函数中,我们可以在形参的位置获取到事件对象event,事件对象中包含了事件相关的信息,通过event.target可以获取到我们的事件目标,在这个例子中事件目标就是我们点击的按钮,而我们事件绑定的是按钮的容器,这样就可以将自己元素的事件委托给父级来处理。

四、事件类型

鼠标事件、键盘事件、触屏事件

键盘事件

document.onkeydowndocument.onkeyup通过e.keyCode获取键盘按键

通过键盘上下左右实现方块的移动

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box{
            width:100px;
            height:100px;
            background-color: red;
            position: absolute;
            top:100px;
            left:100px;
        }
    </style>
</head>
<body>
    <div class="box"></div>
    <script>
        let box = document.querySelector(".box")
        document.onkeydown = function(e){
            let code = e.keyCode;
            //element.offsetLeft获取元素相对于上级定位元素的左偏移量
            //element.offsetTop获取元素相对于上级定位元素的上偏移量
            switch(code){
                case 37:box.style.left = box.offsetLeft - 5 + "px";break;
                case 38:box.style.top = box.offsetTop - 5 + "px";break;
                case 39:box.style.left = box.offsetLeft + 5 + "px";break;
                case 40:box.style.top = box.offsetTop + 5 + "px";break;
            }
        }
    </script>
</body>
</html>

触屏事件

  1. element.ontouchstart:在元素上按下
  2. element.ontouchend:在元素上按下后放开
  3. element.ontouchmove:在元素上滑动
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box{
            width: 100px;
            height: 100px;
            background-color: red;
        }
    </style>
</head>
<body>
    <div class="box"></div>
    <script>
        let box = document.querySelector(".box");
        box.ontouchstart = function(){
            console.log("start")
        }
        box.ontouchend = function(){
            console.log("end")
        }
        box.ontouchmove = function(){
            console.log("hello world")
        }
    </script>
</body>
</html>

四、课后练习

一、实现如下功能(阻止事件冒泡)

  1. 点击一个按钮,显示一个容器的盒子;
  2. 点击容器,容器背景颜色改变;
  3. 点击容器按钮 容器关闭;

二、实现水果列表,让后添加的元素也可以删除(事件委托);

三、通过上下左右按键控制元素移动;

第17节:计时器方法

一、计时器方法概述

计时器方法可以实现在指定的时间过后,单次或重复调用函数的功能,setTimeout可以实现函数在指定毫秒数后单次执行,setInterval可以实现函数在指定毫秒数后重复执行,语法如下所示:

setTimeout(function(){
    //一秒后执行
},1000);

setInterval(function(){
    //一秒后执行,并且每隔一秒重复执行
},1000)

二、setTimeout(只执行一次)

下面我们来实现一个效果,页面加载3秒后在控制台输出hello world

setTimeout(function(){
    console.log("hello world");
},3000)

当计时器开始计时后,我们可以使用clearTimeout方法让计时器停下来,下面我们来定义一个按钮,当页面加载后,如果我们在3秒钟之内点击按钮,计时器会停止,不会输出hello world,如果不点击按钮,3秒钟之后就会输出hello world

var btn = document.querySelector("button");
var t = setTimeout(function(){
    console.log("hello world");
},3000)
btn.onclick = function(){
    clearTimeout(t);
}

setTimeout方法会返回一个整数类型的值,通过这个值,我们可以停止计时器,我们将setTimeout方法的返回值赋值给一个变量,当点击按钮的时,使用clearTimeout()方法,传入t,这样计时器就会停止,hello world就不会在控制台输出。

三、setInterval(不停执行)

setInterval的用法与setTimeout的用法非常类似,都是传入两个参数,第一个参数是计时器执行的函数,第二个参数是毫秒数。下面我们来实现一个效果,每3秒钟在控制台输出依次hello world

setInterval(function(){
    console.log("hello world");
},3000)

从代码可以看出,setInterval与setTimeout完全相同,区别在于setInterval参数中的函数没个指定毫秒数后都会重复执行,当我们不希望计时器重复执行的时候,就可以使用clearInterval方法来停止计时器

var btn = document.querySelector("button");
var t = setInterval(function(){
    console.log("hello world");
},3000)
btn.onclick = function(){
    clearInterval(t);
}

下面我们来实现一个效果,让控制台输出每隔1秒按顺序输出正整数,从数字1开始输出

var n = 1;
function showNumber(){
    console.log(n);
    n++;
}
setInterval(showNumber,1000);
showNumber();    //调用函数,可以在页面加载时直接输出1。

上面的代码与之前有一点区别,我们并没有直接给setInterval传递一个匿名函数,而是先定义了一个函数showNumber,然后将showNumber传递给setInterval,这两种写法效果是一样的,但是如果将匿名函数传入setInterval,这个函数将不能被调用。

在上面代码的结尾,我们在页面加载之后调用了一次showNumber,目的是为了让页面加载的时候就输出1,否则我们将要等待一秒之后才能看到控制台输出1。

这个案例会一直输出数字,下面我们来改进这个例子,当数字为10的时候就停止,效果看起来有些想之前讲过的for循环输出数字,但用计时器输出可以实现每个1秒输出一个数字,而不是连续的输出

var n = 1,t=0;
function showNumber(){
    console.log(n);
    if(n === 10){
        clearInterval(t);
    }
    n++;
}
t=setInterval(showNumber,1000);
showNumber(); 

我们通过一个if语句判断n的值,当n到达10的时候,就停止计时器,这样计时器就不会再继续输出数字了。

我们还可以继续用按钮控制计时器,这次我们定义一个h1标签存放数字,再用两个按钮来实现“开始计数”和“停止计数”功能

    <h1>0</h1>
    <button id="start">开始计数</button>
    <button id="stop">停止计数</button>
    <script>
        var btnStart = document.querySelector("#start");
        var btnStop = document.querySelector("#stop");
        var h1 = document.querySelector("h1");
        var t;
        btnStart.onclick = function(){
            clearInterval(t);//防止连续点击开始计时,导致创建多个计时器
            t = setInterval(function(){
                var number = Number(h1.innerHTML); //将h1的文本节点转换成数字
                number++;
                h1.innerHTML = number;
            },300); 
        }

        btnStop.onclick = function(){
            clearInterval(t);
        }
    </script>

在网页中我们经常会看到指定秒数跳转到其他网页,location.href可以实现页面跳转,我们可以用计时器方法来实现这个功能

<p><span class="seconds">5</span>秒后跳转到百度</p>
<script>
    var seconds = document.querySelector(".seconds");
    setInterval(function(){
        var s = Number(seconds.innerHTML);
        s--;
        seconds.innerHTML = s;
        if(s === 0){
            //location.href可以实现页面跳转
            location.href = "http://baidu.com";
        }
    },1000)
</script>

四、防抖(debounce)与节流(throttle)

解决性能问题,短时间内多次触发事件会造成性能问题,尤其是事件内部有大量业务逻辑时。例如widows.onscroll窗口滚轮事件,一旦窗口滚动,就会在短时间内大量触发。还有input输入搜索内容,当停下输入时才执行搜索。

防抖

对于短时间内多次触发事件的情况,可以使用防抖停止事件持续触发

let timer = null;
window.onscroll = function(){
    if(timer !== null){
        clearTimeout(timer);
    }
    timer = setTimeout(() => {
        console.log("hello world")
        timer = null;
    },2000)
}

节流

防止短时间内多次触发事件,但是在间隔一定时间后还是需要不断触发

//节流:每两秒钟执行一次
let mark = true;
window.onscroll = function(){
    if(mark){
        setTimeout(() => {
            console.log("hello world");
            mark = true;
        },2000)
    }
    mark = false;
}

使用闭包封装防抖和节流

//使用闭包封装防抖函数
function debounce(fn,delay){
    let timer = null;
    function eventFun(){
        if(timer !== null){
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            fn();
            timer = null
        },delay)
    }
    return eventFun;
}

//节流
function throttle(fn,delay){
    let mark = true
    //这种return更直观,推荐
    return function(){
        if(mark){
            setTimeout(() => {
                fn();
                mark = true;
            },delay)
        }
        mark = false;
    }
}

window.onscroll = throttle(() => {
            console.log("hello world");
        },300);

第18节:BOM概述

一、概述

《javaScript高级程序设计》这本书讲过: javaScript = ECMAScript + DOM + BOM

  • DOM: 文档对象模型 Document Object Model
  • BOM:浏览器对象模型 Browser Object Model

二、Window对象(全局对象)

windows对象是全局对象,所有在浏览器可以直接使用的方法都是window对象的方法,其中document对象和alert()方法就是window的属性,使用时window.document和window.alert() 一般可省略window。

首先写一个打开vscode写一个案例计时器方法 示例代码如下:

window.setTimeout(()>={
    console.log("hello word")
},1000)

如上代码时一个计时器方法使用setTimeout方法,一秒钟输出一次hello word;

计时器方法上节已经讲过了,本节主要讲的还是弹出框方法,弹出框的方法主要分为三种:

  • alert()
    alert("hello word") //直接弹出编写的参数
  • prompt()
let str = prompt("请输入你的名字") //prompt方法中可以加入一个参数,但是通常我们只加入一个参数就可以了。
console.log(str) //点击确定控制台会输出弹出框的内容,点击取消会返回null
  • confirm()
let result= confirm("确定是否删除此弹框") //confirm返回的是一个布尔值,也可以加入一个参数
console.log(result) //点击确定控制台会返回true 取消返回false

在开发应用中,一般不适用系统自带的弹出框,移动端可能会被屏蔽。

三、location对象

  1. location.href - 属性返回当前页面的URL - "https://www.baidu.com"
  2. location.hostname - 主机的域名 - "wwww.baidu.com"
  3. location.pathname - 当前页面的路径和文件名 "/s"
  4. location.port - 端口 - "8080"
  5. loaction.protocol - 协议 - "https:"

四、navigator对象

获取访问者浏览器的信息

navigator.userAgent 检查当前设备,并在控制台输出

判断访问者的浏览器类型

let equipment = checkagent(navigator.userAgent)
console.log(equipment)
function checkagent(ua) {
    let isWindowsPhone = /(?:Windows Phone)/.test(ua),
        isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,
        isAndroid = /(?:Android)/.test(ua),
        isFireFox = /(?:Firefox)/.test(ua),
        isChrome = /(?:Chrome|CriOS)/.test(ua),
        isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),
        isPhone = /(?:iPhone)/.test(ua) && !isTablet,
        isPc = !isPhone && !isAndroid && !isSymbian,
        isWechat = /(?:MicroMessenger)/.test(ua);
    let equipment = {
        isTablet: isTablet,
        isPhone: isPhone,
        isAndroid: isAndroid,
        isPc: isPc,
        isWechat:isWechat
    };
    if (equipment.isAndroid) {
        return "安卓手机";
    } else if (equipment.isPhone) {
        return "苹果手机";
    } else if (equipment.isTablet) {
        return "平板";
    } else if (equipment.isPc) {
        return "电脑";
    }
}

第19节:原始类型与引用类型

一、原始类型与引用类型的基本概念

image-20220319195258316

原始类型赋值给变量,遍历存储的是这个值本身,而你用类型赋值给变量,变量存储的是一个引用,这个引用会指向内存中的这个对象。

二、原始类型与引用类型的差异

接下来,我们在实际案例中展示一下原始类型与引用类型的区别:

原始类型与引用类型赋值的区别

实例代码(demo01.html)

var str1 = "hello world";
var str2 = str1;
str1 = "hello javascript";

console.log(str1);   //hello javascript
console.log(str2);   //hello world

字符串是原始类型,str1和str2两个变量存储的都是字符串类型的数据,所以对其中一个变量赋值不会影响第二个变量。

下面我们将程序中的字符串换成对象,再来看看这个例子(demo02.html)。

var obj1 = {name:'xiaoming'};
var obj2 = obj1; 
obj1.name = 'xiaohong';

console.log(obj1.name);    //xiaohong
console.log(obj2.name);    //xiaohong

程序最后输出的两个值是相同的,但是我们只是将obj1的name属性赋值为xiaohong,并没有将obj2的name属性赋值为xiaohong,为什么他们都变成了同一个值?这就是原始类型与引用类型的区别:变量存储的原始类型,仅仅是存储它的值,所以我们将存储原始类型的变量重新赋值,不会影响其他变量,但是变量存储引用类型的时候,情况有所改变,变量并不是存储这个对象本身,而是存储这个对象的引用,而这个引用可以指向这个变量本身,所以当我们将obj1赋值给obj2的时候,其实是让这两个变量的引用同时指向一个对象,这样当我们改变这个对象的时候,两个变量都会有变化。

原始类型与引用类型比较的区别

接下来我们来看一下原始类型与引用类型比较的时候有什么来区别(demo03.html)。

var str1 = 'hello world';
var str2 = 'hello world';
console.log(str1 == str2);     //true 

var obj1 = {name:'xiaoming'};
var obj2 = {name:'xiaoming'};
console.log(obj1 == obj2);     //false

在上面的代码中,两个字符串都是'hello world'他们比较后的返回值是true,但是两个对象name属性都是'xiaoming',比较后的返回值却是false,这也是原始类型与引用类型本质上的却别导致的,原始类型存的是值,比较的也是值,如果值相等,则返回true,如果值不等,则返回false,引用类型存的是应用,比较的也是引用,如果两个引用指向同一个对象,返回true,指向不同对象,则返回false,上面的例子中,两个对象虽然属性相同,但明显是不同的两个对象,他们就像两个重名的学生一样,即使有相同的名字,也不会是同一个人,所以返回值是false。

原始类型与引用类型传参的区别

下面我们分别将原始类型和引用类型当做参数传给一个函数,看看有什么样的区别(demo04.html)

var str = "hello world";
function fun(s){
    s = "hello javascript";
}

fun(str);
console.log(str);   //hello world

在上面的代码中我们将变量str传给函数fun,在fun内部将参数将另一个字符串赋值给参数,然后在函数外面输出str,发现str的值并没有变,然后我们再来看下面这个例子(demo05.html)

var obj = {name:'xiaoming'};
function fun(o){
    o.name = 'xiaohong'
}

fun(obj);
console.log(obj.name);  //xiaohong

将参数换成引用类型后,我们发现再次输出obj的时候,它的值已经变成了函数中赋的值,这是因为原始类型传参的时候,实参是形参的副本,改变形参的时候不会影响实参,而应用类型传参的时候,形参和实参的对象都指向一个引用,这样当我们修改形参的值的时候,其实是操作了内存中的对象,所以函数外部变量的值也就跟着变化了。

原始类型与引用类型的类型检测

  • 原始类型:typeof(值)
  • 引用类型:值 instanceof 类型

三、对象的浅度克隆

上面我们说了原始类型与引用类型的区别,在实际开发的过程中,有一种情况,我们需要得到一个对象的备份。例如我们有一个对象:

var student = {
    name:'xiaoming',
    age:2,
    sex:'male'
}

现在希望或得一个student对象的克隆对象,这两个对象所有属性都相同,我们修改其中一个对象的时候不会影响另一个对象。我们来编写一个函数,参数是一个对象,返回值是这个对象的克隆对象。

我们首先需要了解一下for...in语句(demo06.html)

var student = {name:'xiaoming',age:2,sex:'male'};
for(var i in student){
    console.log("属性"+i);
    console.log("值"+student[i]);
}

我们通过for...in语句可以遍历对象中的属性和属性值,这样我们就可以很容易地实现对象克隆的功能(demo07.html)。

var student1 = {name:'xiaoming',age:2,sex:'male'};

function clone(obj){
    var newobj = {};    //创建一个新对象
    for(var i in obj){  //遍历传进来的参数对象
        newobj[i] = obj[i];  //将传进来的参数的属性和属性值赋值给新对象的同名属性。
    }
    return newobj;      //返回新创建的对象
}

var student2 = clone(student1);
student1.name = 'xiaohong';
console.log(student1.name);
console.log(student2.name);

在上面的代码中,我们成功复制了student1,当我们修改student1的时候,student2不会改变。但是在上面的例子中,对象的所有属性值都是原始类型,如果将对象换成下面的对象

var student = {
    name:'xiaoming',
    age:2,
    sex:'male',
    friends:["Lily","lucy"]
}

然后再用我们的clone方法克隆这个对象,就会发现,当我们改变friends属性的时候,两个对象都会改变。所以这样的clone方法只能叫“浅度克隆”,如果希望对象中所有属性都能被克隆,那么需要“深度克隆”,“深度克隆”我们会在下一章讲解。

第20节:异步编程

一、同步与异步

异步: 可以多条任务线去执行程序,一条任务卡顿不影响其他任务。

二、异步数据场景

function getData(){
    setTimeout(() => {
        let data = "hello world"; //模拟一个异步数据
        return data; //无法通过返回值输出数据。return只能返回同步的数据
    },500)
}

let data = getData();
console.log(data) //值为undefined,普通函数无法通过返回值返回异步数据。

二、回调函数

回调函数是将函数作为参数传递给另一个函数,通过回调函数可以获取异步数据,实例代码如下所示:

function getData( next ){ //参数为一个函数
    setTimeout(() => {
        let data = "hello world"; //模拟一个异步数据
        next(data); //将数据传递给这个函数
    },500)
}

getData((data) => {
    console.log(data);  //可以获取到异步数据
})

接下来制作一个案例巩固这个例子:定义两个异步获取数据的函数,一个是getFirstName,另一个是getLastName,将两个函数获取到的数据连接成为一个名字,实例代码如下所示:

function getFirstName( next ){
    setTimeout(() => {
        let data = "李";
        next(data);
    },500)
}

function getLastName( next ){
    setTimeout(() => {
        let data = "小明";
        next(data);
    },500)
}

getFirstName(first => {
    getLastName( last => {
        let name = first + last;
        console.log(name);
    })
})

虽然成功获取到了数据,但是通过上面的例子,我们发现,多个异步数据使用回调函数来嵌套,代码的维护成本会越来越高,接下来,我们使用es2015的语法来逐步优化这个案例。

三、promise对象

在ES2015中新加入了Promise对象,Promise对象用来解决异步问题,关于异步问题,我们会在第8章详细讲解,本章只要概括性地了解Promise对象的语法即可,创建一个Promise对象的语法如下所示:

// resolve的作用是把异步获取的数据结果传递出来。
let getData = new Promise((resolve) => {
    let data = "string data";
    resolve(data);
})

Promise构造函数的参数是一个函数,而函数的形参resolve同样也是一个函数,调用resolve可以将数据从promise对象中传递出来。

使用Promise对象,模拟刚才的异步获取数据,实例代码如下所示:

// promise的构造函数,需要传递一个参数
function getLastName(){
    return new Promise(function(resolve){
        // resolve也是一个函数
        setTimeout(() => {
            let data = "小明"
        	resolve(data);
        },500)

    });
}

// 客户端代码
let promise = getLastName();
promise.then(function(d){
    console.log(d);
})

使用Promise对象,定义刚才案例中的两个函数,一个是getFirstName,另一个是getLastName,将两个函数获取到的值链接到一起。

四、async函数

async函数

async函数与Promise对象结合使用,可以优雅地处理异步问题,声明async函数的语法如下所示:

async function fun(){
    //async函数内部可以使用await关键字
}

我们用一个实际的案例来讲解async函数与Promise对象的用法。

在Promise对象的示例中,我们用resolve函数将数据传递出来之后,然后可以用async函数中的await关键字接收数据,完整的实例代码如下所示。

// resolve的作用是把异步获取的数据结果传递出来。
let getData = new Promise((resolve) => {
    let data = "string data";
    resolve(data);
})

async function showData(){
    let data = await getData;
    console.log(data)
}

showData();

await后面跟着一个Promise对象,可以获取到Promise对象内部resolve传递出来的数据,这这里需要注意的是:

await关键字必须写在async函数内部。

第21节:倒计时效果

一、倒计时效果需求分析

在电商网站中我们经常会遇到倒计时抢购的网页效果,在页面中会有一个倒计时的始终,当时间距离目标时间越近,时钟的时间就越小。需要统计日、时、分、秒。当最后一秒结束后,一个抢购按钮从不可点击变成可点击。

二、需求分析

通过需求的描述,我们知道程序中有两个时间,一个是当前时间,一个是目标时间,当前时间是不断变化的,目标时间不会变化。倒计时始终记录的是当前时间和目标时间的时间差。那么如何计算两个时间的时间差呢,我们可以通过时间戳来实现

var target = new Date("2017-8-5 22:30");
var targetTime = target.getTime();

function getTimeNow(){      
    var now = new Date();
    var nowTime = now.getTime();
    console.log(targetTime - nowTime);
}

setInterval(getTimeNow,1000);

我们已经输出了距离目标时间的时间戳,现在我们需要一个函数将毫秒数转换成天、小时、分钟和秒。

var ms = 17420000;      //定义一个毫秒数
function getResultTime(ms){
    var days = Math.floor(ms / (24 * 60 * 60 * 1000));  //计算出天数
    var ms1 = ms % (24 * 60 * 60 * 1000); //计算出天后还剩多少毫秒
    var hours = Math.floor(ms1 / (60 * 60 * 1000)); //计算出小时
    var ms2 = ms % (60 * 60 * 1000); //计算出小时候还有多少毫秒
    var minutes = Math.floor(ms2 / (60 * 1000)); //计算出分钟
    var ms3 = ms % (60 * 1000); //计算出分钟后还有多少毫秒
    var seconds = Math.floor(ms3 / 1000); //计算出秒数
    return {                               //返回值是一个对象,这个对象包括了日、小时、分钟、秒
        days:days,
        hours:hours,
        minutes:minutes,
        seconds:seconds
    }
}
var time = getResultTime(ms);
console.log(time.days);
console.log(time.hours);
console.log(time.minutes);
console.log(time.seconds);

下面我们可以通过这个行数,将我们得到的距离目标时间的毫秒数,计算出距离目标时间的天、小时、分钟和秒,并将时间更新到页面上。 示例代码如下

<h1></h1>
<script>
    var h1 = document.querySelector("h1");
    var target = new Date("2017-8-5 22:30");
    var targetTime = target.getTime();
    var ms;
    function getTimeNow(){      
        var now = new Date();
        var nowTime = now.getTime();
        ms = targetTime - nowTime;
        var time = getResultTime(ms);
        var timeResult = time.days + "天" + time.hours + "小时" + time.minutes + "分钟" + time.seconds + "秒";
        h1.innerHTML = timeResult;
    }

    setInterval(getTimeNow,1000);

    function getResultTime(ms){
        var days = Math.floor(ms / (24 * 60 * 60 * 1000));  
        var ms1 = ms % (24 * 60 * 60 * 1000); 
        var hours = Math.floor(ms1 / (60 * 60 * 1000)); 
        var ms2 = ms % (60 * 60 * 1000); 
        var minutes = Math.floor(ms2 / (60 * 1000)); 
        var ms3 = ms % (60 * 1000); 
        var seconds = Math.floor(ms3 / 1000); 
        return {                               
            days:days,
            hours:hours,
            minutes:minutes,
            seconds:seconds
        }
    }
    
</script>