javascript

1 - JAVASCRIPT的介绍

1.1 javascript的诞⽣

  • 1994 年,⽹景公司(NetScape)发布了Navigator浏览器0.9版, 这是历史上第⼀个⽐较成熟的

    浏览器,引起了⼴泛关注。但是,这个版本的浏览器只能⽤来浏览,不具备与访问者互动的能

    ⼒。。。。⽹景公司急需要⼀⻔⽹⻚脚本语⾔,使得浏览器可以与⽹⻚进⾏互动。

  • 1995年4⽉,⽹景公司录⽤了34岁的系统程序员Brendan Eich, 他只⽤10天时间就把Javascript设计出

    来。布兰登·艾奇

1.2 javascript的特点

  • javascript是⼀个轻量级的语⾔

  • javascript是可以插⼊HTML⻚⾯的编程代码

  • javascript⽀持⽬前所有的浏览器

  • 解释执⾏不需要编译

  • 基于对象,内置⼤量的现成对象,编写少量的程序就可以完成⽬标

1.3 javascript的组成

  • ECMAScript javascript的语法标准
    • ECMAScript 是由ECMA 国际( 原欧洲计算机制造商协会)进⾏标准化的⼀⻔编程语⾔,这种语⾔在万维⽹上应⽤⼴泛,它往往被称为 JavaScript或 JScript,但实际上后两者是 ECMAScript 语⾔的实现和扩展。
  • DOM(Document Object Model)⽂档对象模型: javascript 操作⽹⻚元素的API
  • BOM(Browser Object Model) javascript 操作浏览器部分功能的API

1.4 javascript 和 Html css 的关系

  • Html ⽤来编写⽹⻚的结构
  • css 美化⽹⻚添加样式
  • javacript 实现⽹⻚和客户之间的沟通,让⽹⻚有活⼒

2 - JAVASCRIPT的书写位置

2.1 在html标签中使⽤ ⾏内式

<button onclick="alert('wsfsf ')">按钮</button>
  • 可以将单⾏或少量 JS 代码写在HTML标签的事件属性中(以 on 开头的属性),如:onclick
  • 注意单双引号的使⽤:在HTML中我们推荐使⽤双引号, JS 中我们推荐使⽤单引号
  • 可读性差, 在html中编写JS⼤量代码时,不⽅便阅读;
  • 引号易错,引号多层嵌套匹配时,⾮常容易弄混;
  • 特殊情况下使⽤

2.2 在HTML内部使⽤

标签,可以放在任意的地⽅ ⻚内式
  • head标签中使⽤,需要 load 事件,否则获取不到 DOM 元素。
 <script>
 window.onload = function () {
 alert('wwwww')
 }
 </script>
  • body标签中使⽤,⼀般在 HTML 代码之后。
  • 可以将多⾏JS代码写到 script 标签中
  • 内嵌 JS 是学习时常⽤的⽅式

2.3 外部调⽤

javascript在script标签的src属性中设置 在外部的脚本中不能包

括script标签。 外链式

  • 利于HTML⻚⾯代码结构化,把⼤段 JS代码独⽴到 HTML ⻚⾯之外,既美观,也⽅便⽂件级别的复⽤

  • 引⽤外部 JS⽂件的 script 标签中间不可以写代码

  • 适合于JS 代码量⽐较⼤的情况

2.4 js代码书写需要注意的问题:

  1. 在⼀对script标签中,出现错误,那么这对script标签中的后⾯的代码则不会再往下执⾏;
  2. 如果有⼀对的script标签中出现错误,不会影响其他script标签对的执⾏;
  3. script 中可以写什么内容: type="text/javascript" 是标准的写法,也可以写language="javascript",但是在⽬前的html中,type和language则可以省略,因为html是遵循h5的标准。
  4. 有时兼容性的问题,type和language会同时出现;
  5. script可以在⻚⾯中可以出现多对;
  6. 如果script是引⼊外部的js⽂件,则标签内不能再写js代码,要写则需要重新写⼀⾏script包裹;

3 - JAVASCRIPT显示数据的⽅式

3.1 使⽤ window.alert() 弹出警告框

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"> <title>text</title>
</head>
<body><h1>我的第⼀个⻚⾯</h1>
<p>我的第⼀个段落</p>
<script type="text/javascript">
window.alert("我的第⼀个弹窗");
</script>
</body>
</html>

3.2 使⽤document.write() ⽅法将内容写到html⽂档中

-直接将内容写⼊⻚⾯,会清空之前所有的⽂档内容,如果⽂档流执⾏完毕,会导致⻚⾯刷新,全部重绘。

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"> <title>Title</title>
</head>
<body> <button>按纽</button>
<script >
var btn = document.querySelector('button');
btn.addEventListener('click', ()=>{
document.write('kkkkk');
 })
</script>
</body>
</html>
================点击之后⻚⾯就变成了这样===========================
<html> <head></head>
<body>kkkkk</body>
</html>

3.3 使⽤innerHTML写⼊到HTML元素。

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"> <title>text</title>
</head>
<body>;<h1>我的第⼀个⻚⾯</h1>
<p id="demo">我的第⼀个段落</p>
<script type="text/javascript">
document.getElementById("demo").innerHTML = "段落被修改了"; </script>
</body>
</html>

3.4 使⽤console.log()写⼊到浏览器的控制台。

  • F12 启⽤调试模式, 在调试窗⼝中点击 "Console" 菜单。

  • console.warn("警告输出") ,

  • console.error('这是⼀个错误')

  • console.dir() 显示⼀个对象所有的属性和⽅法。

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"> <title>test</title>
</head>
<body><script type="text/javascript">
var a = 5;
var b = 6;
var c = a + b;
console.log(c);
</script>
</body>
</html>

3.5 ⽤户输⼊ prompt()语句

prompt()语句就是专⻔⽤来弹出能够让⽤户输⼊的对话框。⽤⼀个变量来接收值,且不论⽤户输⼊的是什么都是⼀个字符串。

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"> <title>test</title>
</head>
<body><script type="text/javascript">
var a = prompt('今天的天⽓如何');
console.log(a);
</script>
</body>
</html>

3.6 confirm

在⽹⻚中弹出提示框,显示信息,⼀般和if 判断来配合使⽤,相⽐alert多了⼀个取消的按钮。

<script>
comfirm("Hello,JavaScript!");
</script>

3.7 javascript显示⽅式的说明和对⽐

3.7.1 console.log()和alert相⽐

console.log()的⽤处主要是⽅便你调式javascript⽤的, 你可以看到你在⻚⾯中输出的内
容。
相⽐alert他的优点是:他能看到结构化的东⻄,如果是alert,弹出⼀个对象就是[object
object],但是console能看到对象的内容。console不会打断你⻚⾯的操作,如果⽤alert弹出
来内容,那么⻚⾯就死了,但是console输出内容后你⻚⾯还可以正常操作。

3.7.2 document.write 和 innerHTML

1、document.write是直接写⼊到⻚⾯的内容流,如果在写之前没有调⽤document.open, 浏览器会⾃动调⽤open。每次写完关闭之后重新调⽤该函数,会导致⻚⾯被重写。
2、innerHTML则是DOM⻚⾯元素的⼀个属性,代表该元素的html内容。你可以精确到某⼀个具体的元素来进⾏更改。如果想修改document的内容,则需要修改document.documentElement.innerElement。 
3、innerHTML很多情况下都优于document.write,其原因在于其允许更精确的控制要刷新⻚⾯
的那⼀个部分。

3.7.3 alert和prompt的区别

alert("我很帅"); 直接使⽤不需要变量
var a = prompt(“请输⼊⼀个数字”) 需要⽤⼀个变量来接收⽤户的输⼊,且这个值⼀定是⼀个字符串

4 - JAVASCRIPT的语法

4.1 js 基本的代码规范:

  • javascript对换⾏、缩进、空格不敏感。

  • 每条语句的末尾都要加上分号,除了if、for、function语句结尾处不加分号,如果不加分号,压缩之后将不能运⾏。

  • 所有的符号都是英⽂的。

  • js中的字符串可以使⽤单引号,也可以使⽤双引号,⼀般使⽤双引号;

  • 区分⼤⼩写

  • 变量是弱类型的,定义变量时要⽤var运算符声明变量,可以将它初始化为任意值。因此,可以随时改变变量所存数据的类型(尽量避免这样做)。

  • 变量、函数的命名必须要有意义

  • 变量的名称⼀般⽤名词

  • 函数的名称⼀般⽤动词

  • 操作符规范

  • // 操作符的左右两侧各保留⼀个空格
    for (var i = 1; i <= 5; i++) {
    if (i == 3) {
    break; // 直接退出整个 for 循环,跳到整个for循环下⾯的语句
     }
    console.log('我正在吃第' + i + '个包⼦呢');
    }
    
  • 关键词、操作符之间后加空格

  • image-20210802093501115

4.2 javascript的注释

  • 单⾏ // 单⾏注释可以嵌套单⾏、多⾏注释

    • for (var i = 1; i <= 5; i++) {
      if (i == 3) {
      break; // 单⾏注释前⾯注意有个空格
       }
      console.log('我正在吃第' + i + '个包⼦呢');
      }
      
  • 多⾏ /* 内容 */ 多⾏注释可以嵌套单⾏注释,但是 不能嵌套多⾏注释

    • /*多⾏注释*/
      var name;
      
  • 括号表示代码块,代码块表示⼀系列应该按顺序执⾏的语句,这些语句被封装在左括号({)和右

    括号(})之间。

5 - JAVASCRIPT的字⾯量

“直接量”即常量,就是⼀些不可改变的值,也称为“字⾯量”,看⻅什么就是什么。 字⾯量的种类

  • 数字(number)字⾯量,可以是整数或者是⼩数,或者是科学计数(e)。

    • 3.14
      1001
      123e5
      
  • 字符串(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;}
      

6 - 变量

  • 变量表示⼀些可以变化的数据,当⼀个数据的值经常变化或是不确定时,就应该⽤变量来表示。 变量的

    作⽤:⽤来操作数据的(可以读取,可以存储)

6.1 声明变量

// 声明变量
var age; // 声明⼀个 名称为age 的变量
  • var 是⼀个 JS关键字,⽤来声明变量( variable 变量的意思 )。使⽤该关键字声明变量后,计算机会⾃动为变量分配内存空间,不需要程序员管
  • age 是程序员定义的变量名,我们要通过变量名来访问内存中分配的空间

6.2 赋值

age = 10; // 给 age 这个变量赋值为 10
  • = ⽤来把右边的值赋给左边的变量空间中 此处代表赋值的意思

  • 变量值是程序员保存到变量空间⾥的值

6.3 变量的初始化

var age = 18; // 声明变量同时赋值为 18
// 声明⼀个变量并赋值, 我们称之为变量的初始化。

6.4 变量语法扩展

  • 变量使⽤前必须⽤var运算符进⾏声明,且不需要声明变量的类型,如果不声明,就使⽤,就会报错。

    • var test = "hi";
      var test1 = 1;
      
  • 可以⽤⼀个var声明多个变量,变量之间⽤逗号分隔。

    • var test1 = 1, test2 = "string";
      
  • 可以⽤同⼀个 var 语句声明的变量不必具有相同的类型。

    • var test = "hi", age = 25;
      
  • var声明的变量并不⼀定要初始化⼀个值,没有经过初始化的值是 undefined

    • var test; // 这样也是有效的,
      
  • 变量可以存放不同类型的值。这是弱类型变量的优势,如可以把变量初始化为字符串类型的值,之后把它设置为数字值。

    • var test = "hi";
      alert(test);
      test = 55;
      alert(test);
      
  • 变量声明不是必须的。没有经过var声明的变量是隐式全局变量,是可以被删除的,经过 var 声明的变量是不能被删除的

    • var sTest = "hello ";
      sTest2 = sTest + "world";
      alert(sTest2);
      
    • ⾸先,sTest 被声明为字符串类型的值 "hello"。接下来的⼀⾏,⽤变量 sTest2 把 sTest 与字符串"world" 连在⼀起。变量 sTest2并没有⽤var运算符定义,这⾥只是插⼊了它,就像已经声明过它⼀样。 ECMAScript的解释程序遇到未声明过的标识符时,⽤该变量名创建⼀个全局变量,并将其初始化为指定的值。这是该语⾔的便利之处,不过如果不能紧密跟踪变量,这样做也很危险最好的习惯是像使⽤其他程序设计语⾔⼀样,总是声明所有变量

  • 更新变量

  • ⼀个变量被重新复赋值后,它原有的值就会被覆盖,变量值将以最后⼀次赋的值为准。

    • var age = 18;
      age = 81; // 最后的结果就是81因为18 被覆盖掉了
      
  • 同时声明多个变量

  • 同时声明多个变量时,只需要写⼀个 var, 多个变量名之间使⽤英⽂逗号隔开。

    • var age = 10, name = 'zs', sex = 2;
      
  • 声明变量特殊情况

    • 情况 说明 结果
      var age ; console.log (age); 只声明 不赋值 undefined
      console.log(age) 不声明 不赋值 直接使⽤ 报错
      age = 10; console.log (age); 不声明 只赋值 隐式全局变量 10

6.2 变量的命名规范

变量名需要遵守两条简单的规则:

  • 第⼀个字符必须是字⺟、下划线(_)或美元符号($),不能以数字开头,会报错,不能以js的关键字做为变量。

  • 余下的字符可以是下划线、美元符号或任何字⺟或数字字符;

  • 变量名⼀般都是⼩写;

下⾯的变量都是合法的:

  • var test;
    var $test;
    var $1;
    var _$te$t2;
    
  • 保留字不能⽤作变量名

    • abstract、boolean、byte、char、class、const、debugger、double、enum、export、
      extends、final、float、goto
      implements、import、int、interface、long、native、package、private、protected、
      public、short、static、super、synchronized、throws、transient、volatile
      
  • ⼏种常⻅的命名⽅式:

    1. Camel 标记法:⾸字⺟是⼩写的,接下来的字⺟都以⼤写字符开头。例如:

      1. var myTestValue = 0, mySecondValue = "hi";
        
    2. Pascal 标记法 ⾸字⺟是⼤写的,接下来的字⺟都以⼤写字符开头。例如:

      1. var MyTestValue = 0, MySecondValue = "hi";
        
    3. 匈⽛利类型标记法:

      1. 在以Pascal标记法命名的变量前附加⼀个⼩写字⺟(或⼩写字⺟序列),说明该变量的类型。例如,i表示整数,s 表示字符串,如下所示“

        var iMyTestValue = 0, sMySecondValue = "hi";
        

6.3 变量值交换

6.3.1 第⼀种⽅法:利⽤临时变量进⾏交换

<script type="text/javascript">
 var num1 = 10;
 var num2 = 20;
 var temp = num1;
 num1 = num2;
 num2 = temp;
 console.log(num1, num2); // 20 10
</script>

6.3.2 第⼆种⽅法: ⼀般⽤于数字交换

<script type="text/javascript">
 var num1 = 10;
 var num2 = 20;
 num1 = num1 + num2;
 num2 = num1 - num2;
 num1 = num1 - num2;
 console.log(num1, num2); // 20 10
</script>

6.3.3 第三种⽅法: 位运算交换

<script type="text/javascript">
 var num1 = 10;
 var num2 = 20;
 num1 = num1 ^ num2;
 num2 = num1 ^ num2;
 num1 = num1 ^ num2;
 console.log(num1, num2); // 20 10
</script>

6.4 关键字和保留字

6.4.1 标识符

标识(zhi)符:就是指开发⼈员为变量、属性、函数、参数取的名字。
标识符不能是关键字或保留字。

6.4.2 关键字

关键字:是指 JS本身已经使⽤了的字,不能再⽤它们充当变量名、⽅法名。
包括:break、case、catch、continue、default、delete、do、else、finally、for、
function、if、in、instanceof、new、return、switch、this、throw、try、typeof、
var、void、while、with 等。

6.4.3 保留字

保留字:实际上就是预留的“关键字”,意思是现在虽然还不是关键字,但是未来可能会成为关键字,
同样不能使⽤它们当变量名或⽅法名。
包括:boolean、byte、char、class、const、debugger、double、enum、export、
extends、fimal、float、goto、implements、import、int、interface、long、mative、
package、private、protected、public、short、static、super、synchronized、
throws、transient、volatile 等。
注意:如果将保留字⽤作变量名或函数名,那么除⾮将来的浏览器实现了该保留字,否则很可能
收不到任何错误消息。当浏览器将其实现后,该单词将被看做关键字,如此将出现关键字错误。

7 - 数据类型

7.1 数据类型简介

  • 为什么需要数据类型
    • 在计算机中,不同的数据所需占⽤的存储空间是不同的,为了便于把数据分成所需内存⼤⼩不同的数据,充分利⽤存储空间,于是定义了不同的数据类型。 简单来说,数据类型就是数据的类别型号。⽐如姓名“张三”,年龄18,这些数据的类型是不⼀样的。
  • 变量的数据类型
    • 变量是⽤来存储值的所在处,它们有名字和数据类型。变量的数据类型决定了如何将代表这些值的位存储到计算机的内存中。JavaScript 是⼀种弱类型或者说动态语⾔。这意味着不⽤提前声明变量的类型,在程序运⾏过程中,类型会被⾃动确定:
var age = 10; // 这是⼀个数字型
var areYouOk = '是的'; // 这是⼀个字符串

在代码运⾏时,变量的数据类型是由 JS引擎 根据 = 右边变量值的数据类型来判断 的,运⾏完毕之后, 变量就确定了数据类型。JavaScript 拥有动态类型,同时也意味着相同的变量可⽤作不同的类型:

var x = 6; // x 为数字
var x = "Bill"; // x 为字符串
  • 数据类型的分类

    JS 把数据类型分为两类:

    • 简单数据类型 (Number,String,Boolean,Undefined,Null)也叫值类型或者基本数据类型。
    • 复杂数据类型 (object) 也叫引⽤值

7.2 typeof运算符查看变量值的类型

typeof是js中唯⼀不会报错的

typeof 运算符有⼀个参数,即要检查的变量或值。例如:

var sTemp = "test string";
alert (typeof sTemp); //输出 "string"
<script type="text/javascript">
var a = 1;
var b = '我是谁';
var c = 1.3242424;
var d = 3e7;
var e;
var f = "";
console.log(typeof a, typeof b, typeof c, typeof d);
console.log(typeof e);
console.log(typeof f);
console.log(typeof bb);
</script>

输出结果:

image-20210802095649378

对变量或值调⽤ typeof 运算符将返回下列值之⼀:

  • undefined - 如果变量是 Undefined 类型的
  • boolean - 如果变量是 Boolean 类型的
  • number - 如果变量是 Number 类型的
  • string - 如果变量是 String 类型的
  • object - 如果变量是⼀种引⽤类型或 Null 类型的
  • function-函数

typeof 运算符对于 null 值会返回 "Object"。这实际上是 JavaScript 最初实现中的⼀个错误,然后被

ECMAScript 沿⽤了。现在,null 被认为是对象的占位符,从⽽解释了这⼀⽭盾,但从技术上来说,它

仍然是原始值。

7.3 简单数据类型

简单数据类型(基本数据类型)

JavaScript 中的简单数据类型及其说明如下:

image-20210802095758091

7.3.1 Undefined 类型

Undefined 类型只有⼀个值,即 undefined。当声明的变量未初始化时,该变量的默认值是undefined。

var oTemp;

undefined 并不同于未定义的值。但是,typeof 运算符并不真正区分这两种值。

console.log(typeof e); //e 被定义了,但是没有给值进⾏初始化。也是输出undefined
console.log(typeof bb); //bb没有被定义过 也是输出undefined

如果对bb使⽤除typeof之外的其他运算符的话,会引起错误,因为其他运算符只能⽤于已声明的变量上。

当函数⽆明确返回值时,返回的也是值 "undefined"

function testFunc() {}
alert(testFunc() == undefined); //输出 "true"

调⽤函数时,应该提供的参数没有提供,该参数等于 undefined

function f(x) {
return x; }f() // undefined

对象没有赋值的属性

// 对象没有赋值的属性
var o = new Object();
o.p // undefined

7.3.2 object

Null 类型

它只有⼀个专⽤值 null,即它的字⾯量。值 undefined 实际上是从值 null 派⽣来的,因此ECMAScript 把它们定义为相等的。

alert(null == undefined); //输出 "true"

尽管这两个值相等,但它们的含义不同。undefined 是声明了变量但未对其初始化时赋予该变量的值,null则⽤于表示尚未存在的对象。如果函数或⽅法要返回的是对象,那么找不到该对象时,返回的通常是 null。想让⼀个变量的值是Null时,必须⼿动指定 var a = Null;

数组类型(array)

function类型

7.3.3 Boolean 类型

它有两个值 true 和 false (即两个 Boolean 字⾯量)。即使 false 不等于 0,0 也可以在必要时被转换成 false,这样在 Boolean 语句中使⽤两者都是安全的

任何的⾮0数值都是true,包括正负⽆穷,只有0和NaN是false 任何的⾮空字符串都是true,只有空
字符串是false 任何的对象都是true,除了Null和 undefined是false,空数组[],和空对象{}是true

7.3.4 Number 类型

ECMA-262 中定义的最特殊的类型是 Number 类型。这种类型既可以表示 32 位的整数,还可以表示
64位的浮点数。直接输⼊的(⽽不是从另⼀个变量访问的)任何数字都被看做 Number 类型的字⾯量。
  • javascript 可正常计算的范围:⼩数点前16位,后16位。
  • ⼋进制数和⼗六进制数

js 可以表示不同进制的数字,得看浏览器的⽀持

整数也可以被表示为⼋进制(以8为底)或⼗六进制(以16为底)的字⾯量。⼋进制字⾯量的⾸数字必须是 0,其后的数字可以是任何⼋进制数字(0-7),

var iNum = 070; //070 等于⼗进制的 56

要创建⼗六进制的字⾯量,⾸位数字必须为 0,后⾯接字⺟ x,然后是任意的⼗六进制数字(0 到 9 和A 到 F)。这些字⺟可以是⼤写的,也可以是⼩写的。

var iNum = 0x1f; //0x1f 等于⼗进制的 31
var iNum = 0xAB; //0xAB 等于⼗进制的 171

尽管所有整数都可以表示为⼋进制或⼗六进制的字⾯量,但所有数学运算返回的都是⼗进制结果。

  • 浮点数

要定义浮点值,必须包括⼩数点和⼩数点后的⼀位数字(例如,⽤ 1.0 ⽽不是 1)。这被看作浮点数字⾯量。

var fNum = 5.0;

对于浮点字⾯量的有趣之处在于,⽤它进⾏计算前,真正存储的是字符串

  • 科学计数法

对于⾮常⼤或⾮常⼩的数,可以⽤科学计数法表示浮点数,可以把⼀个数表示为数字(包括⼗进制数字)加 e(或 E),后⾯加乘以 10 的倍数。

var fNum = 5.618e7

该符号表示的是数 56180000。把科学计数法转化成计算式就可以得到该值:5.618 x 107。

  • 特殊的 Number 值

前两个是 Number.MAX_VALUE 和 Number.MIN_VALUE,它们定义了 Number 值集合的外边界。所有ECMAScript数都必须在这两个值之间。不过计算⽣成的数值结果可以不落在这两个值之间。 最后⼀个特殊值是 NaN,表示⾮数(NotaNumber)。NaN是个奇怪的特殊值。⼀般说来,这 种情况发⽣在类型(String、Boolean 等)转换失败时。例如,要把单词 blue 转换成数值就会失败,因为没有与之等价的数值。与⽆穷⼤⼀样,NaN也不能⽤于算术计算, 是⼀个⾮法的数字,当对数值进⾏计算,没有结果返回,则返回NaN,NaN 的另⼀个奇特之处在于,它与⾃身不相等,这意味着NaN也不等于NaN。

  • NaN 属性是代表⾮数字值的特殊值。

含义:表示⾮数字, 主要⽤在将字符串解析成数字出错的场合。

5 - 'x' // NaN

⼀些数学函数的运算结果也会出现NAN

Math.acos(2) // NaN
Math.log(-1) // NaN
Math.sqrt(-1) // NaN

0除0也会是NaN 1/0是Infinity

运算规则:

NaN不等于任何值,包括它本身 NaN === NaN // false

Nan在布尔运算时被当作是false Boolean(NaN) // false

NaN与任何数(包括它⾃⼰)的运算,得到的都是NaN

NaN + 32 // NaN
NaN - 32 // NaN
NaN * 32 // NaN
NaN / 32 // NaN

7.3.4.1 注意

(1)⽆论是⼩数还是整数都是数字类型

(2)不要⽤⼩数去验证⼩数

var x=0.1;
var y=0.2;
var sum=x+y;
console.log(sum==0.3); // false

(3)不要⽤NaN判断是不是Nan,⽽是⽤isNan(值或者是变量句)

判断结果不是⼀个数字可以使⽤isNaN(变量名)
var num;
var sum=num+10;//NaN
console.log(sum);
console.log(isNaN(sum));//不是数字为true,是数字结果为false

(4)想表示⼗进制就是正常的数字

(5)想表示⼋进制以0开头

(6)想表示⼗六进制以0x开头

(7)像010这样的字符串,有些浏览器会当成是8进制,有些则会当成是10进制,为了保证准确性,在进⾏转换时,parseInt()时,⽤第⼆个参数明确具体的进制数。

7.3.5 String 类型

String 类型的独特之处在于,它是唯⼀没有固定⼤⼩的原始类型。可以⽤字符串存储 0 或更多的Unicode 字符, 字符串字⾯量是由双引号(")或单引号(')声明的。⽽Java则是⽤双引号声明字符串,⽤单引号声明字符。但是由于 ECMAScript 没有字符类型,所以可使⽤这两种表示法中的任何⼀种。

var sColor1 = "red";
var sColor2 = 'red';

只要有⼀个是字符串,其他的是数字,那么结果也是拼接,不是相加

console.log("我" + "爱" + "你"); //连字符,把三个独⽴的汉字,连接在⼀起了
console.log("我+爱+你"); //原样输出
console.log(1+2+3); //输出6

字符串 - 或 ***** 数值 = 数值 如果有⼀个是字符串,另⼀个不是字符串,使⽤ - 或 ***** 号,此时会发⽣计算

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"> <title>test</title>
</head>
<body><script type="text/javascript">
var a = "54";
var b = 99;
// 浏览器会⾃动把字符串类型的数字转换为数字类型,这叫隐式转换。
var c = b - a;
console.log(typeof c, c);
</script>
</body>
</html>

输出结果:

image-20210802100623511

如果把a改为字⺟ var a = 'e'; 则输出结果为:

image-20210802100635816

如果把-换成*乘号,var c = b * a; 则输出结果为:

image-20210802100649073

7.4 类型转换

7.4.1 转换成字符串

7.4.1.1 toString()⽅法

<script type="text/javascript">
// 数字转字符串
var num1 = 123;
var str1 = num1.toString();
console.log(str1, typeof str1) //123 string
// Boolean 转字符串
var bool = true;
var str2 = bool.toString();
console.log(str2, typeof str2); // true string
</script>

需要注意的地⽅:

null 和 undefined 没有toString()的⽅法,强⾏调⽤会报错 toString 不会改变原变量的值,它只会
将转化的结果返回

7.4.1.2 String() 函数

有时候有些值没有toString()的⽅法,⽐如null和undefined

如果Number 和 Boolean ⽤String函数进⾏字符串的转换,实际上是调⽤了toString的⽅法 对于
null和undefined来说,没有toString的⽅法,会在内部产⽣⼀个新的字符串
<script type="text/javascript">
// 数字转字符串
var num1 = 123;
var str1 = String(num1);
console.log(str1, typeof str1); // 123 string
// Boolean 转字符串
var bool = true;
var str2 = String(bool);
console.log(str2, typeof str2); // true string
// null转字符串
var nu = null;
var str3 = String(nu);
console.log(str3, typeof str3); // null string
var unde = undefined;
var str4 = String(unde);
console.log(str4, typeof str4) // undefined string
</script>
  • 任何数据和+连接在⼀起都会转为字符串,其内部的原理和String⼀样

    • <script>
      var num1 = 123;
      var str1 = num1 + '';
      console.log(str1, typeof str1); // 123 string
      var bool = true;
      var str2 = bool + '';
      console.log(str2, typeof str2); // true string
      var nul = null;
      var str3 = nul + '';
      console.log(str3, typeof str3); // null string
      var unde = undefined;
      var str4 = unde + '';
      console.log(str4, typeof str4); // undefined string
      </script>
      

7.4.2 转换成数字

  • parseInt() parseFloat() 和 Number()。

  • 注意的地⽅

    • parseInt()把值转换成整数,parseFloat()把值转换成浮点数。只有对String类型调⽤这些⽅法,它们才能正确运⾏;对其他类型都是先转化成字符串(string()⽅法)后再进⾏转换。他的返回值只有两种,⼀种是⼗进制的整数,⼀种是NaN。

    • Number()函数中⽆论混合字符串中是否存在有效的整数,都会返回NaN,利⽤parseInt()和parseFloat()可以提取字符串中的有效整数。

    • parseInt()和parseFloat()的区别是,前者可以提取有效的整数,后者可以提取有效的⼩数。

    • 对⾮String使⽤parseInt()或者parseFloat(),会将其转换成String然后再操作

  • var str11 = true;
    var res13 = parseInt(str11); // 这⾥相当于parseInt("true");
    console.log(res13); // NaN
    var res14 = Number(str11);
    console.log(res14); // 1
    

7.4.2.1 Number()函数

1、字符串转数字

如果是纯数字字符串,则直接将其转换为数字

<script>
var str1 = "123";
var num1 = Number(str1);
console.log(num1, typeof num1); // 123 "number"
</script>

如果字符串中含有⾮数字的内容,则转换为NaN

<script>
var str1 = "abc123";
var num1 = Number(str 1);
console.log(num1, typeof num1); // NaN "number"
</script>

如果字符串是⼀个空串,或是⼀个全部是空格的字符串,则转换为0

<script>
var str1 = ""; // 空串
var str2 = " "; // 全部是空格
var str3 = "123 " // 最后含有两个空格
var str4 = " 123" // 开头含有两个空格
var num1 = Number(str1);
var num2 = Number(str2);
var num3 = Number(str3);
var num4 = Number(str4);
console.log(num1, typeof num1); // 0 "number"
console.log(num2, typeof num2); // 0 "number"
console.log(num3, typeof num3); // 123 "number"
console.log(num4, typeof num4); // 123 "number"
</script>

2、undefined 转数字 NaN

<script>
// undefined转数字
var unde = undefined;
var num1 = Number(unde);
console.log(num1, typeof num1); // NaN "number"
</script>

3、null转数字 0

<script>
// null转数字
var nu = null;
var num1 = Number(nu);
console.log(num1, typeof num1) // 0 "number"
</script>

4、布尔转数字 true转成1 false转成0

<script>
// 布尔值转数字
var bool = true;
var num1 = Number(bool);
console.log(num1, typeof num1) // 1 "number"
var bool1 = false;
var num2 = Number(bool1);
console.log(num2, typeof num2); // 0 "number"
</script>

7.4.2.2 parseInt() 把值转换成整数

  1. 带有⾃动净化的功能;只保留字符串最开头的数字,后⾯的中⽂⾃动消失parseInt()⽅法⾸先查看位置0处的字符,判断它是否是个有效数字;如果不是,该⽅法将返回NaN,不再继续执⾏其他操作。但如果该字符是有效数字,该⽅法将查看位置 1 处的字符,进⾏同样的测试。这⼀过程将持续到发现⾮有效数字的字符为⽌,此时 parseInt() 将把该字符之前的字符串转换成数字。
  2. ⾃动带有截断⼩数的功能:取整,不四舍五⼊字符串中包含的数字字⾯量会被正确转换为数字,⽐如 "0xA" 会被正确转换为数字 10。不过,字符串 "22.5" 将被转换成 22,因为对于整数来说,⼩数点是⽆效字符
  3. parseInt()⽅法还有基模式,可以把⼆进制、⼋进制、⼗六进制或其他任何进制的字符串转换成整数。
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"> <title>test</title>
</head>
<body><script type="text/javascript">
var a = '123adfsfw'; // 数字开头的
var b = 'afsfsf234'; // 字⺟开头的
var c = '1sf23iwr'; // 开头1个数字,混全的
var d = '1334北京!!';
var f = '1.23'; // ⼩于1.5的
var f1 = '1.98'; //⼤于1.5的
var aa = 'AF';
var bb = '010';
console.log(parseInt(a))
console.log(parseInt(b))
console.log(parseInt(c))
console.log(parseInt(d))
console.log(parseInt(f))
console.log(parseInt(f1))
console.log(parseInt(aa, 16))
console.log(parseInt(bb, 8))
</script>
</body>
</html>

输出结果:

image-20210802101205320

7.4.2.3 parseFloat() 把值转换成浮点数

  • 会解析第⼀个,遇到第⼆个或者⾮数字结束;

  • 如果第⼀位不是有效数字,什么也提取不到;

  • 不⽀持第⼆个参数,只能解析10进制数;

  • 如果解析内容⾥只有整数,解析成整数;

var fNum1 = parseFloat("12345red"); //返回 12345
var fNum2 = parseFloat("0xA"); //返回 NaN
var fNum3 = parseFloat("11.2"); //返回 11.2
var fNum4 = parseFloat("11.22.33"); //返回 11.22
var fNum5 = parseFloat("0102"); //返回 102
var fNum1 = parseFloat("red"); //返回 NaN

8 - 运算符

  • 作⽤:

运算符是告诉程序执⾏特定算术或逻辑操作的符号, 例如告诉程序, 某两个数相加, 相减等,由于浮点数精度的问题,不要⽤浮点数进⾏加减乘除。

  • 分类
    • 按照功能分:算术、位、关系、逻辑
    • 按照操作个数分:
      • 单⽬运算 只有⼀个操作数:如i++
      • 双⽬运算 有两个操作数:如a+b
      • 三⽬运算 也称为问号表达式 如:a > b ? 1:0
    • 结合性 JavaScript中各种运算符的结合性分为两种: 左结合性(⾃左⾄右) 和 右结合性(⾃右⾄左)
      • ⾃左⾄右,即先左后右 例如表达式: x - y + z; 则y 应先与“-”号结合,执⾏ x-y 运算,然后再执⾏+z 的运算。 这种⾃左⾄右的结合 ⽅向就称为“左结合性”。
      • ⾃右⾄左,即先右后左 例如:如x = y = z = 10 由于“=”的 右结合性,应先执⾏z = 10; 再执⾏y= z 再执⾏x = y运算。

8.1 加法运算符 +

  • ⾮Number类型的值,除字符串外,都会先转为Number类型的值后再进⾏计算
var sun = true + 2;
console.log(sun, typeof sun); // 3 "number"
var sun1 = true + false;
console.log(sun1, typeof sun1); // 1 "number"
var sun3 = 3 + null; // null 转为0
console.log(sun3, typeof sun3); // 3 “number"
  • 任何值和NaN进⾏运算时,结果都是NaN
var sun2 = NaN + 3;
console.log(sun2, typeof sun2); // NaN "number"
var sun4 = undefined + 3;
console.log(sun4, typeof sun4); // NaN "number"
  • 任何值和字符串进⾏加法运算,都会先转为字符串,然后和字符串进⾏拼接
var sun5 = 3 + "3";
console.log(sun5, typeof sun5); // 33 string
var sun6 = null + "3";
console.log(sun6, typeof sun6); // null3 string
var sun7 = undefined + "3";
console.log(sun7, typeof sun7); // undefined3 string
var sun8 = true + '3';
console.log(sun8, typeof sun8); // true3 string

8.2 减法运算符 -

  • 所有的⾮Number类型(包括字符串)的数据类型,都会将⾮Number数据类型先转为Number后再进⾏计算
var difference = 10 - 2;
console.log(difference, typeof difference); // 8 "number"
var difference1 = 10 - '2';
console.log(difference1, typeof difference1); // 8 "number"
var difference2 = 10 - true;
console.log(difference2, typeof difference2); // 9 "number"
var difference3 = 10 - null;
console.log(difference3, typeof difference3); // 10 "number"
  • 任何数据类型和NaN进⾏减法运算时,结果都是NaN
var difference4 = 10 - undefined;
console.log(difference4, typeof difference4); // NaN "number"
var difference5 = 10 - NaN;
console.log(difference5, typeof difference5); // NaN "number"

8.3 乘法运算 *

  • 规律和减法⼀样

8.4 除法运算符 /

  • 规律和减法⼀样

8.5 取余运算符 %

  • m % n 求余 相当于 m / n 取余数
  • m 等于0 返回 0
var remainder = 0 % '4';
console.log(remainder, typeof remainder); // 0 "number"
  • n 等于0 返回 NaN
// 当n为0时
var remainder1 = '4' % 0;
console.log(remainder1, typeof remainder1); // NaN "number"
  • m > n 正常取余
// 当m>n时
var remainder2 = 5 % 2;
console.log(remainder2, typeof remainder2); // 1 "number"
  • m < n 结果是m
// 当m<n时
  var remainder3 = 2 % 5;
  console.log(remainder3, typeof remainder3); // 2 "number"
  • 其他的规则和减法⼀样

8.6 ⼀元运算符

只有⼀个操作数的运算符 + -

  • +号不会对数字产⽣任何的影响,类似与数字中的正号

  • 对⾮Number类型的值,会调⽤Number()函数先进⾏转换,然后再运算

// + 对数字不会产⽣影响,对与⾮Number类型的数值,会先调⽤Number()函数进⾏转换
var num1 = + "6";
console.log(num1, typeof num1); // 6 "number"
var num2 = + null;
console.log(num2, typeof num2); // 0 "number"
var num3 = + undefined;
console.log(num3, typeof num3); // NaN "number"
var num4 = + true;
console.log(num4, typeof num4); // 1 "number"
  • -号相当于数字中的负数,对数字进⾏取反操作
var num5 = - - '6'; // 负负得正
console.log(num5, typeof num5); // 6 "number"
var num6 = 1 - -"3" + 3;
console.log(num6, typeof num6); // 7 "number"
var num7 = - "123abc"; // 转换的时候调⽤的Number()函数,不是调⽤的parseInt()函
数。
console.log(num7, typeof num7); // NaN "number"

8.7 赋值运算符

赋值运算符可以分为简单赋值运算符和复合赋值运算符

  • 简单赋值运算符

    • 格式:变量名 = 数据
    • =的左边只能是变量
    • 多个赋值运算符可以组成赋值表达式,具有右结合性
    // 从右往左进⾏赋值
    // 先将10赋值给c,然后变量c中储存的值再赋值给b,变量b中储存的值再赋值给a,最后a b c
    储存的值都是10
    a = b = c = 10;
    
  • 复合赋值运算符

    • += 加后赋值 如: a += 1 相当于 a = a + 1
      -= 减后赋值 如:a -= 1 相当于 a = a -1
      *= 乘后赋值 如:a *= 1 相当于 a = a * 1
      /= 除后赋值 如:a /= 1 相当于 a = a / 1
      %= 取模后赋值 如:a %= 1 相当于 a = a % 1
      
  • 复合赋值表达示运算

    • 格式:a *= 1 + 2
    • 由于赋值运算符是右结合性,所以会先计算=右边的,然后再进⾏计算 相当于 a = a * (1+ 2)
    var a = 10
    a *= 100 + 30;
    console.log(a) // 1300 a = 10 * (100+30)
    

8.8 ⾃增⾃减运算符

javascript提供了 ++ -- 这两种⾃增和⾃减的运算符来简化 i = i + 1, i = i - 1 这样的操作

⾃增、⾃减 求值的过程

  • ⽆论运算符在前⾯还是后⾯(前缀还是后缀)变量⾃身都会发⽣改变
  • 后缀表达式 i ++ , i -- 先⽤i的值做为整个表达式的值,再进⾏加1或减1的操作,即:先⽤后变
// a ++ a-- 先⽤后变
var a = 10, b; b = a ++; // 先⽤i的值做为赋值运算右边的值,把a的值赋值给b,然后a再⾃身加1
console.log(a); // 11
console.log(b); // 10
var num = 10;
sun = num++ + 10;
console.log(sun); // 20 先完成num + 10 后,再⾃身加1
console.log(num); // 11
  • 前缀表达式 ++ i, -- i 先⾃身加1或减1,然后再将i的值做为表达式的值,即:先变后 ⽤

  • // ++a --a 先变后⽤
    var a1 = 10 , b;
    b1 = ++ a1; // a1⾃身先加上1,然后把值赋值给b1
    console.log(a1); // 11
    console.log(b1); // 11
    var num1 = 10;
    sun1 = ++num1 + 10;
    console.log(sun1); // 21 先⾃身加1 后,再进⾏ + 10 的运算
    console.log(num1); // 11
    

8.9 关系运算符

关系运算符的返回值,只有两个,要么是真(true)要么是假(false)

  • javascript ⽀持的关系运算符

image-20210802102227365

  • 对于⾮数值类型进⾏⽐较时,会转换为数值再进⾏⽐较
var a = 1;
console.log(a == true); // true
console.log( a === true); // false
console.log(a > '0'); // ture
  • 如果两侧都是字符串,不会转换为数字进⾏⽐较,⽽是分别⽐较字符串的Unicode编码的⼤⼩
    • ⽐较编码时是从左到右⼀位⼀位进⾏⽐较
    • 如果两位相同,则会⽐较下⼀位,可以⽤来做英⽂的排序
    • ⽐较中⽂没有意义
console.log('a' < 'b'); // true
console.log('abc' < 'abd'); // true
console.log('你' > "我"); // false
  • null、 undefined、 NaN⽐较
console.log(null == 0); // false
console.log(undefined == 0); // false
// 永远不要判断两个NaN是否相等
console.log(NaN == NaN); // false
/*
 * 可以通过isNaN()函数来判断⼀个值是否是NaN
 * 如果该值是NaN(不是⼀个数字)则返回true,否则返回false
 */
var num = NaN;
console.log(isNaN(num)); // true
console.log(isNaN(1)); // false
console.log(isNaN(null)); // false
console.log(isNaN(undefined)); // true
console.log(isNaN('abc')); // true
// undefined 衍⽣⾃ null, 所以返回true
console.log(null == undefined); // true;
console.log(null === undefined); // false;
// == 判断值是否相等
// == 会进⾏数据类型转换
console.log("123" == 123); // true
// === 判断值和类型时候同时相等
// === 不会进⾏数据类型转换
console.log("123" === 123); // false
============================================
<script type="text/javascript">
var a = 'abc'; // 字⾯量
var b = 'abc'; // 字⾯量
var c = new String('abc');
var c1 = new String('abc');
alert(a == b); // true
alert(a === b); // true
alert(a == c); // true
alert(a === c); // false 因为new过之后类型是object对象
alert(c == c1); // false 字⾯量才可能相等,任何对象都是不相等的
var d = [1, 2, 3];
var e = [1, 2, 3];
var f = new Array(1, 2, 3);
alert(d == e); // false 数组本身就是⼀个对象,虽然是【1, 2, 3】这样写,但
是也是会通过new来实现
alert(d === e); // false
alert(d == f); // false
  • 注意的地⽅

    • ⽐较两个字符串类型的数字,结果可能有偏差,所以需要提前转型;

    • console.log("1111123" < "124" ); // true
      console.log("1111123" < 124 ); // false
      

8.10 逻辑运算符

  1. && (与运算)
  • 运⾏结果 条件A && 条件B

    • 先去判断条件A成不成⽴,如果A成⽴了才去判断B是否成⽴,如果A不成⽴则不会去判断B两个条件为真才为真,有⼀个为假就为假
  • 短路测试

    • 条件 && 表达式 条件为真时,后⾯的表达式执⾏

    • console.log(7 > 3 && console.log('执⾏了')) // 执⾏了
      
    • 注意的地⽅

        1. 对于⾮Boolean类型的数值,逻辑与会⾃动将其转换为Boolean类型来判断;

        2. 如果条件A成⽴,不管条件B成⽴与不成⽴都会返回条件B;

        3. 如果条件A不成⽴,则返回条件A本身;

      • // 如果条件A不成⽴,则返回a的本身
        var result = null && 7 > 3;
        console.log(result); // null
        // 如果条件A成⽴,不管B成不成⽴,都将返回B
        var result1 = 123 && 'abc';
        console.log(result1) // abc
        var result2 = 123 && null;
        console.log(result2); // null
        var result3 = 0 && 1 + 2 && 456 * 56789
        console.log(result3) // 0 因为⼀开始0为假,短路了,后⾯的全部不执⾏,
        所以返回0
        
  1. || (或运算)

    • 运⾏结果 条件A || 条件B

      • 有⼀个为真就为真,两个为假才为假
      • 如果A成⽴了,则不会去判断条件B,只有在A不成⽴的时候才会去判断条件B
    • 短路测试

      • 条件 || 表达式 条件为假时,后⾯的表达式执⾏

      • console.log(7 < 3 && console.log('执⾏了')) // 执⾏了
        
    • 注意的地⽅

      1. 对于⾮Boolean类型的值,逻辑或会将其⾃动转为布尔类型的值来判断;

      2. 如果条件A不成⽴,则不管条件B成不成⽴都会返回条件B本身的数值;

      3. 如果条件A成⽴,则返回条件A本身的数值;

  2. !(⾮运算)

    • 运⾏结果 !条件A
      • 对条件A进⾏取反 ⽐如:条件A成⽴true,取反后为false
    • 注意的地⽅
    1. 对⼀个值进⾏两次取反,它不会发⽣变化;

    2. 对⾮布尔值进⾏操作,则先将其转换为Boolean,然后再进⾏取反,所以将⼀个数值转换为Boolean,除了Boolean()函数外,还可以使⽤ !!数值, 实现的原理和Boolean(数值)是⼀样的;

8.11 逗号运算符

逗号表达式就是把多个表达式连接起来,组成⼀个表达式。

  • 结果

从左往右计算,⼀直到最后⼀个,整个表达式的值也是最后⼀个表达式的值。

var a, b; b = (a=3, --a, a*5);
console.log(b) // 10
  • 使⽤注意事项
    • 程序中使⽤逗号表达式,通常是求表达式内各个表达式的值,并不⼀定要求,整个逗号表达式的值;
    • 并不是所有出现逗号的地⽅都组成逗号表达式,例如在变量说明中,函数参数表中的逗号只是分隔作⽤;
var a, b, c; //这⾥的逗号只是分隔符
function sendMessage(num, content) { //这⾥的逗号只是分隔符
console.log(num, content);
}

8.12 三⽬运算符

  • 格式:

条件表达式 ?语句1:语句2;

  • 求值规则

条件表达式为真时,执⾏语句1;为假时执⾏语句2.

// 如果条件表达式为真,则执⾏语句1,为假则执⾏语句2
true?alert("语句1"):alert("语句2"); // 语句1
false?alert('语句1'):alert('语句2') // 语句2

⼀个案例:

<script>
// 接收⽤户输⼊的三个数,找出最⼤的那个
var num1, num2, num3, maxNum;
num1 = Number(prompt("请输⼊数字1"));
num2 = Number(prompt("请输⼊数字2"));
num3 = Number(prompt('请输⼊数字3'));
maxNum = num1 > num2 ? num1:num2;
maxNum = maxNum > num3 ? maxNum:num3;
console.log(maxNum);
</script>
  • 注意的地⽅
    • 条件运算符?:是⼀对运算符,不能分开单独使⽤
    • 如果条件表达式的求值结果不是⼀个Boolean,则会转换为Boolean值后再运算

8.13 运算符的优先级

  • 运算符的优先级共分为15级,1最⾼,15最低;

  • 计算规则

    • 先计算优先级⾼的;
    • 如果优先级⼀样,则谁在左边先算谁
    • 可以使⽤()来改变优先级
  • 优先级图示

image-20210802103322491

9 - 流程控制

9.1 流程控制概念

在⼀个程序执⾏的过程中,各条代码的执⾏顺序对程序的结果是有直接影响的。很多时候我们要通
过控制代码的执⾏顺序来实现我们要完成的功能。
简单理解:流程控制就是来控制代码按照⼀定结构顺序来执⾏
流程控制主要有三种结构,分别是顺序结构、分⽀结构和循环结构,代表三种代码执⾏的顺序。

image-20210802103425816

9.2 顺序流程控制

顺序结构是程序中最简单、最基本的流程控制,它没有特定的语法结构,程序会按照代码的先后顺序,

image-20210802103616175

9.3 分⽀流程控制

  • 分⽀结构

  • image-20210802103645720

  • if 语句

    • ⼀个分⽀,要么执⾏,要么不执⾏

      1. 语法

        if (条件){
        代码块;
        }
        
      2. 执⾏过程

        如果条件是true,则执⾏代码块,如果是false,不会执⾏代码块

  • if else 语句

    两个分⽀,只能执⾏⼀个分⽀,像这种情况也可以使⽤三元表达式

    1. 语法

      if(表达式){
      代码块1;
      }else{
      代码块2;
      }
      
    2. 执⾏过程

      如果表达式的结果是true,执⾏代码块1,如果结果是false,则执⾏代码块2

  • if else if else if .. else 多分⽀语句

    多个分⽀,最终也执⾏⼀个

    1. 语法

      if(表达式1){
      代码块1; }else if(表达式2){
      代码块2;
      .....
      }else{
      代码块3; }
      else if 可以写多个,具体看需要
      else 可以不⽤写,具体看需求
      
      1. 执⾏过程

      先判断表达式1的结果,如果是true,就执⾏代码块1,后⾯的不会执⾏,如果结果是

      false,则会判断表达式2的结果,如果是true,就执⾏代码块2,后⾯的不执⾏,如果是false,

      则会继续判断下⼀个表达式,依此类推,最终,如果else if的表达式结果都是false,则执⾏

      最后的else代码块的语句。

9.4 switch case 语句

多个分⽀,最终也执⾏⼀个(最终执⾏⼏个由break决定)

  • 语法
switch(表达式){
case 值1:代码1;
break;
case 值2:代码2;
break;
case 值3:代码3;
break;
case 值4:代码4;
break;
.....
default:代码5;
break; }
default 后的break可以省略
default 也可以省略
case 值和表达式的值进⾏⽐较的时候是严格模式,相当是===的⽐较;
break,如果不写,会从case匹配到的值开始,⼀直执⾏代码块,直到遇到break跳出
  • 执⾏过程

获取表达式的值,和值1进⾏⽐较,如果相同,则执⾏代码1,遇到break跳出,后⾯的代码不执⾏;如果和值1不相同,则和值2进⾏⽐较,依此类推,如果表达式的值和case的值都不⼀样,就执⾏default代码5的,跳出。

  • break的使⽤

  • <script>
    // 每个⽉有多少天
    var month = Number(prompt("⽉份"));
    if (!isNaN(month)) {
      switch (month) {
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
        case 12:
          console.log("有31天")
          break;
        case 4:
        case 6:
        case 9:
        case 11:
          console.log("有30天")
          break;
        case 2:
          console.log("有28天")
          break;
      }
    }else{
      console.log("输⼊有误")
    }
    </script>
    
    • switch 语句和 if else if 语句的区别
      • ⼀般情况下,它们两个语句可以相互替换
      • switch...case 语句通常处理 case为⽐较确定值的情况, ⽽ if…else…语句更加灵活,常⽤于范围判断(⼤于、等于某个范围)
      • switch 语句进⾏条件判断后直接执⾏到程序的条件语句,效率更⾼。⽽if…else 语句有⼏种条件,就得判断多少次。
      • 当分⽀⽐较少时,if… else语句的执⾏效率⽐ switch语句⾼。
      • 当分⽀⽐较多时,switch语句的执⾏效率⽐较⾼,⽽且结构更清晰。

10 - 循环

10.1 while 循环

  • 语法

    • while(循环条件){
      循环体;
      计数器++; }
      
    • 执⾏过程

      • 先判断循环条件是true还是false,如果条件是true,则执⾏循环体和计数器,再次判断循环条件,如果条件成⽴,就执⾏循环体,否则就退出循环。

      • 和for循环嵌套的不同,while 循环⾥⾯⾛完⼀圈就不⾛了,只循环⼀次

      • for (var i=0; i<3; i++){
        for(var j=0; j<3; j++){
        console.log(i+','+j) //
        }
        }
        # 执⾏结果
        0,0 0,1 0,2 1,0 1,1 1,2 2,0 2,1 2,2
        var i = 0;
        var j = 0;
        while (i < 3) {
        while (j < 3) {
        console.log(i + ','+ j) j ++
        }
        i++
        }
        # 执⾏结果
        0,0 0,1 0,2
        

10.2 do while 循环

  • 语法

    • do {
      循环体;
      } while(条件);
      
  • 执⾏过程

    • 先执⾏⼀次循环体,然后判断条件是true还是false,如果是false,则跳出循环,如果是true,则继续执⾏循环体,⾄到条件不满⾜,跳出循环体,
    • while 和 do while的区别
      • while 是先判断,后循环,有可能⼀次循环都不执⾏;
      • do-while 是先循环,后判断,⾄少执⾏⼀次循环;
      • do while ⾥不要放 while 循环,不会执⾏

10.3 for 循环

  • 语法

    • for(表达式1;表达式2;表达式3){
      循环体;
      }
      
  • 执⾏过程 先执⾏⼀次表达式1,然后判断表达式2的条件成不成⽴。

    • 如果成⽴,则执⾏⼀次循环体,然后执⾏表达式3,再判断表达式2成不成⽴,依此类推,⾄到表达式2不成⽴。跳出循环;

    • 如果不成⽴,则跳出循环;

    • 表达式2其实是⼀个条件,最终是true和false,多个条件之间⽤逗号隔开

    • while 与 do while 不能相互嵌套

    • // 斐波那契数列
      var num1=1, num2=1, sun=0;
      for (var i=3; i<=12; i++) {
      sun = num1 + num2;
      num1 = num2;
      num2 = sun; }
      console.log(sun);
      
    • 也可以写成这样

    • var a = 0;
      for (; a < 5, a < 3; ) { // 逗号这个js语句没有完成,分号是这个js语句完成了
      console.log(a);
      a ++;
      }
      

10.4 双重for循环

  • 双重 for 循环概述

    • 循环嵌套是指在⼀个循环语句中再定义⼀个循环语句的语法结构,例如在for循环语句中,可以再嵌套⼀个for 循环,这样的 for 循环语句我们称之为双重for循环。
  • 双重 for 循环语法

  • for (外循环的初始; 外循环的条件; 外循环的操作表达式) {
    for (内循环的初始; 内循环的条件; 内循环的操作表达式) {
    需执⾏的代码;
    }
    }
    
    • 内层循环可以看做外层循环的循环体语句
    • 内层循环执⾏的顺序也要遵循 for 循环的执⾏顺序
    • 外层循环执⾏⼀次,内层循环要执⾏全部次数
  • 打印五⾏五列星星

    • var star = '';
      for (var j = 1; j <= 3; j++) {
      for (var i = 1; i <= 3; i++) {
      star += '☆'
      }
      // 每次满 5个星星 就 加⼀次换⾏
      star += '
      '
      }
      console.log(star);
      
    • 核⼼逻辑:

      1.内层循环负责⼀⾏打印五个星星

      2.外层循环负责打印五⾏

  • for 循环⼩结

    • for 循环可以重复执⾏某些相同代码
    • for 循环可以重复执⾏些许不同的代码,因为我们有计数器
    • for 循环可以重复执⾏某些操作,⽐如算术运算符加法操作
    • 随着需求增加,双重for循环可以做更多、更好看的效果
    • 双重 for 循环,外层循环⼀次,内层 for 循环全部执⾏
    • for 循环是循环条件和数字直接相关的循环

10.5 break 语句

如果在循环中,遇到了break,则⽴即跳出当前所在的循环(注意是当前的循环,如果还有外层循环,是不会跳出的)

<script>
// break 跳出
for(var i=0; i<5; i++){
  while (true) {
    console.log("哈哈哈");
    break; // 只跳出当前这层循环
  }
}
</script>
=================================
// break 找100-200以内被7整除的第2个数
for (var i=100, j=0; i<=200; i++) {
if (i%7==0){
if(j==1){
console.log(i);
break;
}
j++; }}

10.6 continue 跳出本次循环

在循环中遇到continue关键字,直接跳出本次循环,进⼊⼀下次循环。

// 求100-200之间所有的奇数的和 continue
for (var i=100, sun=0; i<=200; i++) {
if (i%2==0) {
continue;
}
sun += i; }
console.log(sun);
=====================================
// 求整数100-200的累加值,要求跳过所有个位数为3的数
// 个数数为3,要取出来,就是10取余后,余数是3的
for (var sun=0, i=100; i<=200; i++) {
if (i%10==3) {
/* 说明个位数是3,不要 */
continue;
}
sun += i; }
console.log(sun);

11 - 数组

11.1 数组的概念

  • 数组:变量只能存储⼀个值,所谓的数组就是将多个元素(通常是同⼀类型)按⼀定顺序排列放到⼀个集合中,那么这个集合我们就称为数组。内存引⽤机制

  • 数组的作⽤:可以⼀次性存储多个数据并提供⽅便的访问(获取)⽅式。

  • 数组的所有⽅法来源于: Array.prototype

  • // ⾃⼰写数组的push⽅法
    Array.prototype.push = function () {
    for (var i=0; i<arguments.length; i++) {
    this[this.length] = arguments[i];
    }
    return this.length;
    };
    var arr = [1, 2]
    

11.2 创建数组

1、通过构造函数创建数组;

语法:
var 数组名=new Array(⻓度);
var array=new Array();//没有数据,空数组
数组的名字如果直接输出,那么直接就可以把数组中的数据显示出来,如果没有数据,就看不到
数据。
var array=new Array(5); // 只传⼀个参数,就是数组的⻓度,数组中的每个值就是
undefined
var arr = new Array(10.2) // 会报错,第⼀位传⼩数会报错

2、通过字⾯量的⽅式创建数组;

var 数组名=[]; //空数组

3、数组的读和写

arr[num] 不可以溢出读,结果是undefind,不会报错

arr[num] = xxx 可以溢出写,会把数组的⻓度撑⻓到num,前⾯的数据是undefind。

  • 数组元素:数组中存储的每个数据,都可以叫数组的元素,⽐如:存储了3个数据,数组中3个元素
  • 数组的⻓度:就是数组的元素的个数,⽐如有3个元素,就说这个数组的⻓度是3;
  • 数组索引(下标):⽤来存储或者访问数组中的数据的;数组名[下标]
  • 注意点:
    • ⽆论是构造函数的⽅式还是字⾯量的⽅式定义的数组,如果有⻓度,那么默认的是undefined;
    • 构造函数的⽅式创建数组的时候,如果在Array(⼀个数字)————>数组的⻓度(数组元素的个数);
    • 如果在Array(多个值),这个数组中就有数据了,数组的⻓度就是这些数据的个数;
    • 数组的索引和数组⻓度的关系:就是⻓度减1,就是最⼤的索引;
// for 循环遍历数组
var array = new Array(1, 2, 'mjc', undefined, true, null, new Object);
for (var i=0; i<array.length; i++) {
console.log(array[i]);
}
===================
// 求数组⾥的最⼤值
var array = new Array(1, 6, 9000, 120, 900, 1000, 0);
for (var max=array[0], i=0; i<array.length; i++) {
if (max < array[i]) {
max = array[i];
}
}
console.log(max);
==================
// 数组的倒序
var array = new Array(1, 2, 3, 4, 5, 6);
for (var i=array.length-1; i>=0; i--) {
console.log(array[i]);
}
==================
// 把数组中的每个元素⽤|拼接到⼀起产⽣⼀个字符串输出
var array = new Array(1, 2, 3, 4, 5, 6);
for (var i=0, str=''; i<array.length-1; i++) { /* 这⾥的length-1,为了不输出最后
⼀个 */
str += array[i] +'|'; }
console.log(str+array[array.length-1]); /* 输出的最后再把数组的最后⼀个加上 */
// 1|2|3|4|5|6
=================
// 去掉数组中重复的0, 把其他的元素放在⼀个新的数组中
var array = new Array(1, 0, 4, 9, 10, 0, 23);
var newArray=[];
for (var i=0; i<array.length; i++) {
if (array[i] != 0) {
newArray[newArray.length] = array[i]; // 把新数组的⻓度作为下标
}
}
console.log(newArray); // [1, 4, 9, 10, 23]
================
// 翻转数组 位置调换
// 思路:
// 1、交换的次数是⻓度的⼀半,4个元素,交换2次,5个元素也交换2次
// 2、利⽤第三⽅变量临时存储交换的值
/* 3、循环交换时:第⼀次:索引位置是0的和索引位置是length-1-0的进⾏交换;第⼆次:索
引位置是1的和索引位置是length-1-1的进⾏交换;
第三次:索引位置是2的和索引位置是length-1-2的进⾏交换。。。依此类推 */
var array = new Array(1, 2, 3, 4, 5, 6, 7);
for (var i=0; i<array.length/2; i++) {
var temp = array[i];
array[i] = array[array.length-1-i];
array[array.length-1-i] = temp; }
console.log(array);

image-20210802105209501

// 冒泡排序
/* 思路:
1、拿⼀个数字和其他的元素进⾏对⽐,每⼀个元素都要跟其他的元素对⽐⼀次;
2、⽐较的轮数是⻓度-1; 3、每⼀轮⽐较的次数:length-1-i;
4、⽤第三⽅交换变量; */
// 从⼩到⼤排序
var array = new Array(1, 3, 5, 9, 2, 6, 4);
for (var i=0; i<array.length-1; i++) {
for (var j=0;j<array.length-1-i; j++) {
var temp;
if (array[j] < array[j+1]) {
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
console.log(array); // [9, 6, 5, 4, 3, 2, 1]

11.3 伪数组

arguments对象是所有(⾮箭头)函数中都可⽤的局部变量。你可以使⽤arguments对象在函数中引⽤函数的参数。arguments对象不是⼀个 Array 。它类似于Array,但除了length属性和索引元素之外没有任何Array属性。例如,它没有 pop ⽅法。

arguments.length 可以获取函数在调⽤时,传⼊了⼏个参数,还可以使⽤arguments对象可以获取传⼊的每个参数的值(数组形式)函数名.length:可以获取形参的个数;

类数组的构成

// 类数组的构成
var obj = {
0:'a', 1:'b', 2:'c',
length:3,
push:Array.prototype.push,
splice:Array.prototype.splice // 给⼀个对象加上splice,就⻓的像数组⼀样。
}
obj.push('d')
console.log(obj) // {0: "a", 1: "b", 2: "c", 3: "d", length: 4, push: ƒ} 会发
现增加了⼀个3:'d',并且⻓度增加了

上⾯的就叫⼀个类数组,必须有⼏个组成部分

1、属性要为索引(数字)必须有length属性,最好加上push。

2、好处,可以像数组⼀样⽤,也可以像对象⼀样⽤,DOM⽣成的类数组的东⻄,全是类数组。

var obj = {
0:'a', 1:'b', 2:'c',
name:'ww',
age:123,
length:3,
push:Array.prototype.push,
splice:Array.prototype.splice
}
console.log(obj); // Object(3) ["a", "b", "c", name: "ww", age: 123, push: ƒ,
splice: ƒ]
console.log(obj.name); // ww

12 - 函数

12.1 函数的概念

在 JS ⾥⾯,可能会定义⾮常多的相同代码或者功能相似的代码,这些代码可能需要⼤量重复使⽤。虽然 for循环语句也能实现⼀些简单的重复操作,但是⽐较具有局限性,此时我们就可以使⽤ JS 中的函数。

函数:就是封装了⼀段可被重复调⽤执⾏的代码块。通过此代码块可以实现⼤量代码的重复使⽤。

12.2 函数的使⽤

声明函数

// 声明函数
function 函数名() {
//函数体代码
}
  • function 是声明函数的关键字,必须⼩写
  • 由于函数⼀般是为了实现某个功能才定义的, 所以通常我们将函数名命名为动词,⽐如 getSu

声明函数的⽅式:

  1. ⽅式1 函数声明⽅式 function 关键字 (命名函数)

    function fn(){}
    
    1. ⽅式2 函数表达式(匿名函数)
    var fn = function(){}
    
    1. ⽅式3 new Function()
    var f = new Function('a', 'b', 'console.log(a + b)');
    f(1, 2);
    var fn = new Function('参数1','参数2'..., '函数体')
    注意
    /*Function ⾥⾯参数都必须是字符串格式
    第三种⽅式执⾏效率低,也不⽅便书写,因此较少使⽤
    所有函数都是 Function 的实例(对象)
    函数也属于对象
    */
    

调⽤函数

// 调⽤函数
函数名(); // 通过调⽤函数名来执⾏函数体代码
  • 调⽤的时候千万不要忘记添加⼩括号
  • ⼝诀:函数不调⽤,⾃⼰不执⾏
  • 注意:声明函数本身并不会执⾏代码,只有调⽤函数时才会执⾏函数体代码。

调⽤函数的⽅式:

/* 1. 普通函数 */
function fn() {
console.log('⼈⽣的巅峰');
}
fn();
/* 2. 对象的⽅法 */
var o = {
sayHi: function() {
console.log('⼈⽣的巅峰');
}
}o.sayHi();
/* 3. 构造函数*/
function Star() {};
new Star();
/* 4. 绑定事件函数*/
btn.onclick = function() {}; // 点击了按钮就可以调⽤这个函数
/* 5. 定时器函数*/
setInterval(function() {}, 1000); 这个函数是定时器⾃动1秒钟调⽤⼀次
/* 6. ⽴即执⾏函数(⾃调⽤函数)*/
(function() {
console.log('⼈⽣的巅峰');
})();

函数的封装

  • 函数的封装是把⼀个或者多个功能通过函数的⽅式封装起来,对外只提供⼀个简单的函数接⼝
  • 简单理解:封装类似于将电脑配件整合组装到机箱中 ( 类似快递打包)
  • 例⼦:封装计算1-100累加和
/*
计算1-100之间值的函数
*/
// 声明函数
function getSum(){
var sumNum = 0;// 准备⼀个变量,保存数字和
for (var i = 1; i <= 100; i++) {
sumNum += i;// 把每个数值 都累加 到变量中
}
alert(sumNum);
}
// 调⽤函数
getSum();

需要注意的地⽅

  1. 函数要先定义,才能使⽤;

  2. 函数的名字和变量⼀样,要遵循命名法;

  3. 函数声明重名,后⾯的会把前⾯的覆盖,不管在那个位置;函数表达式

  4. 形参的个数和实参的个数可以不⼀到(这⼀点和python不⼀样,python⾥必须要求⼀致,否则会报错);

  5. 函数没有返回值(没有return)或者是没有明确的返回值(return后⾯没有内容)但是在调⽤的时候接收了,那么结果就是undefined(变量声明了,没有赋值也是undefined);

  6. return就是结束函数,后⾯的代码不会被执⾏;

  7. 函数名不加括号,打印出来是函数块的代码(python打印出来的是函数的内存地址);

  8. 函数之间可以相互调⽤;

  9. 函数表达式后⾯,赋值结束后,必须要加分号;

  10. 函数是有数据类型的,是function类型的;

  11. 函数可以作为参数(回调函数),也可以作为返回值;

12.3 函数的参数

函数的参数:在函数定义的时候,函数名⼦后⾯的⼩括号⾥的变量就是参数,⽬的是函数在调⽤的时候,⽤户传进来的值操作;此时函数定义的时候后⾯的⼩括号⾥的变量叫参数;在函数调⽤的时候,按照提示的⽅式,给变量赋值-->就叫传值,把这个值就传到了变量(参数)中;

函数参数语法

  • 形参:函数定义时设置接收调⽤时传⼊,⼩括号⾥的变量叫形参;函数也有length,值是形参的个数
  • 实参:函数调⽤时传⼊⼩括号内的真实数据,实参可以是变量也可以是值。
  • image-20210802105819282

参数的作⽤ : 在函数内部某些值不能固定,我们可以通过参数在调⽤函数时传递不同的值进去。

函数参数的运⽤:

// 带参数的函数声明
function 函数名(形参1, 形参2 , 形参3...) { // 可以定义任意多的参数,⽤逗号分
隔
// 函数体
}
// 带参数的函数调⽤
函数名(实参1, 实参2, 实参3...);
  1. 调⽤的时候实参值是传递给形参的

  2. 形参简单理解为:不⽤声明的变量

  3. 实参和形参的多个参数之间⽤逗号(,)分隔

函数形参和实参数量不匹配时

image-20210802105906074

注意:在JavaScript中,形参的默认值是undefined。

⼩结:

  • 函数可以带参数也可以不带参数
  • 声明函数的时候,函数名括号⾥⾯的是形参,形参的默认值为 undefined
  • 调⽤函数的时候,函数名括号⾥⾯的是实参
  • 多个参数中间⽤逗号分隔
  • 形参的个数可以和实参个数不匹配,但是结果不可预计,我们尽量要匹配

函数参数的封装

  • 为什么要封装函数的参数,当形参的个数多于4个的时候,很难记忆对应的位置,可以将参数封装成对象来接收,对象的属性是⽆序的,⽅便调⽤

    // 假如有⼀个div 需要颜⾊,⾼度,宽度,背景 字体⼤⼩,等等
    let attributes = {
     12,
    height: 39,
    color: "#ccc"
    }
    function distortion(obj) {
    console.log(obj.width, obj.height); // 12 39
    }
    distortion(attributes);
    

函数参数简单数据类型和引⽤数据类型的传递

  • 如果传递的参数是简单的数据类型,会做⼀个简单数据类型的副本传⼊到函数内部,不会对本身做出修改。

  • 如果传递的参数是引⽤数据类型,会做把数据做⼀个内存地址的副本传⼊到函数的内部,如果修改,会修改本身数据的值。

  • var a = 12 , b = {'name':'mjc'};
    function fn(c1, c2) {
    c1 = 15;
    c2.name = 'wwww'
    }
    console.log(a); //12 函数参数如果是⼀个简单的数据类型,会做⼀个值类型的
    副本传到函数的内部
    console.log(b); // ['name': 'wwww']如果是⼀个引⽤数据类型,会将引⽤数据
    的内存地址复制⼀份传⼊到函数。
    

12.4 函数的返回值

return 语句

返回值:函数调⽤整体代表的数据;函数执⾏完成后可以通过return语句将指定数据返回 。当函数调⽤之后,需要这个返回值,那么就定义变量接收,即可;如果⽤逗号分隔多个值,只返回最后⼀个值。

// 声明函数
function 函数名(){
...
return 需要返回的值;
}
// 调⽤函数
函数名(); // 此时调⽤函数就可以得到函数体内return 后⾯的值
  • 在使⽤ return 语句时,函数会停⽌执⾏,并返回指定的值
  • 如果函数没有 return ,返回的值是 undefined

break ,continue ,return 的区别

  • break :结束当前的循环体(如 for、while)
  • continue :跳出本次循环,继续执⾏下次循环(如 for、while)
  • return :不仅可以退出循环,还能够返回 return 语句中的值,同时还可以结束当前的函数体内的代码

12.5 arguments的使⽤

当不确定有多少个参数传递的时候,可以⽤ arguments 来获取。JavaScript 中,arguments实际上它是当前函数的⼀个内置对象。所有函数都内置了⼀个 arguments 对象,arguments 对象中存储了传递的所有实参。arguments展示形式是⼀个伪数组,因此可以进⾏遍历。伪数组具有以下特点:

  • 具有 length 属性

  • 按索引⽅式储存数据

  • 不具有数组的 push , pop 等⽅法

  • 注意:在函数内部使⽤该对象,⽤此对象获取函数调⽤时传的实参。

  • // 如果实参的个数少于形参的个数,那么多出来的形参的值就为undefined
    // 如果实参的个数多于形参的个数,形参只能取到对应个数的值,
    // 所有传进函数的值都会在arguments⾥
    function Max() {
    if (arguments.length > 0) {
    let a = 0;
    for (let i = 0; i < arguments.length; i++) {
    a = arguments[i] > a ? arguments[i] : a;
    }
    return a
    }
    return NaN; }
    console.log(Max(3, 6, 7, 9, 10)); // 10
    console.log(Max()); // NaN
    console.log(Max.length); // 0 函数也有length属性。
    

12.6 函数的声明⽅式

  • ⾃定义函数⽅式(命名函数)

    利⽤函数关键字 function ⾃定义函数⽅式

    // 声明定义⽅式
    function fn() {...}
    // 调⽤
    fn();
    
    • 因为有名字,所以也被称为命名函数

    • 调⽤函数的代码既可以放到声明函数的前⾯,也可以放在声明函数的后⾯

  • 函数表达式⽅式(匿名函数)

  • 利⽤函数表达式⽅式的写法如下:

  • // 这是函数表达式写法,匿名函数后⾯跟分号结束
    var fn = function(){...};
    // 调⽤的⽅式,函数调⽤必须写到函数体下⾯
    fn();
    
    • 因为函数没有名字,所以也被称为匿名函数
    • 这个fn ⾥⾯存储的是⼀个函数
    • 函数表达式⽅式原理跟声明变量⽅式是⼀致的
    • 函数调⽤的代码必须写到函数体后⾯
    var f1 = function () {
    console.log("我是⼀个函数");
    };
    console.log(f1.name); // f1
    var f1 = function abc () { // 注意这⾥的abc充当了⼀个表达式就不能充当⼀个函
    数体了
    console.log("我是⼀个函数");
    }
    abc(); // 报错
    console.log(f1.name) // abc
    // 如果是函数表达式,那么此时前⾯的变量中存储的就是⼀个函数,⽽这个变量就相当
    于是⼀个函数,就可以直接加⼩括号调⽤了。
    
    • 函数的⾃调⽤,没有名字,调⽤--声明的同时,加⼩括号直接⽤, ⼀次性的,⼀般⽤于初始化数据(function(){console.log("⾃调⽤");})(); 声明函数不能⾃调⽤,但是在声明函数前加上-/+/!/(括号)就变成了函数表达式了,能够⾃调⽤了,但是函数名就不起作⽤了;
    • 回调函数:函数作为参数使⽤,如果⼀个函数作为参数,那么我们说这个参数(函数)可以叫做回调函数,只要是看到了⼀个函数作为参数使⽤了,那么就是回调函数;

12.7 函数的执⾏上下⽂

image-20210802110454746

// 代码执⾏之前的时候,就会⽴即创建⼀个全局的执⾏上下⽂(Global Excution Context)
// 创建完了全局上下⽂之后就会把上下⽂压⼊执⾏环境栈ecs中 (Excution Context Stack)
function f1() {
console.log('f1');
}
function f2() {
console.log('f2');
f3();
}
function f3() {
console.log('f3');
f4();
}
function f4() {
console.log('f4');
}
f1(); // 代码进⼊f1准备执⾏代码: 在执⾏之前js引擎会创建⼀个f1的执⾏上下⽂ f1
Excution Context,压⼊到执⾏环境ecs中。
// 当f1执⾏完毕后,从执⾏环境ecs中弹出f1的执⾏上下⽂。
f2(); // f2函数执⾏之前创建f2的执⾏上下⽂,压⼊到执⾏环境ecs中。因为在f2中调⽤了f3
的函数。f3在执⾏之前也创建⼀个f3的执⾏上下⽂
// 并压⼊到执⾏环境ecs中。f3中也调⽤了f4函数,同样也需要创建⼀个f4的执⾏上下⽂,压⼊
到执⾏环境ecs中。
// f4 执⾏完毕。f4的Ec出栈。
// f3 执⾏完毕,f3的Ec出栈。
// f2 执⾏完毕,f2的Ec出栈。

12.8 函数的其他⼀些⽅法

  1. arguments.callee

    指向的是函数的引⽤,也就是函数本身的代码,在那个函数⾥就指代那个函数本身

    function test() {
    console.log(arguments.callee); // ƒ test() {console.log(arguments.callee);}
    }
    test()
    ============================
    var num = (function (n) {
    if (n == 1) {
    return 1
    }
    return n * arguments.callee(n-1) // return n * 函数(n -1)
    }(5))
    console.log(num); // 120
    
  2. function.caller

    函数⾃⼰的属性,在那个环境⾥调⽤,就是谁

    function f1() {
    f2()
    }
    function f2() {
    console.log(f2.caller); // 在f1⾥被调⽤了,那么就是f1本身,ƒ f1() {f2()}
    }
    f1()
    

    ⼀些练习

    // 三个数中最⼤的值
    function maxNum(x, y, z) {
    return x>y?(x>z?x:z):(y>z?y:z);
    }
    console.log(maxNum(3, 5, 9));
    ===============================================
    // 判断⼀个数是不是素数
    function primeNumber(x) {
    for (var i=2; i<x; i++) {
    if (x%i == 0) {
    return false;
    }
    return true;
    }
    }
    console.log(primeNumber(5)?"是质数":"不是质数");
    ==============================================
    // 求⼀组数字中最⼤的值
    function maxNum(array) {
    var max = array[0]; // 注意这个变量应该是定义在循环外边
    for (var i=0; i<array.length; i++) {
    if (max < array[i]) {
    max = array[i];
    }
    }
    return max; }
    console.log(maxNum([1, 3, 4, 56, 998, 233, 833]));
    ===============================================
    // 输⼊,年、⽉、⽇、获取这个⽇期是这⼀年的多少天
    // 定义判断闰年的函数
    function isLeapYear(year) {
    // 闰年:能被4整除,但是不能被100整除,或者能被400整除
    return year%4==0&&year%100!=0||year%400==0; }
    // 定义计算天数的函数
    function getDays(year, month, day) {
    var days = day;
    if (month==1) { // 如果⽤户输⼊的是1⽉份,没必要再向后算天数,直接返回天数
    return day;
    }
    // 定义⼀个数组,存储每个⽉份的天数
    var months = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    // ⼩于的是⽉份-1,⽽不是数组⻓度-1
    for (var i=0; i<month-1; i++) {
    days += months[i];
    }
    // 需要判断这个年份是不是闰年
    if (isLeapYear(year) && month>2) {
    days ++;
    }
    return days;
    }
    console.log(getDays(2000, 3, 2))
    ========================================================
    /* arguments 的应⽤ 计算N个数字的和
    定义⼀个函数,如果不确定⽤户是否传⼊了参数,或者说不知道⽤户传了⼏个参数,没办法计
    算,但是如果在函数中知道了参数的个数,也知道了每个参数的值,可以 */
    function f1() {
    var sun = 0;
    for (var i=0; i<arguments.length; i++) {
    sun += arguments[i];
    }
    return sun; }
    console.log(f1(20, 30, 10, 50));
    =======================================================
    // 声名函数 重名 后⾯的会覆盖,不管在那个位置调⽤
    function f1() {
    console.log("⼩明好帅") }
    f1(); // ⼩猪好帅哦
    function f1() {
    console.log("⼩猪好帅哦") }
    f1(); // ⼩猪好帅哦
    // 函数表达式 重名后,不会覆盖,调⽤时会根据位置的不同产⽣不同的结果。
    var f2 = function () {
    console.log("星期天真好")
    }; // 函数表达式这个分号不能少
    f2(); // 星期天真好
    f2 = function () {
    console.log("星期⼀真痛苦") }
    f2(); // 星期⼀真痛苦
    // 函数的⾃调⽤,因为加⼩括号就是函数的调⽤,函数名不加括号输出就是函数的代码,⾃调⽤
    就是匿名函数代码直接加⼩括号,且匿名函数之间不影响;
    (function () {
    console.log("macbook真⾹");
    })(); // macbook真⾹
    (function () {
    console.log("Thinkpad也还⾏"
    );
    })(); // macbook真⾹
    

12.9 函数中的this

函数内部的this指向

这些 this 的指向,是当我们调⽤函数的时候确定的。调⽤⽅式的不同决定了this 的指向不同⼀般指向我们的调⽤者.

image-20210802110843499

改变函数内部 this 指向

  1. call⽅法

    call()⽅法调⽤⼀个对象。简单理解为调⽤函数的⽅式,但是它可以改变函数的 this 指向

    应⽤场景: 经常做继承.

    var o = {
    name: 'andy'
    }
    function fn(a, b) {
    console.log(this);
    console.log(a+b)
    };
    fn(1,2)// 此时的this指向的是window 运⾏结果为3
    fn.call(o,1,2)//此时的this指向的是对象o,参数使⽤逗号隔开,运⾏结果为3
    

    以上代码运⾏结果为:

    image-20210802110950744

    2.apply⽅法

    apply() ⽅法调⽤⼀个函数。简单理解为调⽤函数的⽅式,但是它可以改变函数的 this 指向。

    应⽤场景: 经常跟数组有关系

    var o = {
    name: 'andy'
    }
    function fn(a, b) {
    console.log(this);
    console.log(a+b)
    };
    fn()// 此时的this指向的是window 运⾏结果为3
    fn.apply(o,[1,2])//此时的this指向的是对象o,参数使⽤数组传递 运⾏结果为3
    

    image-20210802111022516

    3.bind⽅法

    bind() ⽅法不会调⽤函数,但是能改变函数内部this 指向,返回的是原函数改变this之后产⽣的新函数

    如果只是想改变 this 指向,并且不想调⽤这个函数的时候,可以使⽤bind

    应⽤场景:不调⽤函数,但是还想改变this指向

    var o = {
    name: 'andy'
    };
    function fn(a, b) {
    console.log(this);
    console.log(a + b);
    };
    var f = fn.bind(o, 1, 2); //此处的f是bind返回的新函数
    f();//调⽤新函数 this指向的是对象o 参数使⽤逗号隔开
    

    image-20210802111115543

    4.call、apply、bind三者的异同

    • 共同点 : 都可以改变this指向

    • 不同点:

      • call 和 apply 会调⽤函数, 并且改变函数内部this指向.
      • call 和 apply传递的参数不⼀样,call传递参数使⽤逗号隔开,apply使⽤数组传递
      • bind 不会调⽤函数, 可以改变函数内部this指向.
    • 应⽤场景

    1. call 经常做继承.

    2. apply经常跟数组有关系. ⽐如借助于数学对象实现数组最⼤值最⼩值

    3. bind 不调⽤函数,但是还想改变this指向. ⽐如改变定时器内部的this指向.

12.10 ⾼阶函数

⾼阶函数是对其他函数进⾏操作的函数,它接收函数作为参数或将函数作为返回值输出。

image-20210802111241355

此时fn 就是⼀个⾼阶函数

函数也是⼀种数据类型,同样可以作为参数,传递给另外⼀个参数使⽤。最典型的就是作为回调函数。

同理函数也可以作为返回值传递回来

12.10.1. 闭包

  1. 变量的作⽤域复习

    变量根据作⽤域的不同分为两种:全局变量和局部变量。

    1. 函数内部可以使⽤全局变量。

    2. 函数外部不可以使⽤局部变量。

    3. 当函数执⾏完毕,本作⽤域内的局部变量会销毁。

  2. 什么是闭包

    闭包(closure)指有权访问另⼀个函数作⽤域中变量的函数。简单理解就是 ,⼀个作⽤域可以访问另

    外⼀个函数内部的局部变量。

    image-20210802111349998

  3. 闭包的作⽤

    作⽤:延伸变量的作⽤范围。

    function fn() {
    var num = 10;
    function fun() {
    console.log(num);
    }
    return fun;
    }
    var f = fn();
    f();
    

    案例

    1. 利⽤闭包的⽅式得到当前li 的索引号

      for (var i = 0; i < lis.length; i++) {
      // 利⽤for循环创建了4个⽴即执⾏函数
      // ⽴即执⾏函数也成为⼩闭包因为⽴即执⾏函数⾥⾯的任何⼀个函数都可以使⽤它的i这变量
      (function(i) {
      lis[i].onclick = function() {
      console.log(i);
      }
      })(i);
      }
      
    2. 闭包应⽤-3秒钟之后,打印所有li元素的内容

      for (var i = 0; i < lis.length; i++) {
      (function(i) {
      setTimeout(function() {
      console.log(lis[i].innerHTML);
      }, 3000)
      })(i);
      }
      
    3. 闭包应⽤-计算打⻋价格

      /*需求分析
      打⻋起步价13(3公⾥内), 之后每多⼀公⾥增加 5块钱. ⽤户输⼊公⾥数就可以计算打⻋价格
      如果有拥堵情况,总价格多收取10块钱拥堵费*/
      var car = (function() {
      var start = 13; // 起步价 局部变量
      var total = 0; // 总价 局部变量
      return {
      // 正常的总价
      price: function(n) {
      if (n <= 3) {
      total = start;
      } else {
      total = start + (n - 3) * 5
      }
      return total;
      },
      // 拥堵之后的费⽤
      yd: function(flag) {
      return flag ? total + 10 : total;
      }
      }
      })();
      console.log(car.price(5)); // 23
      console.log(car.yd(true)); // 33
      

      案例

      var name = "The Window";
      var object = {
      name: "My Object",
      getNameFunc: function() {
      return function() {
      return this.name;
      };
      }
      };
      console.log(object.getNameFunc()())
      -----------------------------------------------------------------------------
      ------
      var name = "The Window";  
      var object = {    
      name: "My Object",
      getNameFunc: function() {
      var that = this;
      return function() {
      return that.name;
      };
      }
      };
      console.log(object.getNameFunc()())
      

12.10.2 递归

  1. 什么是递归

    递归:如果⼀个函数在内部可以调⽤其本身,那么这个函数就是递归函数。简单理解:函数内部⾃⼰调⽤

    ⾃⼰, 这个函数就是递归函数

    注意:递归函数的作⽤和循环效果⼀样,由于递归很容易发⽣“栈溢出”错误(stack overflow),所以

    必须要加退出条件return。

  2. 利⽤递归求1~n的阶乘

    //利⽤递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
    function fn(n) {
    if (n == 1) { //结束条件
    return 1;
    }
    return n * fn(n - 1);
    }
    console.log(fn(3));
    

    image-20210802111610498

    3.利⽤递归求斐波那契数列

    // 利⽤递归函数求斐波那契数列(兔⼦序列) 1、1、2、3、5、8、13、21...
    // ⽤户输⼊⼀个数字 n 就可以求出 这个数字对应的兔⼦序列值
    // 我们只需要知道⽤户输⼊的n 的前⾯两项(n-1 n-2)就可以计算出n 对应的序列值
    function fb(n) {
    if (n === 1 || n === 2) {
    return 1;
     }
    return fb(n - 1) + fb(n - 2);
    }
    console.log(fb(3));
    
    1. 利⽤递归遍历数据

      // 我们想要做输⼊id号,就可以返回的数据对象
      var data = [{
      id: 1,
      name: '家电',
      goods: [{
      id: 11,
      gname: '冰箱',
      goods: [{
      id: 111,
      gname: '海尔'
       }, {
      id: 112,
      gname: '美的'
       },
       ]
       }, {
      id: 12,
      gname: '洗⾐机'
       }]
       }, {
      id: 2,
      name: '服饰'
      }];
      //1.利⽤ forEach 去遍历⾥⾯的每⼀个对象
      function getID(json, id) {
      var o = {};
      json.forEach(function(item) {
      // console.log(item); // 2个数组元素
      if (item.id == id) {
      // console.log(item);
      o = item;
      return o;
      // 2. 我们想要得⾥层的数据 11 12 可以利⽤递归函数
      // ⾥⾯应该有goods这个数组并且数组的⻓度不为 0
       } else if (item.goods && item.goods.length > 0) {
      o = getID(item.goods, id);
       }
       });
      return o; }
      

13 - 作⽤域

13.1 作⽤域概述

通常来说,⼀段程序代码中所⽤到的名字并不总是有效和可⽤的,⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域。作⽤域的使⽤提⾼了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。

JavaScript(es6前)中的作⽤域有两种:

  • 全局作⽤域
  • 局部作⽤域(函数作⽤域)

13.2 全局作⽤域

作⽤于所有代码执⾏的环境(整个script标签内部)或独⽴的js⽂件。

13.3 局部作⽤域

作⽤于函数内的代码环境,就是局部作⽤域。
因为跟函数有关系,所以也称为函数作⽤域。

13.4 jS没有块级作⽤域

  • 块级作⽤域:⼀对⼤括号就可以看作是⼀块,在这块区域中定义的变量,只能在这个区域中使⽤,但是在js中在这个块级作⽤域中定义的变量,外⾯也能使⽤。说明,js中没有块级作⽤域;

  • 块作⽤域由 { } 包括。

  • 在其他编程语⾔中(如 java、c#等),在 if 语句、循环语句中创建的变量,仅仅只能在本 if 语句、本循环语句中使⽤,如下⾯的Java代码:

  • java有块级作⽤域:

    if(true){
    int num = 123;
    system.out.print(num); // 123
    }
    system.out.print(num); // 报错
    

    以上java代码会报错,是因为代码中 { } 即⼀块作⽤域,其中声明的变量 num,在 “{ }” 之外不

    能使⽤;⽽与之类似的JavaScript代码,则不会报错。

    js中没有块级作⽤域(在ES6之前)

    if(true){
    var num = 123;
    console.log(123); //123
    }
    console.log(123); //123
    

14 - 变量的作⽤域

在JavaScript中,根据作⽤域的不同,变量可以分为两种:

  • 全局变量
  • 局部变量
  • 隐式全局变量

1.全局变量

在全局作⽤域下声明的变量叫做全局变量(在函数外部定义的变量)。是使⽤var声明的,依托于window环境下的变量,那么这个变量就是全局变量,全局变量可以在⻚⾯的任何位置使⽤,如果⻚⾯不关闭,那么就不会释放,就会占空间,消耗内存;⼀切声明的全局变量,都是window的属性。

如 var a = 123; console.log(window.a) // 123

全局变量在代码的任何位置都可以使⽤

在全局作⽤域下 var 声明的变量 是全局变量

特殊情况下,在函数内不使⽤ var 声明的变量也是全局变量(不建议使⽤)

  1. 局部变量

    在局部作⽤域下声明的变量叫做局部变量(在函数内部定义的变量)

    • 局部变量只能在该函数内部使⽤
    • 在函数内部 var 声明的变量是局部变量
    • 函数的形参实际上就是局部变量
  2. 隐式全局变量

    也叫(暗式全局变量):声明的变量没有var,就叫隐式全局变量;函数内的隐式全局变量,在函

    数外也可以访问,但是隐式全局变量不会预解析进⾏变量的提升。 var a=b=c=3 b和c就是隐式

    全局变量,只有a是声明后的变量;

  3. 全局变量和局部变量的区别

    • 全局变量:在任何⼀个地⽅都可以使⽤,只有在浏览器关闭时才会被销毁,因此⽐较占内存
    • 局部变量:只在函数内部使⽤,当其所在的代码块被执⾏时,会被初始化;当代码块运⾏结束后,就会被销毁,因此更节省内存空间
    • 在⼀个函数中使⽤⼀个变量,先在该函数中搜索这个变量,找到了则使⽤,找不到则继续向外找这个变量,⼀直找到全局使⽤域,找不到则是undefined;
    • 全局变量是不能被delete删除的,隐式全局变量是可以被删除的,也就是说使⽤var是不会被删除的,没有var是可以删除的;

15 - 作⽤域链

只要是代码都⼀个作⽤域中,写在函数内部的局部作⽤域,未写在任何函数内部即在全局作⽤域中;如果函数中还有函数,那么在这个作⽤域中就⼜可以诞⽣⼀个作⽤域;根据在[内部函数可以访问外部函数变量]的这种机制,⽤链式查找决定哪些数据能被内部函数访问,就称作作⽤域链。

案例分析1:
function f1() {
var num = 123;
function f2() {
console.log( num );
}
f2();
}
var num = 456;
f1();

image-20210802112415744

作⽤域链:采取就近原则的⽅式来查找变量最终的值
var a = 1;
function fn1() {
var a = 2;
var b = '22';
fn2();
function fn2() {
var a = 3;
fn3();
function fn3() {
var a = 4;
console.log(a); //a的值 ?
console.log(b); //b的值 ?
}
}
}
fn1();

image-20210802112441424

16 - 预解析

1 预解析的相关概念

js的执⾏过过程:

1、语法分析,先过⼀遍看有没有语法错误;

2、预编译;

3、解释执⾏代码;

预编译也叫变量提升,不管函数在那定义的,都会把函数整体(整个函数,包括声明,但是不是函数表达

式 )提升到逻辑的最前⾯,变量声明提升,只提升声明,不会赋值。

2 变量预解析

变量的声明会被提升到当前作⽤域的最上⾯,变量的赋值不会提升。

console.log(num); // 结果是多少?
var num = 10; // ?

结果:undefined 注意:变量提升只提升声明,不提升赋值

3 函数预解析

函数的声明会被提升到当前作⽤域的最上⾯,但是不会调⽤函数。

fn();
function fn() {
console.log('打印');
}

结果:控制台打印字符串 --- ”打印“

注意:函数声明代表函数整体,所以函数提升后,函数名代表整个函数,但是函数并没有被调⽤!

4 函数表达式声明函数问题

函数表达式创建函数,会执⾏变量提升

fn();
var fn = function() {
console.log('想不到吧');
}

结果:报错提示 ”fn is not a function"

解释:该段代码执⾏之前,会做变量声明提升,fn在提升之后的值是undefined;⽽fn调⽤是在fn被赋值为函数体之前,此时fn的值是undefined,所以⽆法正确调⽤

注意点:

  1. 预解析中,变量声名的提升,只会在当前的作⽤域中提升,提升到当前的作⽤域的最上⾯,函数中的变量只会提前到局部作⽤域的最前⾯,不会成为全局的;

  2. 预解析会分段(多对的script标签中函数重名,预解析的时候不会冲突)

预解析代码:

// 预编译(变量的提升)
/*
* 预编译发⽣在函数执⾏的前⼀刻,预编译的四部曲
* 1、创建Ao对象(相当于作⽤域或是执⾏期上下⽂)
* AO = {
*
* }
* 2、找形参和变量声明,将变量和形参名作为ao属性名,值为undefined, 只找有var的
* AO = {
* a:undefined,
* b:undefined
* }
* 3、将实参值和形参值统⼀
* AO = {
* a:1,
* b:undefined
* }
* 4、在函数体⾥⾯找函数声明,将函数名作为AO属性,值是函数体,注意是函数声明,不是
函数表达式
* AO = {
* a:function a() {},
* b:undefined // b叫函数表达式,不叫函数声明
*
* }
* 预编译结束开始执⾏函数
* */
function fn (a) {
console.log(a); // function a() {}
var a = 123; // 预编译已经执⾏过var a; 这⼀步只执⾏ a = 123;
console.log(a); // 123
function a() {} // 预编译时已经执⾏过了不再执⾏
console.log(a); // 123
var b = function () {}; // 预编译的时候已经执⾏过var b; 只执⾏b=function () {}
console.log(b); // function () {}
}
fn(1);
/*
* 全局预编译
* 1、创建GO对象,
* 2、找到变量声明,只找var的,将变量名作为GO的属性,值为undefined;
练习题⽬:
* 3、找到函数声明,将函数名作为GO属性,值是函数体,注意是函数声明,不是函数表达
式
* */

练习题⽬:

// 预解析
// ⼀般变量的书写顺序是先提前声明变量,输出在后,但是预解析会把变量的声名提前到最前⾯
// 正常书写
var num = 11;
console.log(num); // 11
// 先输出,后声明
console.log(num1); // undefined;
var num1 = 12;
// 正常书写函数
function f1() {
console.log("我是⼀个函数");
}
f1(); // 我是⼀个函数
// 先调⽤,后声明
f2(); // 预解析
function f2() {
console.log("预解析");
}
// 例3;
f3(); // undefined 调⽤函数的时候,会把函数的声明提升到作⽤域的上⾯
var num3 = 20 // 这个变量会被提升到变量使⽤之前,相当于在f3()之前,var num3; 但是
没有赋值。
function f3() {
console.log(num3);
}
// 例4
var a = 25;
function abc() {
/* 相当于是
alert(a); // undefined ,预解析只会在局部变量⾥提升 var a=10 的声名,不会到全局
的a
/* 相当于是
a= 20 */
var a = 10; }
abc();
console.log(a) // 25 全局变量
// 例5
console.log(a); // 函数的代码 因为提升的是函数的声明
function a() {
console.log("aaaa");
}
var a = 1;
console.log(a); // 1
// 上⾯的代码相当于:
function a() {
console.log("aaaa");
}
console.log(a);
var a = 1;
console.log(a);
// 例6
function f4() {
console.log(num4); // undefined
var num4 = 20; }
console.log(num4); // 会报错,因为,预解析只会提升到当前作⽤域的最上⾯。不会提升到
全局作⽤域的
// 例7:注意坑
f5();
console.log(c); // 9
console.log(b); // 9
console.log(a); // 报错
function f5() {
var a=b=c=9;
console.log(a); // 9
console.log(b); // 9
console.log(c); // 9
}
// 原因:相当于是下⾯的代码
function f5() {
var a; a = 9;
// b和c是没有var的隐式全局变量
b = 9; c = 9;
console.log(a); // 9
console.log(b); // 9
console.log(c); // 9
}
f5();
console.log(c); // 9
console.log(b); // 9
console.log(a); // 报错
// 例8
f6(); // 报错
var f6 = function () {
console.log(a);
var a = 10;
};
// 原因:因为这个函数是函数表达式,是通过赋值的形式,在预解析的时候,提升声名,只会在
f6();前⾯声明var f6;并没有函数的代码,所以报错;

17 - 函数的执⾏相关

17.1 函数的执⾏上下⽂

vo 是全局作⽤域下的,Ao是局部作⽤域下(函数内部的)的

image-20210802112725255

// 代码执⾏之前的时候,就会⽴即创建⼀个全局的执⾏上下⽂(Global Excution Context)
// 创建完了全局上下⽂之后就会把上下⽂压⼊执⾏环境栈ecs中 (Excution Context Stack)
function f1() {
console.log('f1');
}
function f2() {
console.log('f2');
f3();
}
function f3() {
console.log('f3');
f4();
}
function f4() {
console.log('f4');
}
f1(); // 代码进⼊f1准备执⾏代码: 在执⾏之前js引擎会创建⼀个f1的执⾏上下⽂ f1
Excution Context,压⼊到执⾏环境ecs中。
// 当f1执⾏完毕后,从执⾏环境ecs中弹出f1的执⾏上下⽂。
f2(); // f2函数执⾏之前创建f2的执⾏上下⽂,压⼊到执⾏环境ecs中。因为在f2中调⽤了f3
的函数。f3在执⾏之前也创建⼀个f3的执⾏上下⽂
// 并压⼊到执⾏环境ecs中。f3中也调⽤了f4函数,同样也需要创建⼀个f4的执⾏上下⽂,压⼊
到执⾏环境ecs中。
// f4 执⾏完毕。f4的Ec出栈。
// f3 执⾏完毕,f3的Ec出栈。
// f2 执⾏完毕,f2的Ec出栈。

函数执⾏环境栈的变化

image-20210802112812835

17.2 函数上下⽂执⾏的⽣命周期

为什么函数可以先调⽤,后声明呢?

image-20210802112848153

image-20210802112859884

image-20210802112922953

image-20210802112940815

18 - ⾯向对象

  • 编程思想:把⼀些⽣活中做事的经验融⼊到程序中;
  • ⾯向过程:凡事都要亲⼒亲为,每件事的具体过程都要知道,注重的是过程;
  • ⾯向对象:根据需求找对象,所有的事都⽤对象来做,注重的是结果;
  • ⾯对对象的三⼤特性:封装、多态、继承;
  • js不是⾯对对象的语⾔,是⼀⻔基于对象的语⾔,但是可以模拟⾯向对象的思想;
  • 什么是对象:看的⻅,摸的到,具体特指的某个东⻄,⽐如:汽⻋就是不是⼀个对象,是⼀个类别,⽽挂⽢A10011的就是对象。有属性和⽅法,具体特指的某⼀个事物;

18.1 创建对象的三种⽅式

1、调⽤系统的构造函数创建对象

创建对象:
var obj = new Object();
添加属性:对象.名⼦ = 值;
obj.name = "⼩明";
obj.age = 18;
添加⽅法:对象.名⼦ = 函数;
obj.eat = function () {
console.log("我喜欢吃⼤⽶");
};
调⽤属性:对象.名⼦;
console.log(obj.name);
调⽤⽅法:对象.名⼦();
obj.eat();
删除属性: delete 对象.属性名
虽然全局变量var num = 123 是window的属性,但是⼀但经历了var操作,所得出的属性,
这种属性叫做不可配置的属性,不可配置的属性,delete是删不掉的。但是隐式全局变量没有经
历var操作,就可以删掉

2、⼯⼚模式创建对象

// ⼯⼚模式创建,解决了创建多个相似对象的问题,相⽐于⾃定义的构造函数,但是不能解决对
象识别的问题
function createObject(name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.fangFa = function () {
console.log("⼯⼚模式的⽅法");
};
return obj; }
var person = createObject("⼩明", 18);
person.fangFa();

3、⾃定义构造函数创建对象(结合第⼀种和需求通过⼯⼚模式创建对象)

// 2、⾃定义构造函数创建(⼯⼚模式创建对象)
// ⼀次性可以创建多个对象,把创建对象的代码封装在⼀个函数中
// 对象可以分辨出是属于什么类型
// 函数和构造函数的区别:名字的⾸字⺟是不是⼤写;
// ⾃定义构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.eat = function () {
console.log(this.name + "今年" + this.age + "岁" + "想吃⼤⽶");
};
}
var pers1 = new Person("⼩明", 18); // ⾃定义构造函数创建对象 当这⾏代码执⾏的时
候发⽣了4件事
//第⼀件事:在内存中开辟空间,存储创建的新对象;
//第⼆件事:把this设置为当前的对象,并且在这个对象中添加
__proto__:Person.prototype(原型)的属性
//第三件事:设置对象的属性和⽅法的值
//第四件事:把this这个对象返回
pers1.eat();
console.log(pers1 instanceof Person);
⾃定义构造函数相⽐⼯⼚模式:
1、没有显式的创建对象;
2、直接将属性和⽅法赋给了this对象;
3、没有return;
// instanceof
// 如何获取该变量(对象)是不是属于什么类型的?
// 语法:变量 instanceof 类型的名字 ----> 布尔类型

4、字⾯量的⽅式创建对象;

// 3、字⾯量创建对象
// 注意⾥⾯的属性⽅法与值之间⽤冒号,相互之间⽤逗号隔开,最后⼀个不加
// 缺陷:⼀次性的对象,不对传值
var pers2 = {
name:"⼩明",
age:18,
eat:function () {
console.log(this.name + this.age + "爱吃辣条");
}
};
pers2.eat();

18.2 访问对象属性⽅法的另⼀种写法

obj.prop与obj["prop"]

当执⾏obj.prop的时候,内部会隐式的转化成obj["prop"]的⽅式来执⾏,obj[]的这种形式,⽅括号⾥的必须是字符串

// 访问对象属性的另⼀种⽅法["名⼦"] 注意双引号,是以字符串的形式
function Dog (name, age) {
this.name = name;
this.age = age;
this.play = function () {
console.log(this.name + "愉快的玩要");
};
}
var dog = new Dog("阿⻩", 3);
dog["play"](); // 调⽤⽅法
console.log(dog["name"]); // 调⽤属性
console.log(dog["age"]);

属性名是字符串拼接的情况

// 属性的字符串拼接
var den = {
wife1: {name:'xiaoliu'},
wife2: {name:'xiaozhang'},
wife3: {name:'xiaowang'},
sayWife: function (num) {
return this['wife'+num]
}
}
console.log(den.sayWife(1)); // {name: "xiaoliu"}
===================================================
var obj = {
name:'www',
age:12,
sex: 'wrwr'
}
for (var key in obj){
console.log(obj.key) // 打印3次undefined 为什么呢?因为执⾏obj.key相当于是
==>obj["key"],这个字符串的key属性是对象没有的,所以是undefined
console.log(obj[key]) // 这样才是对的
}

18.3 知识点

  • 对象是⼀组⽆序属性的集合,属性的值可以是任意的数据类型

    function Person(name, age, bool) {
    this.name = name; // 字符串
    this.age = age; // 数字
    this.dog = {name:"阿花", age:18}; // 对象
    this.sex = bool; // 布尔值
    this.play = function () { // 函数
    console.log("喜欢玩游戏")
    };
    }
    var xiaoMing = new Person("⼩明", 18, true);
    console.log(xiaoMing.sex?"男":"⼥"); // 男
    console.log(xiaoMing.dog.name); // 阿花
    ============================================
    
  • json格式的数据:⼀般都是成对的,是键值对,json也是⼀个对象,数据都是成对的,⼀般json

    格式的数据⽆论是键还是值都是⽤双引号括起来的。

    JSON.parse():string —> json

    JSON.stringify():json —>string

    var json = {
    "name":"⼩明",
    "age":18,
    "sex":"男" }
    // 遍历对象,不单是json,所有的对象都适⽤
    for (var key in json) {
    // console.log(json.key) // 这种是错误的,key 是个变量,不是值,在属性⾥没有,
    会是undefined,点语法,如果没有属性值,会是undefind
    console.log(key + "====" + json[key]);
    }
    
  • 判断⼀个对象⾥有没有指定属性

    // 判断⼀个对象⾥有没有指定的属性
    var obj = {
    sex:"男",
    age:18
    };
    if (obj["sex"]) {
    console.log("有");
    }else {
    console.log("没有");
    }
    // 有
    ======================
    // 判断⼀个对象⾥没有指定的属性
    var obj = {
    sex:"男",
    age:18
    };
    function check(key, obj) {
    return obj[key]?true:false; }
    console.log(check("sex", obj)); // true
    
  • 在对象中⼀个⽅法调⽤另⼀个⽅法

    // 在对象中⼀个⽅法调⽤另⼀个⽅法
    obj = {
    sayHi:function () {
    console.log("sayhi⽅法");
    this.eat(); // 此时的this就是obj
    },
    eat:function () {
    console.log("eat⽅法");
    }
    };
    

18.4. 对象的枚举

  1. for in

    会延伸到原型链上的属性,但是不会打印Object上的东⻄。

    hasOwnProperty(属性名) ,判断⼀个属性是⾃⼰的还是原型链上的,是⾃⼰的为true

var obj = {
name:'www',
age:12,
sex: 'wrwr',
__proto__: {
lastName:'邓'
}
}
for (var key in obj){
// console.log(obj.key) // 打印3次undefined 为什么呢?因为执⾏obj.key相
当于是==>obj["key"],这个字符串的key属性是对象没有的,所以是undefined
if (obj.hasOwnProperty(key)) { // 判断obj⾃身有没有这循环出来的这些属性,
有了才打印,for in 会延展到原型链上,如果不加判断则会打印出邓
console.log(obj[key]) // 这样才是对的
}
}
  1. in

    判断整个原型链上有没有这个属性,有就是true,

    "lastName" in obj 则为true

18.5 instanceof

A instanceof B A对象是不是B构造函数构造出来的,实际上是看A的原型链上有没有B的原型

person instanceof Person // true
person instanceof Object // true
[] instanceof Array // true
[] instanceof Object // true

判断数据的类型

// 第⼀种判断数组和对象的⽅法
if ([].constructor === Array){
console.log('是⼀个数组');
}
var obj = {};
if (obj.constructor === Object) {
console.log('是⼀个对象');
}
// 第⼆种区分数组和对象的⽅法
console.log([] instanceof Array ? '是数组':'是对象');
console.log(obj instanceof Array ? '是数组':'是对象');
// toString, 因为函数重写了toString⽅法
// Object.prototype.toString = function () {
// // 识别this
// // 返回相应的结果
// }
console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call(123)); // [object Number]
console.log(Object.prototype.toString.call({})); // [object Object]
⼀般⽤的时候⽤toString,因为在⻚⾯中有iframe⼦⻚⾯的时候,⽐如⼦域⾥的数组到⽗域[]
instanceof Array 会是false

18.6. 对象的深浅拷⻉

  1. 浅拷⻉

    总结起来,浅拷⻉就是只能拷⻉原始值,只是拷⻉了引⽤值的指向,所以引⽤值⼀个改变也会影

    响另⼀个的改变。

    obj = {
    name:'abc',
    age:123,
    sex:"female",
    card:['visa', 'unionpay']
    };
    function clone(origin, target) {
    var target = target || {};
    for (var key in origin) {
    target[key] = origin[key];
    }
    return target
    }
    var obj1 = {};
    clone(obj, obj1);
    console.log(obj1);
    obj1.card.push('mm') // 修改引⽤值
    console.log(obj); // 另⼀个对象中的引⽤值也会跟着改变
    obj1.name = 'bb'; // 修改原始值
    console.log(obj) // 另⼀个对象中的原始值不会发⽣改变。
    
    1. 深拷⻉

      var obj = {
      name: "abc",
      age: 123,
      card: ["visa", "master"],
      wife: {
      name: "bcd",
      son: {
      name: "aaa"
      }
      }
      };
      // 1.判断是不是原始值
      // 2.判断引⽤值是数组还是对象
      // 3. 建⽴相应的数组或对象
      // 4.递归
      function deepClone (origin, target) {
      var target = target || {},
      toStr = Object.prototype.toString,
      arrStr = '[object Array]';
      for (var key in origin) {
      if (origin.hasOwnProperty(key)) { // 只拷⻉⾃身的,⽽不是原型链上的
      if (origin[key] !== "null" && typeof(origin[key]) == "object") {
      // 开始判断是数组还是对象
      target[key] = toStr.call(origin[key]) == arrStr ? []:{};
      deepClone(origin[key], target[key])
      }else { // 是原始值就直接拷⻉
      target[key] = origin[key];
      }
      }
      }
      return target; }
      var obj1 = {};
      deepClone(obj, obj1)
      

18.7 对象与类

对象

对象是由属性和⽅法组成的:是⼀个⽆序键值对的集合,指的是⼀个具体的事物

  • 属性:事物的特征,在对象中⽤属性来表示(常⽤名词)
  • ⽅法:事物的⾏为,在对象中⽤⽅法来表示(常⽤动词)

创建对象

//以下代码是对对象的复习
//字⾯量创建对象
var ldh = {
name: '刘德华',
age: 18
}
console.log(ldh);
//构造函数创建对象
function Star(name, age) {
this.name = name;
如上两⾏代码运⾏结果为 : 类
在 ES6 中新增加了类的概念,可以使⽤ class 关键字声明⼀个类,之后以这个类来实例化对象。
类抽象了对象的公共部分,它泛指某⼀⼤类(class)对象特指某⼀个,通过类实例化⼀个具体的
对象
创建类
1. 语法:
2. 示例
以上代码运⾏结果:
通过结果我们可以看出,运⾏结果和使⽤构造函数⽅式⼀样
类创建添加属性和⽅法
this.age = age;
}
var ldh = new Star('刘德华', 18)//实例化对象
console.log(ldh);

image-20210802115925803

  • 在 ES6 中新增加了类的概念,可以使⽤ class 关键字声明⼀个类,之后以这个类来实例化对象。类抽象了对象的公共部分,它泛指某⼀⼤类(class)对象特指某⼀个,通过类实例化⼀个具体的对象

创建类

  1. 语法:

    //步骤1 使⽤class关键字
    class name {
    // class body
    }
    //步骤2使⽤定义的类创建实例 注意new关键字
    var xx = new name();
    
  2. 示例

    // 1. 创建类 class 创建⼀个 明星类
    class Star {
    // 类的共有属性放到 constructor ⾥⾯
    constructor(name, age) {
    this.name = name;
    this.age = age;
    }
    }
    // 2. 利⽤类创建对象 new
    var ldh = new Star('刘德华', 18);
    console.log(ldh);
    

    以上代码运⾏结果:

    image-20210802120030369

通过结果我们可以看出,运⾏结果和使⽤构造函数⽅式⼀样

类创建添加属性和⽅法

// 1. 创建类 class 创建⼀个类
class Star {
// 类的共有属性放到 constructor ⾥⾯ constructor是 构造器或者构造函数
constructor(uname, age) {
this.uname = uname;
this.age = age;
}//------------------------------------------->注意,⽅法与⽅法之间不需要添加
逗号
sing(song) {
console.log(this.uname + '唱' + song);
}
}
// 2. 利⽤类创建对象 new
var ldh = new Star('刘德华', 18);
console.log(ldh); // Star {uname: "刘德华", age: 18}
ldh.sing('冰⾬'); // 刘德华唱冰⾬

以上代码运⾏结果:

image-20210802120059192

注意哟:

  1. 通过class 关键字创建类, 类名我们还是习惯性定义⾸字⺟⼤写

  2. 类⾥⾯有个constructor 函数,可以接受传递过来的参数,同时返回实例对象

  3. constructor 函数 只要 new ⽣成实例时,就会⾃动调⽤这个函数, 如果我们不写这个函数,类也会⾃动⽣成这个函数

  4. 多个函数⽅法之间不需要添加逗号分隔

  5. ⽣成实例 new 不能省略

  6. 语法规范, 创建类 类名后⾯不要加⼩括号,⽣成实例 类名后⾯加⼩括号, 构造函数不需要加function

类的继承

  1. 语法

    // ⽗类
    class Father{
    }
    // ⼦类继承⽗类
    class Son extends Father {
    }
    
  2. 示例

    class Father {
    constructor(surname) {
    this.surname= surname;
    }
    say() {
    console.log('你的姓是' + this.surname);
    }
    }
    class Son extends Father{ // 这样⼦类就继承了⽗类的属性和⽅法
    }
    var damao= new Son('刘');
    damao.say(); //结果为 你的姓是刘
    

    以上代码运⾏结果:

    image-20210802133004961

  • ⼦类使⽤super关键字访问⽗类的⽅法

    //定义了⽗类
    class Father {
    constructor(x, y) {
    this.x = x;
    this.y = y;
    }
    sum() {
    console.log(this.x + this.y);
    }
    }
    //⼦元素继承⽗类
    class Son extends Father {
    constructor(x, y) {
    super(x, y); //使⽤super调⽤了⽗类中的构造函数
    }
    }
    var son = new Son(1, 2);
    son.sum(); //结果为3
    

    需要注意的地⽅

    1. 继承中,如果实例化⼦类输出⼀个⽅法,先看⼦类有没有这个⽅法,如果有就先执⾏⼦类的

    2. 继承中,如果⼦类⾥⾯没有,就去查找⽗类有没有这个⽅法,如果有,就执⾏⽗类的这个⽅法(就近原则)

    3. 如果⼦类想要继承⽗类的⽅法,同时在⾃⼰内部扩展⾃⼰的⽅法,利⽤super 调⽤⽗类的构造函数,super 必须在⼦类this之前调⽤

    // ⽗类有加法⽅法
    class Father {
    constructor(x, y) {
    this.x = x;
    this.y = y;
    }
    sum() {
    console.log(this.x + this.y);
    }
    }
    // ⼦类继承⽗类加法⽅法 同时 扩展减法⽅法
    class Son extends Father {
    constructor(x, y) {
    // 利⽤super 调⽤⽗类的构造函数 super 必须在⼦类this之前调⽤,放到this之后
    会报错
    super(x, y);
    this.x = x;
    this.y = y;
    }
    subtract() {
    console.log(this.x - this.y);
    }
    }
    var son = new Son(5, 3);
    son.subtract(); //2
    son.sum();//8
    

    以上代码运⾏结果为:

    image-20210802133109676

  1. 时刻注意this的指向问题,类⾥⾯的共有的属性和⽅法⼀定要加this使⽤.

    1. constructor中的this指向的是new出来的实例对象

    2. ⾃定义的⽅法,⼀般也指向的new出来的实例对象

    3. 绑定事件之后this指向的就是触发事件的事件源

  2. 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象

image-20210802133225541

image-20210802133236660

⾯向对象版tab 栏切换

功能需求

  1. 点击 tab栏,可以切换效果.

  2. 点击 + 号, 可以添加 tab 项和内容项.

  3. 点击 x 号, 可以删除当前的tab项和内容项.

  4. 双击tab项⽂字或者内容项⽂字可以修改⾥⾯的⽂字内容

案例准备

  1. 获取到标题元素

  2. 获取到内容元素

  3. 获取到删除的⼩按钮 x号

  4. 新建js⽂件,定义类,添加需要的属性⽅法(切换,删除,增加,修改)

  5. 时刻注意this的指向问题

切换

  • 为获取到的标题绑定点击事件,展示对应的内容区域,存储对应的索引

    this.lis[i].index = i;
    this.lis[i].onclick = this.toggleTab;
    
  • 使⽤排他,实现只有⼀个元素的显示

    toggleTab() {
    //将所有的标题与内容类样式全部移除
    for (var i = 0; i < this.lis.length; i++) {
    this.lis[i].className = '';
    this.sections[i].className = '';
    }
    //为当前的标题添加激活样式
    this.className = 'liactive';
    //为当前的内容添加激活样式
    that.sections[this.index].className = 'conactive';
    }
    

    添加

    • 为添加按钮+ 绑定点击事件

      this.add.onclick = this.addTab;
      
    • 实现标题与内容的添加,做好排他处理

      addTab() {
      that.clearClass();
      // (1) 创建li元素和section元素
      var random = Math.random();
      var li = '<li class="liactive"><span>新选项卡</span><span
        class="iconfont icon-guanbi"> </span></li>';
      var section = '<section class="conactive">测试 ' + random +
      '</section>';
      // (2) 把这两个元素追加到对应的⽗元素⾥⾯
      that.ul.insertAdjacentHTML('beforeend', li);
      that.fsection.insertAdjacentHTML('beforeend', section);
      that.init();
      }
      

    删除

    • 为元素的删除按钮x绑定点击事件

      this.remove[i].onclick = this.removeTab;
      
    • 获取到点击的删除按钮的所在的⽗元素的所有,删除对应的标题与内容

      removeTab(e) {
      e.stopPropagation(); // 阻⽌冒泡 防⽌触发li 的切换点击事件
      var index = this.parentNode.index;
      console.log(index);
      // 根据索引号删除对应的li 和section remove()⽅法可以直接删除指定的元
      素
      that.lis[index].remove();
      that.sections[index].remove();
      that.init();
      // 当我们删除的不是选中状态的li 的时候,原来的选中状态li保持不变
      if (document.querySelector('.liactive')) return;
      // 当我们删除了选中状态的这个li 的时候, 让它的前⼀个li 处于选定状态
      index--;
      // ⼿动调⽤我们的点击事件 不需要⿏标触发
      that.lis[index] && that.lis[index].click();
      }
      

    编辑

    • 为元素(标题与内容)绑定双击事件

      this.spans[i].ondblclick = this.editTab;
      this.sections[i].ondblclick = this.editTab;
      
    • 在双击事件处理⽂本选中状态,修改内部DOM节点,实现新旧value值的传递

      editTab() {
      var str = this.innerHTML;
      // 双击禁⽌选定⽂字
      window.getSelection ? window.getSelection().removeAllRanges() :
      document.selection.empty();
      // alert(11);
      this.innerHTML = '<input type="text" />';
      var input = this.children[0];
      input.value = str;
      input.select(); // ⽂本框⾥⾯的⽂字处于选定状态
      // 当我们离开⽂本框就把⽂本框⾥⾯的值给span
      input.onblur = function() {
      this.parentNode.innerHTML = this.value;
      };
      // 按下回⻋也可以把⽂本框⾥⾯的值给span
      input.onkeyup = function(e) {
      if (e.keyCode === 13) {
      // ⼿动调⽤表单失去焦点事件 不需要⿏标离开操作
      this.blur();
      }
      }
      }
      

19 - 内置对象

对象分为三种:内置对象、⾃定义对象、浏览器对象

内置对象:系统提供的

⾃定义对象:⾃⼰写的对象

浏览器对象:浏览器的

实例对象,通过构造函数创建出来的,实例化的对象;

静态对象,不需要创建,直接就是⼀个对象,⽅法(静态⽅法),直接通过这个对象名⼦调⽤。

实例⽅法必须通过实例化后的对象调⽤的;对象.⽅法();

静态⽅法必须通过⼤写的构造函数这样的调⽤的⽅式调⽤的;构造函数.⽅法()

19.1 内置对象

JavaScript 中的对象分为3种:⾃定义对象 、内置对象、 浏览器对象 前⾯两种对象是JS 基础 内容,属

于 ECMAScript; 第三个浏览器对象属于 JS 独有的, JS API 讲解内置对象就是指 JS 语⾔⾃带的⼀些

对象,这些对象供开发者使⽤,并提供了⼀些常⽤的或是最基本⽽必要的功能(属性和⽅法),内置对

象最⼤的优点就是帮助我们快速开发

JavaScript 提供了多个内置对象:Math、 Date 、Array、String等

19.2 查⽂档

查找⽂档:学习⼀个内置对象的使⽤,只要学会其常⽤成员的使⽤即可,我们可以通过查⽂档学习,可

以通过MDN/W3C来查询。 Mozilla 开发者⽹络(MDN)提供了有关开放⽹络技术(Open Web)的

信息,包括 HTML、CSS 和万维⽹及 HTML5 应⽤的 API。 MDN:https://developer.mozilla.org/zh-C

N/

19.3 Math对象

Math 对象不是构造函数,它具有数学常数和函数的属性和⽅法。跟数学相关的运算(求绝对值,取

整、最⼤值等)可以使⽤ Math 中的成员。

属性、⽅法名 功能
Math.PI 圆周率
Math.floor() 向下取整
Math.ceil() 向上取整
Math.round() 四舍五⼊版 就近取整 注意 -3.5 结果是 -3
Math.abs() 绝对值
Math.max()/Math.min() 求最⼤和最⼩值
Math.random() 获取范围在[0,1)内的随机值

注意:上⾯的⽅法使⽤时必须带括号

获取指定范围内的随机整数:

function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min; }

19.4 ⽇期对象

Date 对象和 Math 对象不⼀样,Date是⼀个构造函数,所以使⽤时需要实例化后才能使⽤其中具体⽅

法和属性。Date 实例⽤来处理⽇期和时间

  • 使⽤Date实例化⽇期对象

    • 获取当前时间必须实例化:

      var now = new Date();
      
    • 获取指定时间的⽇期对象

      var future = new Date('2019/5/1');
      

      注意:如果创建实例时并未传⼊参数,则得到的⽇期对象是当前时间对应的⽇期对象

  • 使⽤Date实例的⽅法和属性

  • image-20210802134259564

  • 通过Date实例获取总毫⽶数

    • 总毫秒数的含义

      基于1970年1⽉1⽇(世界标准时间)起的毫秒数

    • 获取总毫秒数

      // 实例化Date对象
      var now = new Date();
      // 1. ⽤于获取对象的原始值
      console.log(date.valueOf())
      console.log(date.getTime())
      // 2. 简单写可以这么做
      var now = + new Date();
      // 3. HTML5中提供的⽅法,有兼容性问题
      var now = Date.now();
      
  • ⼀些练习

    // 创建⼀个Date的实例化对象(不传参)
    var date = new Date();
    console.log(date);
    // 获取时间戳
    console.log(date.getTime())
    // 传⼊参数
    var date1 = new Date("2000-5-4")
    console.log(date1);
    // 获取年份
    console.log(date.getFullYear());
    // 获取⽉份
    console.log(date.getMonth() + 1);//是从0开始的,真实的需要加1
    // 获取⽇期
    console.log(date.getDate());
    // 获取时
    console.log(date.getHours());
    // 获取分
    console.log(date.getMinutes());
    // 获取秒
    console.log(date.getSeconds());
    // 获取星期
    console.log(date.getDay()); // 周⽇是0,依此类推
    // 英⽂的⽇期的字符串
    console.log(date.toDateString());
    // 数字的⽇期的字符串
    console.log(date.toLocaleDateString());
    // 英⽂的时间的字符串
    console.log(date.toTimeString());
    // 中⽂的时间的字符串
    console.log(date.toLocaleTimeString());
    ==============================================================
    // 格式化⽇期输出“2018年11⽉15⽇ 11:23:23”
    /** 检查⽉、⽇、时、分、秒是不是⼤于0
    * @param num ⽇期的数字
    * @returns {string} 返回的是清洗后的数字
    */
    function checkDate(num) {
    return num>10?num:"0"+num; }
    /** 返回指定格式的⽇期字符串
    * @param date ⽇期对象
    * @returns {string} 返回的是字符串
    */
    function formatDate(date) {
    // var date = new Date(); // 可以直接向函数传⼀个对象的参数
    //年
    var y = date.getFullYear();
    //⽉
    var m = checkDate(date.getMonth() + 1); // ⽉份是从0开始的,所以要加1
    //⽇
    var d = checkDate(date.getDate());
    //时
    var h = checkDate(date.getHours());
    //分
    var M = checkDate(date.getMinutes());
    //秒
    var s = checkDate(date.getSeconds());
    var str = y+"年"+ m +"⽉"+d+"⽇" + h +":"+M+":"+ s;
    return str; }
    console.log(formatDate(new Date())); // 可以直接向函数传⼀个对象的参数
    ====================================================================
    // 毫秒值转成⽇期对象
    var date = new Date();
    console.log(date.valueOf()) // 得到毫秒数 1546436676284
    var dt = new Date(date.valueOf());
    console.log(dt);
    =======================================
    // 倒计时效果
    // 1.核⼼算法:输⼊的时间减去现在的时间就是剩余的时间,即倒计时 ,但是不能拿
    着时分秒相减,⽐如 05 分减去25分,结果会是负数的。
    // 2.⽤时间戳来做。⽤户输⼊时间总的毫秒数减去现在时间的总的毫秒数,得到的就
    是剩余时间的毫秒数。
    // 3.把剩余时间总的毫秒数转换为天、时、分、秒 (时间戳转换为时分秒)
    // 转换公式如下:
    // d = parseInt(总秒数/ 60/60 /24); // 计算天数
    // h = parseInt(总秒数/ 60/60 %24) // 计算⼩时
    // m = parseInt(总秒数 /60 %60 ); // 计算分数
    // s = parseInt(总秒数%60); // 计算当前秒数
    function countDown(time) {
    var nowTime = +new Date(); // 返回的是当前时间总的毫秒数
    var inputTime = +new Date(time); // 返回的是⽤户输⼊时间总的毫秒数
    var times = (inputTime - nowTime) / 1000; // times是剩余时间总的秒数
    var d = parseInt(times / 60 / 60 / 24); // 天 d = d < 10 ? '0' + d : d;
    var h = parseInt(times / 60 / 60 % 24); //时 h = h < 10 ? '0' + h : h;
    var m = parseInt(times / 60 % 60); // 分 m = m < 10 ? '0' + m : m;
    var s = parseInt(times % 60); // 当前的秒
    s = s < 10 ? '0' + s : s;
    return d + '天' + h + '时' + m + '分' + s + '秒'; }
    console.log(countDown('2019-5-1 18:00:00'));
    var date = new Date();
    console.log(date);
    

19.5 数组对象

创建数组的两种⽅式

  • 字⾯量⽅式

    • 示例代码如下:

      var arr = [1,"test",true];
      
  • new Array()

    • 示例代码如下:

      var arr = new Array();
      

      注意:上⾯代码中arr创建出的是⼀个空数组,如果需要使⽤构造函数Array创建⾮空数组,可以在创建数组时传⼊参数

      参数传递规则如下:

      • 如果只传⼊⼀个参数,则参数规定了数组的⻓度
      • 如果传⼊了多个参数,则参数称为数组的元素

检测是否为数组

  • instanceof 运算符

    • instanceof 可以判断⼀个对象是否是某个构造函数的实例

      var arr = [1, 23];
      var obj = {};
      console.log(arr instanceof Array); // true
      console.log(obj instanceof Array); // false
      
  • Array.isArray()

    • Array.isArray()⽤于判断⼀个对象是否为数组,isArray() 是 HTML5 中提供的⽅法

      var arr = [1, 23];
      var obj = {};
      console.log(Array.isArray(arr)); // true
      console.log(Array.isArray(obj)); // false
      

添加删除数组元素的⽅法

  • 数组中有进⾏增加、删除元素的⽅法,部分⽅法如下表

    image-20210802134658626

    注意:push、unshift为增加元素⽅法;pop、shift为删除元素的⽅法

数组排序

  • 数组中有对数组本身排序的⽅法,部分⽅法如下表

    image-20210802134731344

    注意:sort⽅法需要传⼊参数来设置升序、降序排序

    • 如果传⼊“function(a,b){ return a-b;}”,则为升序

    • 如果传⼊“function(a,b){ return b-a;}”,则为降序

数组索引⽅法

  • 数组中有获取数组指定元素索引值的⽅法,部分⽅法如下表

    image-20210802134805376

数组转换为字符串

  • 数组中有把数组转化为字符串的⽅法,部分⽅法如下表

    image-20210802134827513

    注意:join⽅法如果不传⼊参数,则按照 “ , ”拼接元素

其他⽅法

  • 数组中还有其他操作⽅法,同学们可以在课下⾃⾏查阅学习

    image-20210802135001457

19.6 字符串对象

基本包装类型

为了⽅便操作基本数据类型,JavaScript 还提供了三个特殊的引⽤类型:String、Number和Boolean。

基本包装类型就是把简单数据类型包装成为复杂数据类型,这样基本数据类型就有了属性和⽅法。

// 下⾯代码有什么问题?
var str = 'andy';
console.log(str.length);

按道理基本数据类型是没有属性和⽅法的,⽽对象才有属性和⽅法,但上⾯代码却可以执⾏,这是因为

js 会把基本数据类型包装为复杂数据类型,其执⾏过程如下 :

// 1. ⽣成临时变量,把简单类型包装为复杂数据类型
var temp = new String('andy');
// 2. 赋值给我们声明的字符变量
str = temp;
// 3. 销毁临时变量
temp = null;

字符串的不可变

指的是⾥⾯的值不可变,虽然看上去可以改变内容,但其实是地址变了,内存中新开辟了⼀个内存空间。

当重新给字符串变量赋值的时候,变量之前保存的字符串不会被修改,依然在内存中重新给字符串赋值,会重新在内存中开辟空间,这个特点就是字符串的不可变。 由于字符串的不可变,在⼤量拼接字符串的时候会有效率问题

根据字符返回位置

字符串通过基本包装类型可以调⽤部分⽅法来操作字符串,以下是返回指定字符的位置的⽅法:

image-20210802135120674

案例:查找字符串"abcoefoxyozzopp"中所有o出现的位置以及次数

  1. 先查找第⼀个o出现的位置

  2. 然后 只要indexOf 返回的结果不是 -1 就继续往后查找

  3. 因为indexOf 只能查找到第⼀个,所以后⾯的查找,利⽤第⼆个参数,当前索引加1,从⽽继续查找

根据位置返回字符

字符串通过基本包装类型可以调⽤部分⽅法来操作字符串,以下是根据位置返回指定位置上的字符:

image-20210802135157216

在上述⽅法中,charCodeAt⽅法返回的是指定位置上字符对应的ASCII码,ASCII码对照表如下:

image-20210802135211880

案例:判断⼀个字符串 'abcoefoxyozzopp' 中出现次数最多的字符,并统计其次数

  1. 核⼼算法:利⽤ charAt() 遍历这个字符串

  2. 把每个字符都存储给对象, 如果对象没有该属性,就为1,如果存在了就 +1

  3. 遍历对象,得到最⼤值和该字符

    注意:在遍历的过程中,把字符串中的每个字符作为对象的属性存储在对象总,对应的属性值是该字符出现的次数

字符串操作⽅法

字符串通过基本包装类型可以调⽤部分⽅法来操作字符串,以下是部分操作⽅法:

image-20210802135257618

replace()⽅法

replace() ⽅法⽤于在字符串中⽤⼀些字符替换另⼀些字符,其使⽤格式如下:

字符串.replace(被替换的字符串, 要替换为的字符串);

split()⽅法

split()⽅法⽤于切分字符串,它可以将字符串切分为数组。在切分完毕之后,返回的是⼀个新数组。

其使⽤格式如下:

字符串.split("分割字符")

20 - 简单数据类型和复杂数据类型

20.1 简单数据类型

简单类型(基本数据类型、值类型):在存储时变量中存储的是值本身,包括string ,number,boolean,undefined,null

20.2 复杂数据类型

复杂数据类型(引⽤类型):在存储时变量中存储的仅仅是地址(引⽤),通过 new 关键字创建的对

象(系统对象、⾃定义对象),如 Object、Array、Date等;

20.3 堆栈

  • 堆栈空间分配区别:

  1、栈(操作系统):由操作系统⾃动分配释放存放函数的参数值、局部变量的值等。其操作⽅式类似于数据结构中的栈;

​ 简单数据类型存放到栈⾥⾯

  2、堆(操作系统):存储复杂类型(对象),⼀般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。

image-20210802135858652

  • 简单数据类型的存储⽅式
    • 值类型变量的数据直接存放在变量(栈空间)中

image-20210802135922507

  • 复杂数据类型的存储⽅式

    引⽤类型变量(栈空间)⾥存放的是地址,真正的对象实例存放在堆空间中

    image-20210802135947595

2.4 简单类型传参

函数的形参也可以看做是⼀个变量,当我们把⼀个值类型变量作为参数传给函数的形参时,其实是把变

量在栈空间⾥的值复制了⼀份给形参,那么在⽅法内部对形参做任何修改,都不会影响到的外部变量。

function fn(a) {
a++;
console.log(a);
}
var x = 10;
fn(x);
console.log(x);

运⾏结果如下:

image-20210802140027742

2.5 复杂数据类型传参

函数的形参也可以看做是⼀个变量,当我们把引⽤类型变量传给形参时,其实是把变量在栈空间⾥保存

的堆地址复制给了形参,形参和实参其实保存的是同⼀个堆地址,所以操作的是同⼀个对象。

function Person(name) {
this.name = name; }
function f1(x) { // x = p
console.log(x.name); // 2. 这个输出什么 ?
x.name = "张学友";
console.log(x.name); // 3. 这个输出什么 ?
}
var p = new Person("刘德华");
console.log(p.name); // 1. 这个输出什么 ?
f1(p);
console.log(p.name); // 4. 这个输出什么 ?

运⾏结果如下:

image-20210802140058042

21 - 构造函数和原型

21.1静态成员和实例成员

21.1.1实例成员

实例成员就是构造函数内部通过this添加的成员 如下列代码中uname age sing 就是实例成员,实例成员只能通过实例化的对象来访问

function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
var ldh = new Star('刘德华', 18);
console.log(ldh.uname);//实例成员只能通过实例化的对象来访问

21.1.2静态成员

静态成员 在构造函数本身上添加的成员 如下列代码中 sex 就是静态成员,静态成员只能通过构造函数来访问

function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
Star.sex = '男';
var ldh = new Star('刘德华', 18);
console.log(Star.sex);//静态成员只能通过构造函数来访问

21.2构造函数的问题

构造函数⽅法很好⽤,但是存在浪费内存的问题。

![构造函数通过原型分配的函数是所有对象所共享的。

JavaScript 规定,每⼀个构造函数都有⼀个prototype 属性,指向另⼀个对象。注意这个prototype就

是⼀个对象,这个对象的所有属性和⽅法,都会被构造函数所拥有。

我们可以把那些不变的⽅法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些⽅

法。

function Star(uname, age) {
this.uname = uname;
this.age = age; }
Star.prototype.sing = function() {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
ldh.sing();//我会唱歌
zxy.sing();//我会唱歌

image-20210802140221330

21.3. 对象原型

对象都会有⼀个属性 __proto__ 指向构造函数的 prototype 原型对象,之所以我们对象可以
使⽤构造函数 prototype 原型对象的属性和⽅法,就是因为对象有 __proto__ 原型的存在。
__proto__对象原型和原型对象 prototype 是等价的
__proto__对象原型的意义就在于为对象的查找机制提供⼀个⽅向,或者说⼀条路线,但是它是
⼀个⾮标准属性,因此实际开发中,不可以使⽤这个属性,它只是内部指向原型对象 prototype

image-20210802140242109

image-20210802140256524

21.4. constructor构造函数

对象原型( __proto__)和构造函数(prototype)原型对象⾥⾯都有⼀个属性 constructor
属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
constructor 主要⽤于记录该对象引⽤于哪个构造函数,它可以让原型对象重新指向原来的构造
函数。
⼀般情况下,对象的⽅法都在构造函数的原型对象中设置。如果有多个对象的⽅法,我们可以给
原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原
型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,
添加⼀个 constructor 指向原来的构造函数。 

如果我们修改了原来的原型对象,给原型对象赋值的是⼀个对象,则必须⼿动的利⽤constructor指回原来

的构造函数如:

function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 很多情况下,我们需要⼿动的利⽤constructor 这个属性指回 原来的构造函数
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是⼀个对象,则必须⼿动的利⽤
constructor指回原来的构造函数
constructor: Star, // ⼿动设置指回原来的构造函数
sing: function() {
console.log('我会唱歌');
},
movie: function() {
console.log('我会演电影');
}
}
var zxy = new Star('张学友', 19);
console.log(zxy)

以上代码运⾏结果,设置constructor属性如图:

image-20210802140424595

如果未设置constructor属性,如图:

image-20210802140437242

21.5. 原型链

每⼀个实例对象⼜有⼀个proto 属性,指向的构造函数的原型对象,构造函数的原型对象也是⼀个对象,也有proto属性,这样⼀层⼀层往上找就形成了原型链。

image-20210802140508879

21.6构造函数实例和原型对象三⻆关系

1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属
性也指向了构造函数

image-20210802140534644

21.8. 原型链和成员的查找机制

任何对象都有原型对象,也就是prototype属性,任何原型对象也是⼀个对象,该对象就有proto 属性,这样⼀层⼀层往上找,就形成了⼀条链,我们称此为原型链;

当访问⼀个对象的属性(包括⽅法)时,⾸先查找这个对象⾃身有没有该属性。
如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
如果还没有就查找原型对象的原型(Object的原型对象)。
依此类推⼀直找到 Object 为⽌(null)。
__proto__对象原型的意义就在于为对象成员查找机制提供⼀个⽅向,或者说⼀条路线。

21.9. 原型对象中this指向

构造函数中的this和原型对象的this,都指向我们new出来的实例对象

function Star(uname, age) {
this.uname = uname;
this.age = age; }
var that;
Star.prototype.sing = function() {
console.log('我会唱歌');
that = this; }
var ldh = new Star('刘德华', 18);
// 1. 在构造函数中,⾥⾯this指向的是对象实例 ldh
console.log(that === ldh);//true
// 2.原型对象函数⾥⾯的this 指向的是 实例对象 ldh

image-20210802140621573

21.10通过原型为数组扩展内置⽅法

Array.prototype.sum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
//此时数组对象中已经存在sum()⽅法了 可以始终 数组.sum()进⾏数据的求

22 - 原型的继承

22.1call()

  • call()可以调⽤函数
  • call()可以修改this的指向,使⽤call()的时候 参数⼀是修改后的this指向,参数2,参数3..使⽤逗号隔开连接
function fn(x, y) {
console.log(this);
console.log(x + y);
}
var o = {
name: 'andy'
};
fn.call(o, 1, 2);//调⽤了函数此时的this指向了对象o,

image-20210802140719625

22.2. ⼦构造函数继承⽗构造函数中的属性

  1. 先定义⼀个⽗构造函数

  2. 再定义⼀个⼦构造函数

  3. ⼦构造函数继承⽗构造函数的属性(使⽤call⽅法)

    // 1. ⽗构造函数
    function Father(uname, age) {
    // this 指向⽗构造函数的对象实例
    this.uname = uname;
    this.age = age;
    }
    // 2 .⼦构造函数
    function Son(uname, age, score) {
    // this 指向⼦构造函数的对象实例
    3.使⽤call⽅式实现⼦继承⽗的属性
    Father.call(this, uname, age);
    this.score = score; }
    var son = new Son('刘德华', 18, 100);
    console.log(son);
    

    image-20210802140756713

22.3借⽤原型对象继承⽅法

  1. 先定义⼀个⽗构造函数

  2. 再定义⼀个⼦构造函数

  3. ⼦构造函数继承⽗构造函数的属性(使⽤call⽅法)

// 1. ⽗构造函数
function Father(uname, age) {
// this 指向⽗构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function() {
console.log(100000);
};
// 2 .⼦构造函数
function Son(uname, age, score) {
// this 指向⼦构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了⼦原型对象,
⽗原型对象也会跟着⼀起变化
Son.prototype = new Father();
// 如果利⽤对象的形式修改了原型对象,别忘了利⽤constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 这个是⼦构造函数专⻔的⽅法
Son.prototype.exam = function() {
console.log('孩⼦要考试');
}
var son = new Son('刘德华', 18, 100);
console.log(son);

如上代码结果如图:

image-20210802140841757

22.4. 圣杯继承

// 圣杯模式 普通版
Father.prototype.lastname = "eee";
function Father (name) {
this.name = name; }
var father = new Father("mjc");
function Son (age) {
this.age = age;
}
function Inherit (Target,Origin) {
function F() {};
F.prototype = Target.prototype;
Origin.prototype = new F(); // 注意这⾏不能在F的原型改变之前,⼀定要在后⾯。
原因是new的时候的原型⼀定是要改变后的原型
Origin.prototype.constructor = Origin; // 改变原型的constructor指向⾃身的构
造函数,不改变的话,构造函数会指向⽗类
Origin.prototype.uber = Origin.prototype; // 找到超类,究竟继承⾃谁,为了信息
的⼀个存储
}
Inherit(Father, Son);
var son = new Son(18);
// 圣杯模式 雅⻁版
Father.prototype.lastname = "eee";
function Father (name) {
this.name = name; }
var father = new Father("mjc");
function Son (age) {
this.age = age; }
var inherit = (function () {
var F = function () {}; // 闭包的属性私有化,不想让外界访问这个属性
return function (Target,Origin) {
F.prototype = Target.prototype;
Origin.prototype = new F();
Origin.prototype.constructor = Origin;
Origin.prototype.uber = Origin.prototype;
}
}());
inherit(Father, Son);
var son = new Son(18);

23 - ES5新增⽅法

23.1数组⽅法forEach遍历数组

arr.forEach(function(value, index, array) {
//参数⼀是:数组元素
//参数⼆是:数组元素的索引
//参数三是:当前的数组
})
//相当于数组遍历的 for循环 没有返回值

23.2数组⽅法filter过滤数组

var arr = [12, 66, 4, 88, 3, 7];
var newArr = arr.filter(function(value, index,array) {
//参数⼀是:数组元素
//参数⼆是:数组元素的索引
//参数三是:当前的数组
return value >= 20;
});
console.log(newArr);//[66,88] //返回值是⼀个新数组

23.3数组⽅法some

some 查找数组中是否有满⾜条件的元素
var arr = [10, 30, 4];
var flag = arr.some(function(value,index,array) {
//参数⼀是:数组元素
//参数⼆是:数组元素的索引
//参数三是:当前的数组
return value < 3;
});
console.log(flag);//false返回值是布尔值,只要查找到满⾜条件的⼀个元素就⽴⻢终⽌循环

23.4筛选商品案例

  1. 定义数组对象数据

    var data = [{
    id: 1,
    pname: '⼩⽶',
    price: 3999
    }, {
    id: 2,
    pname: 'oppo',
    price: 999
    }, {
    id: 3,
    pname: '荣耀',
    price: 1299
    }, {
    id: 4,
    pname: '华为',
    price: 1999
    }, ];
    
  2. 使⽤forEach遍历数据并渲染到⻚⾯中

    data.forEach(function(value) {
    var tr = document.createElement('tr');
    tr.innerHTML = '<td>' + value.id + '</td><td>' + value.pname + '</td>
    <td>' + value.price + '</td>';
    tbody.appendChild(tr);
    });
    
  3. 根据价格筛选数据

    1. 获取到搜索按钮并为其绑定点击事件

      search_price.addEventListener('click', function() {
      });
      
    2. 使⽤filter将⽤户输⼊的价格信息筛选出来

      search_price.addEventListener('click', function() {
      var newDate = data.filter(function(value) {
      //start.value是开始区间
      //end.value是结束的区间
      return value.price >= start.value && value.price <=
      end.value;
      });
      console.log(newDate);
      });
      
    3. 将筛选出来的数据重新渲染到表格中

      1. 将渲染数据的逻辑封装到⼀个函数中

        function setDate(mydata) {
        // 先清空原来tbody ⾥⾯的数据
        tbody.innerHTML = '';
        mydata.forEach(function(value) {
        var tr = document.createElement('tr');
        tr.innerHTML = '<td>' + value.id + '</td><td>' +
        value.pname + '</td><td>' + value.price + '</td>';
        tbody.appendChild(tr);
        });
        }
        
      2. 将筛选之后的数据重新渲染

        search_price.addEventListener('click', function() {
        var newDate = data.filter(function(value) {
        return value.price >= start.value && value.price <=
        end.value;
        });
        console.log(newDate);
        // 把筛选完之后的对象渲染到⻚⾯中
        setDate(newDate);
        });
        
    4. 根据商品名称筛选

      1. 获取⽤户输⼊的商品名称

      2. 为查询按钮绑定点击事件,将输⼊的商品名称与这个数据进⾏筛选

        search_pro.addEventListener('click', function() {
        var arr = [];
        data.some(function(value) {
        if (value.pname === product.value) {
        // console.log(value);
        arr.push(value);
        return true; // return 后⾯必须写true
        }
        });
        // 把拿到的数据渲染到⻚⾯中
        setDate(arr);
        })
        

23.5some和forEach区别

  • 如果查询数组中唯⼀的元素, ⽤some⽅法更合适,在some ⾥⾯ 遇到 return true 就是终⽌遍历 迭代效率更⾼
  • 在 forEach 和 filter ⾥⾯ return 不会终⽌迭代

23.6trim⽅法去除字符串两端的空格

var str = ' hello '
console.log(str.trim()) //hello 去除两端空格
var str1 = ' he l l o '
console.log(str.trim()) //he l l o 去除两端空格

23.7获取对象的属性名

Object.keys(对象) 获取到当前对象中的属性名 ,返回值是⼀个数组

var obj = {
id: 1,
pname: '⼩⽶',
price: 1999,
num: 2000
};
var result = Object.keys(obj)
console.log(result)//[id,pname,price,num]

23.8Object.defineProperty

Object.defineProperty设置或修改对象中的属性

Object.defineProperty(对象,修改或新增的属性名,{
value:修改或新增的属性的值,
writable:true/false,//如果值为false 不允许修改这个属性值
enumerable: false,//enumerable 如果值为false 则不允许遍历
configurable: false //configurable 如果为false 则不允许删除这个属性 属性是
否可以被删除或是否可以再次修改特性
})

24 - 严格模式

24.1什么是严格模式

JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采⽤具有限

制性 JavaScript变体的⼀种⽅式,即在严格的条件下运⾏ JS 代码。

严格模式在 IE10 以上版本的浏览器中才会被⽀持,旧版本浏览器中会被忽略。

严格模式对正常的 JavaScript 语义做了⼀些更改:

1.消除了 Javascript 语法的⼀些不合理、不严谨之处,减少了⼀些怪异⾏为。

2.消除代码运⾏的⼀些不安全之处,保证代码运⾏的安全。

3.提⾼编译器效率,增加运⾏速度。

4.禁⽤了在 ECMAScript 的未来版本中可能会定义的⼀些语法,为未来新版本的 Javascript 做好铺

垫。⽐如⼀些保留字如:class,enum,export, extends, import, super 不能做变量名

24.2开启严格模式

严格模式可以应⽤到整个脚本或个别函数中。因此在使⽤时,我们可以将严格模式分为为脚本开启严格

模式和为函数开启严格模式两种情况。

  • 情况⼀ :为脚本开启严格模式

    • 有的 script 脚本是严格模式,有的 script 脚本是正常模式,这样不利于⽂件合并,所以可以将整个脚本⽂件放在⼀个⽴即执⾏的匿名函数之中。这样独⽴创建⼀个作⽤域⽽不影响其他 script 脚本⽂件。

      (function (){
      //在当前的这个⾃调⽤函数中有开启严格模式,当前函数之外还是普通模式
          "use strict";
      var num = 10;
          function fn() {}
      })();
      //或者
      <script>
      "use strict"; //当前script标签开启了严格模式
      </script>
      <script>
      //当前script标签未开启严格模式
      </script>
      
  • 情况⼆: 为函数开启严格模式

    • 要给某个函数开启严格模式,需要把“use strict”; (或 'use strict'; ) 声明放在函数体所有语句之前。

      function fn(){
        "use strict";
        return "123"; }
      //当前fn函数开启了严格模式
      

24.3严格模式中的变化

严格模式对 Javascript 的语法和⾏为,都做了⼀些改变。

'use strict'
num = 10
console.log(num)//严格模式后使⽤未声明的变量
-----------------------------------------------------------------------------
---
var num2 = 1;
delete num2;//严格模式不允许删除变量
-----------------------------------------------------------------------------
---
function fn() {
console.log(this); // 严格模式下全局作⽤域中函数中的 this 是 undefined
}
fn();
-----------------------------------------------------------------------------
----
function Star() {
this.sex = '男'; }
// Star();严格模式下,如果 构造函数不加new调⽤, this 指向的是undefined 如果给他赋值
则 会报错.
var ldh = new Star();
console.log(ldh.sex);
-----------------------------------------------------------------------------
-----
setTimeout(function() {
console.log(this); //严格模式下,定时器 this 还是指向 window
}, 2000);

image-20210802141702498

image-20210802141711715

25 - 正则表达式

25.1什么是正则表达式

正则表达式( Regular Expression )是⽤于匹配字符串中字符组合的模式。在JavaScript中,正则表

达式也是对象。

正则表通常被⽤来检索、替换那些符合某个模式(规则)的⽂本,例如验证表单:⽤户名表单只能输⼊

英⽂字⺟、数字或者下划线, 昵称输⼊框中可以输⼊中⽂(匹配)。此外,正则表达式还常⽤于过滤掉⻚

⾯内容中的⼀些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等 。

其他语⾔也会使⽤正则表达式,本阶段我们主要是利⽤JavaScript 正则表达式完成表单验证。

25.2 正则表达式的特点

  1. 灵活性、逻辑性和功能性⾮常的强。

  2. 可以迅速地⽤极简单的⽅式达到字符串的复杂控制。

  3. 对于刚接触的⼈来说,⽐较晦涩难懂。⽐如:^w+([-+.]w+)@w+([-.]w+).w+([-.]w+)*$

  4. 实际开发,⼀般都是直接复制写好的正则表达式. 但是要求会使⽤正则表达式并且根据实际情况修改正则表达式. ⽐如⽤户名: /[1]{3,16}$/

25.3. 正则表达式在js中的使⽤

  1. 正则表达式的创建

    在 JavaScript 中,可以通过两种⽅式创建⼀个正则表达式。

    ⽅式⼀:通过调⽤RegExp对象的构造函数创建

    var regexp = new RegExp(/123/);
    console.log(regexp);
    

    ⽅式⼆:利⽤字⾯量创建 正则表达式

    var rg = /123/;
    
  2. 测试正则表达式

    test() 正则对象⽅法,⽤于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。

    var rg = /123/;
    console.log(rg.test(123));//匹配字符中是否出现123 出现结果为true
    console.log(rg.test('abc'));//匹配字符中是否出现123 未出现结果为false
    

image-20210802141923129

25.4 正则表达式中的特殊字符

  1. 正则表达式的组成

    ⼀个正则表达式可以由简单的字符构成,⽐如 /abc/,也可以是简单和特殊字符的组合,⽐如 /ab*c/。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专⽤符号,如 ^ 、$ 、+ 等。特殊字符⾮常多,可以参考:

    MDN

    jQuery ⼿册:正则表达式部分

    正则测试⼯具

  2. 边界符

    正则表达式中的边界符(位置符)⽤来提示字符所处的位置,主要有两个字符

边界符 说明
^ 表示匹配⾏⾸的⽂本(以谁开始)
$ 表示匹配⾏尾的⽂本(以谁结束)

如果 ^和 $ 在⼀起,表示必须是精确匹配。

var rg = /abc/; // 正则表达式⾥⾯不需要加引号 不管是数字型还是字符串型
// /abc/ 只要包含有abc这个字符串返回的都是true
console.log(rg.test('abc'));
console.log(rg.test('abcd'));
console.log(rg.test('aabcd'));
console.log('---------------------------');
var reg = /^abc/;
console.log(reg.test('abc')); // true
console.log(reg.test('abcd')); // true
console.log(reg.test('aabcd')); // false
console.log('---------------------------');
var reg1 = /^abc$/; // 精确匹配 要求必须是 abc字符串才符合规范
console.log(reg1.test('abc')); // true
console.log(reg1.test('abcd')); // false
console.log(reg1.test('aabcd')); // false
console.log(reg1.test('abcabc')); // false
  1. 字符类

    字符类表示有⼀系列字符可供选择,只要匹配其中⼀个就可以了。所有可供选择的字符都放在⽅括号

    内。

    1. [] ⽅括号

    表示有⼀系列字符可供选择,只要匹配其中⼀个就可以了

    var rg = /[abc]/; // 只要包含有a 或者 包含有b 或者包含有c 都返回为true
    console.log(rg.test('andy'));//true
    console.log(rg.test('baby'));//true
    console.log(rg.test('color'));//true
    console.log(rg.test('red'));//false
    var rg1 = /^[abc]$/; // 三选⼀ 只有是a 或者是 b 或者是c 这三个字⺟才返回 true
    console.log(rg1.test('aa'));//false
    console.log(rg1.test('a'));//true
    console.log(rg1.test('b'));//true
    console.log(rg1.test('c'));//true
    console.log(rg1.test('abc'));//true
    -----------------------------------------------------------------------------
    -----
    var reg = /^[a-z]$/ //26个英⽂字⺟任何⼀个字⺟返回 true - 表示的是a 到z 的范围
    console.log(reg.test('a'));//true
    console.log(reg.test('z'));//true
    console.log(reg.test('A'));//false
    -----------------------------------------------------------------------------
    ------
    //字符组合
    var reg1 = /^[a-zA-Z0-9]$/; // 26个英⽂字⺟(⼤写和⼩写都可以)任何⼀个字⺟返回
    true
    -----------------------------------------------------------------------------
    -------
    //取反 ⽅括号内部加上 ^ 表示取反,只要包含⽅括号内的字符,都返回 false 。
    var reg2 = /^[^a-zA-Z0-9]$/;
    console.log(reg2.test('a'));//false
    console.log(reg2.test('B'));//false
    console.log(reg2.test(8));//false
    console.log(reg2.test('!'));//true
    

    2. 量词符

    量词符⽤来设定某个模式出现的次数。

    image-20210802142251885

    3. ⽤户名表单验证

    功能需求:

    1. 如果⽤户名输⼊合法, 则后⾯提示信息为: ⽤户名合法,并且颜⾊为绿⾊

    2. 如果⽤户名输⼊不合法, 则后⾯提示信息为: ⽤户名不符合规范, 并且颜⾊为红⾊

    image-20210802142325551

image-20210802142334695

分析:

  1. ⽤户名只能为英⽂字⺟,数字,下划线或者短横线组成, 并且⽤户名⻓度为6~16位.

  2. ⾸先准备好这种正则表达式模式/$[a-zA-Z0-9-_]{6,16}^/

  3. 当表单失去焦点就开始验证.

  4. 如果符合正则规范, 则让后⾯的span标签添加 right类.

  5. 如果不符合正则规范, 则让后⾯的span标签添加 wrong类.

<input type="text" class="uname"> <span>请输⼊⽤户名</span>
<script>
// 量词是设定某个模式出现的次数
var reg = /^[a-zA-Z0-9_-]{6,16}$/; // 这个模式⽤户只能输⼊英⽂字⺟ 数字 下划线
中划线
var uname = document.querySelector('.uname');
var span = document.querySelector('span');
uname.onblur = function() {
  if (reg.test(this.value)) {
    console.log('正确的');
    span.className = 'right';
    span.innerHTML = '⽤户名格式输⼊正确';
  } else {
    console.log('错误的');
    span.className = 'wrong';
    span.innerHTML = '⽤户名格式输⼊不正确';
  }
}
</script>

4. 括号总结

1.⼤括号 量词符. ⾥⾯表示重复次数

2.中括号 字符集合。匹配⽅括号中的任意字符.

3.⼩括号表示优先级

正则表达式在线测试

4,预定义类

预定义类指的是某些常⻅模式的简写⽅式.

image-20210802142441243

案例:验证座机号码

var reg = /^d{3}-d{8}|d{4}-d{7}$/;
var reg = /^d{3,4}-d{7,8}$/;

表单验证案例

//⼿机号验证:/^1[3|4|5|7|8][0-9]{9}$/;
//验证通过与不通过更换元素的类名与元素中的内容
if (reg.test(this.value)) {
// console.log('正确的');
this.nextElementSibling.className = 'success';
this.nextElementSibling.innerHTML = '<i class="success_icon"></i> 恭喜您输
⼊正确';
} else {
// console.log('不正确');
this.nextElementSibling.className = 'error';
this.nextElementSibling.innerHTML = '<i class="error_icon"></i>格式不正
确,请从新输⼊ ';
}
//QQ号验证: /^[1-9]d{4,}$/;
//昵称验证:/^[u4e00-u9fa5]{2,8}$/
//验证通过与不通过更换元素的类名与元素中的内容 ,将上⼀步的匹配代码进⾏封装,多次调⽤
即可
function regexp(ele, reg) {
ele.onblur = function() {
if (reg.test(this.value)) {
// console.log('正确的');
this.nextElementSibling.className = 'success';
this.nextElementSibling.innerHTML = '<i class="success_icon"></i> 恭喜
您输⼊正确';
} else {
// console.log('不正确');
this.nextElementSibling.className = 'error';
this.nextElementSibling.innerHTML = '<i class="error_icon"></i> 格式不正
确,请从新输⼊ ';
}
}
};
//密码验证:/^[a-zA-Z0-9_-]{6,16}$/
//再次输⼊密码只需匹配与上次输⼊的密码值 是否⼀致

3.5正则替换replace

replace() ⽅法可以实现替换字符串操作,⽤来替换的参数可以是⼀个字符串或是⼀个正则表达式。

var str = 'andy和red';
var newStr = str.replace('andy', 'baby');
console.log(newStr)//baby和red
//等同于 此处的andy可以写在正则表达式内
var newStr2 = str.replace(/andy/, 'baby');
console.log(newStr2)//baby和red
//全部替换
var str = 'abcabc'
var nStr = str.replace(/a/,'哈哈')
console.log(nStr) //哈哈bcabc
//全部替换g
var nStr = str.replace(/a/a,'哈哈')
console.log(nStr) //哈哈bc哈哈bc
//忽略⼤⼩写i
var str = 'aAbcAba';
var newStr = str.replace(/a/gi,'哈哈')//"哈哈哈哈bc哈哈b哈哈"

案例:过滤敏感词汇

<textarea name="" id="message"></textarea> <button>提交</button> <div></div>
<script>
var text = document.querySelector('textarea');
var btn = document.querySelector('button');
var div = document.querySelector('div');
btn.onclick = function() {
  div.innerHTML = text.value.replace(/激情|gay/g, '**');
}
</script>

26 - TRY CATCH

不抛出错误,后续代码继续执⾏(try⾥的代码出错的那⾏以下不会再执⾏),把错误放到catch的参数e⾥。

try{
console.log('a');
console.log(b); // 出错⾏
console.log('c'); // 不执⾏
} catch(e){
console.log(e.name); // 错误名称
console.log(e.message) }
console.log('d') // 会执⾏

e.name 的六种对应的信息:

1、EvalError:eval()的使⽤与定义不⼀致;

2、RangeError:数值越界

3、ReferenceError: ⾮法或者不能识别的引⽤数值 ,变量、函数 没声明就使⽤,

4、SyntaxError:发⽣语法解析错误;

5、TypeError:操作类型错误;

6、uRLError:URL处理函数使⽤不当;

27 - ⼩技巧⼯具⽅法

1、实现jquery连续调⽤⽅法

<script >
// ⽅法的连续调⽤
var fanfa = {
  smoke:function () {
    console.log("抽烟有害健康");
    return this;
  },
  drink:function () {
    console.log('喝酒有害健康');
    return this;
  }
}
fanfa.smoke().drink();
</script>

2、⾃定义的typeof

function myType(target) {
var ret = typeof(target);
template = {
"[object Array]": "arrery",
"[object Object]": "object",
"[object Number]": "number-object",
"[object Boolean]": "boolean-object",
"[object String]": "string-object"
};
if (target === null) {
return 'null';
}else if(ret == 'object') {
var str = Object.prototype.toString.call(target);
return template[str];
} else {
return ret;
}
}

3、⾃定义的数组去重⽅法

// 数组的去重,思路:把数组的每⼀个元素放到对象中去,给⼀个随意的值,因为对象中的每个
属性是不重复的,达到去重的效果
Array.prototype.unique = function () {
var temp = {},
arr = [],
len = this.length;
for (var i=1;i<len;i++) {
if (! temp[this[i]]) {
temp[this[i]] = 'abc';
arr.push(this[i]);
}
}
return arr; }

28 - 性能

循环⾥添加匿名函数效率低,要改成命名函数,放在循环的外⾯,如果不是循环的⽅式添加事件,推荐

使⽤匿名函数

异步加载js

js加载的缺点:加载⼯具⽅法没必要阻塞⽂档,js加载会影响⻚⾯效率,⼀旦⽹速不好,那么整个⽹站将

等待js加载⽽不进⾏后续的渲染等⼯作。

有些⼯具⽅法需要按需加载,⽤到再加载,不⽤不加载

异步加载的三种⽅案:

1、defer异步加载,但是要等到dom⽂档全部解析完才会被执⾏,只有ie能⽤,也可以将代码写到内部

script type="text/javascript" src="tools.js" defer="defer"></script>

2、async w3c标准,异步加载,加载完就⾏,async只能加载外部脚本,不能把js写在script标签⾥。

script type="text/javascript" src="tools.js" aysnc="aysnc"></script>

3、执⾏时也不阻塞⻚⾯。

4、创建script,插⼊到dom中,加载完毕后callBack。

demo.js
function test(){
console.log(a) }<script type="text/javascript">
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "demo.js";
// 由于ie没有onload事件,但是是他有⼀个状态码的触发事件
script.onreadystatechange = function (){
  if (script.readState == "complete" || script.readState == "loaded") {
    test();
  }
} else {
// safari chrome firefox opera
  script.onload = function () {
    test();
  }
}
document.head.appendchild(script);
=========== 封装成函数============
function loadScript(url, callback){
  var script = document.createElement('script');
  script.type = "text/javascript";
  if(script.readyState){
    script.onreadystatechange = function (){
      if (script.readState == "complete" || script.readState == "loaded") {
        callback();
      }
    }
  }else {
    script.onload = function () {
      callback();
    }
  }
  script.src = url;
  document.head.appendchild(script);
}
loadScript('demo.js', function (){
  test();
})

JavaScript之js加载时间

在js加载开始的时候,浏览器会记录js执⾏的这段过程。

1.创建Document对象,开始解析web⻚⾯,解析HTML元素和他们的⽂本内容后添加Element对象
和Text节点到⽂档中。这个阶段Document。readyState = "loading"。
2.遇到link外部css,创建线程加载,并继续解析⽂档。
3.遇到script外部js,并且没有设置async , defer ,浏览器加载,并阻塞,等待js加载完
成并执⾏该脚本,然后继续解析⽂档
4.遇到script外部js,并且设置有async,defer 浏览器创建线程加载,并继续解析⽂档,对于
async属性的脚本,脚本加载完成后⽴即执⾏(异步禁⽌使⽤docuemnt.write())。
5.遇到img标签等,先正常解析dom结构,然后浏览器异步加载src,并继续解析⽂档
6.当⽂档解析完成,document.readyState = "interactive";
7.⽂档解析完成后,所有设置有defer的脚本会按照顺序执⾏。
8..当⽂档解析完成之后,document对象触发DOMContentLoaded事件,这也标志着程序执⾏从
同步脚本执⾏阶段,转化为事件驱动阶段
9.当所有saync的脚本加载完成并执⾏后,img等加载完成后,document.readyState =
"complete" window对象触发load事件
10.从此,⻚⾯以异步响应⽅式处理⽤户输⼊,⽹络事件等。
console.log(document.readyState);
document.onreadystatechange = function(){
console.log(document.readyState);
}

在这⾥我们说⼀下两个事件的区别:DOMContentLoaded和load

load事件我们知道,在window上有这个事件window.onload 这个事件是在dom⽂档完全渲染完
毕之后才会触发,那么如果⽹速不好的时候,有那么⼀丁点的数据没有加载完成,这个事件就不
会执⾏,这是⼀个⾮常脱浏览器后腿的事件。
⽽DOMContentLoaded则不同了,这个事件表示的是当Dom解析完毕之后执⾏,没有浪费浏览器运
⾏效率,但是这个事件只能⽤addEventListener绑定。但这也不是个问题,所以,当我们在
head标签⾥⾯写Javascript语句的时候⽤这个事件,千万不要⽤load了。

  1. a-z0-9_- ↩︎

原文地址:https://www.cnblogs.com/zgboy/p/15090405.html