目录

一、JavaScript概述

二、JavaScript常见输出方式

三、常量

四、变量

五、关键字和保留字

1.关键字

2.标识符

3.注释

六、数据及数据类型

1.数据

2.数据类型

七、数据类型转换

1.将其它类型转换为字符串

2.将其它类型转换为Number类型

3.将其它类型转换为布尔类型

八、运算符

1.运算符概述

2.算术运算符

3.正负运算符

4.赋值运算符

5.自增自减运算符

6.关系运算符

7.逻辑运算符

8.逗号运算符

9.三目运算符

九、流程控制

1.选择结构-if语句

2. 选择结构-switch语句

3. 循环结构-while语句

4. 循环结构-do while语句

5.变量的作用域

6. 循环结构-for语句

7. 关键字break

8.关键字continue

9.循环嵌套

十、数组

1.数组的基本操作

2.数组的解构赋值

3.数组的增删改查

4.数组的常用方法

5.数组的排序

6.二维数组

十一、函数

1.函数的基本概念

2.函数的注意点

3.函数中的 arguments 与 ...

4.三个特殊函数

5.函数中变量作用域

6.作用域链

7.浏览器的预解析

十二、面向对象

1.面向对象基本概念

2.使用默认类创建对象

3.函数与方法的区别

4.关键字 this

5.使用工厂函数创建对象

6.使用构造函数创建对象

7.对象三角恋关系

8.面向对象三大特性

9.类与对象

10.ES6继承

11. 对象相关操作

12.深拷贝与浅拷贝

十三、数组的高级使用

1.数组的遍历

2.数组的查找

3.数组的过滤与映射

4.数组的删除与清空

5.数组的排序

十四、字符串的常用方法

1.字符串与数组的共有方法

2.字符串特有的方法

十五、基本数据类型和基本包装类型

十六、JS自带的对象


一、JavaScript概述

1.什么是JavaScript

JavaScript 简称 JS ,是前端开发的一门脚本语言(解释型语言

  • 解释型语言:程序执行之前,不需要对代码进行编译,在运行时边解析边执行的语言;比如:python

  • 浏览器工作原理

(1).User Interface(用户界面):除了显示内容以外的所有都是用户界面,比如:输入地址栏、前进、后退、刷新、书签等;

(2).Browser engine(浏览器引擎):是用户界面与渲染引擎的桥梁,通过浏览器引擎可以实现用户界面与渲染引擎进行交互、查询等;

(3).Rendering engine(渲染引擎):渲染请求到的数据,可以解析HTML和CSS,并将解析后的内容显示出来;

(4).Networking(网络模块):处理HTTP网络请求;

(5).JavaScript Interpreter(JavaScript 解析器):解析和执行JavaScript代码;

(6).UI Backend(UI后端):专门处理复选框、弹窗等复杂的界面;

(7).Data Persistence(数据存储器):存储网页数据,比如:cookies,登录时存储账号与密码;

 

编译型语言:程序执行之前,需要一个专门的编译过程,把程序编译成机器语言的文件,比如exe文件;比如:C语言,C++,Java

2.JavaScript的作用与组成

(1).JavaScript作用

  • HTML 提供网页上显示的内容(结构)

  • CSS 美化网页(样式)

 

  • JavaScript 控制网页行为(行为

(2).JavaScript发展史

  • JavaScript起源于Netscape公司的 LiveScript 语言
    • 1994年网景公司发布历史上第一个比较成熟的浏览器(Navigator 0.9), 但是只能浏览不能交互
    • 1995年为了解决表单有效性验证就要与服务器进行多次地往返交互问题,网景公司录用Brendan Eich(布兰登·艾奇),他在 10 天内开发出 LiveScript 语言
    • 在 Netscape Navigator 2.0 即将正式发布前,Netscape 将LiveScript 更名为 JavaScript, 目的是为了蹭Java的热度
    • 所以Java和 JavaScript之间的关系就像老婆和老婆饼一样,就是毫无关系

(3).JavaScript组成

  • ECMAScript:JavaScript的语法标准
    • ECMA是European Computer Manufacturers Association的缩写,即欧洲计算机制造商协会
    • ECMAScript是ECMA制定的脚本语言的标准, 规定了一种脚本语言实现应该包含的基本内容
    • JavaScript是脚本语言的一种,所以JavaScript也必须遵守ECMAScript标准,包含ECMAScript标准中规定的基本内容
  • DOM(Document Object Model):JavaScript操作网页上的元素(标签)的API
  • BOM(Browser Object Model):JavaScript操作浏览器的部分功能的API

3.JavaScript书写格式

  • 和CSS书写格式一样, JavaScript也有三种书写格式, 分别是"行内式"、"内嵌式"、"外链式"
  • 和CSS书写格式一样, JavaScript三种书写格式中也推荐大家使用"外链式", 遵守结构、样式、行为分离

  • 行内式格式(不推荐)
<div onclick="alert('hello world');">我是div</div>

  • 内嵌式格式(内联式)
</body>
... ...
<script type="text/javascript">
       alert("hello world");
</script>
</body>
  • 内嵌式注意点
    • 通常将js代码放在body的最后, 因为HTML是从上至下加载, 而js代码通常是给标签添加交互(操作元素), 所以需要先加载HTML, 否则如果执行js代码时HTML还未被加载, 那么js代码将无法添加交互(操作元素);
    • HTML页面中出现<script>标签后,就会让页面暂停等待脚本的解析和执行。无论当前脚本是内嵌式还是外链式,页面的下载和渲染都必须停下来等待脚本的执行完成才能继续, 所以如果把js代码写在head中, 那么js代码没有执行完毕之前后续网页无法查看
    • 如果不愿意把js代码放在body的最后, 也可以放在head标签中, 但是为了保证执行js代码时HTML一定已经被加载了, 那么需要添加一些额外代码.(不推荐)
<head>
    <script>
        window.onload = function () { // 必须添加这句
            alert("hello world");
        }
    </script>
</head>

  • 外链式格式
<script type="text/javascript" src="01-js书写格式.js"></script>
  • 外链式注意点
    • 外链式的script代码块中不能编写js代码, 即便写了也不会执行
<script type="text/javascript" src="01-js书写格式.js">
    alert("hello world"); // 不会被执行
</script>
  • 由于每次加载外链式的js文件都会发送一次请求, 这样非常消耗性能, 所以在企业开发中推荐将多个JS文件合成为一个JS文件,以提升网页的性能和加载速度

二、JavaScript常见输出方式

  • 1.在控制台中显示内容
console.log("普通信息");  
console.error("错误信息"); 
console.warn("警告信息");

  • 2.在浏览器弹窗中显示内容
alert("hello world");

confirm("你好吗?");

prompt("请输入内容:");

注意点:如果输出内容不是数字,则必须用单引号双引号括起来!

  • 页面内容区域显示
document.write("hello world2");

  • 注意点
  • JS中严格区分大小写
alert("hello world"); // 正确 
Alert("hello world"); // 错误
  • 每一条JS语句  分号 结尾
    • 如果不写分号,浏览器会自动添加,但是会消耗一些系统资源
    • 并且有些时候浏览器会加错分号,所以在开发中分号必须写
  • JS中会忽略多个空格和换行
alert
(
"hello world"
);  

三、常量

1.什么是常量?

  • 常量表示一些固定的数据,也就是不能改变的数据

2.常量分类

  • 整型常量(整数)
    • 二进制(以0b开头; 例如0b1001,0b1010)
    • 十进制(9,-10,0)
    • 八进制(以0开头; 例如011, 012)
    • 十六进制(以0x开头; 0x10, 0x11)
  • 实型常量(小数)
    • 小数(3.14, 9.8)
  • 字符串常量
    • 使用单引号(')或双引号(")括起来的一个或几个字符
    • 注意点:无论用单引号或者双引号括起来了多少个字符,在JavaScript中都是字符串常量
  • 布尔值
    • 布尔常量只有两种状态:true(真) 或 false(假)
  • 特殊字符
    \n     #换行,相当于敲一下回车
    \t     #跳到下一个tab位置,相当于按一下键盘上的tab键。 \b 退格,相当于按一下backspace
    \r     #回车
    \f     #换页,光标移到到下页开头
    \\     #输出\字符,也就是在屏幕上显示一个\字符
    \'     #输出'字符,也就是在屏幕上显示一个'字符
    \"     #输出"字符,也就是在屏幕上显示一个"字符

3.常量的定义

  • 自定义常量,在ES6中新增的
  • 格式:const 常量名称 = 常量取值;
  • 注意点:常量是无法被修改的
        // 定义常量
        const NUM = 66;
        // 常量无法修改!
        // NUM = 99;   // Uncaught TypeError: Assignment to constant variable.
        console.log(NUM);

四、变量

1.什么是变量?

  • 变量表示一些可以变化的数据。当一个数据的值需要经常改变或者不确定时,就应该用变量来表示
  • 在现实生活中超市的储物格就是变量, 在不同的时间段里面, 储物格中存储的数据也不一样

2.如何定义变量?

  • 定义变量(声明变量), 任何变量在使用之前,必须先进行定义
  • 定义变量目的:在内存中分配一块存储空间给变量,方便以后存储数据
  • 如果定义了多个变量,就会为这多个变量分别分配不同的存储空间
  • 格式1: var 变量名称;
  var num;
  • 格式2: var 变量名称, 变量名称;
  var num1, num2, num3;

3.如何使用变量

  • 使用变量就是往变量里面存点东西或者取出变量中存储的内容;
  • 3.1 往变量中存储数据
var num;
num = 10;
  • 3.2 如何获取存储在变量中的数据

var num;
num = 10;
console.log(num);

4.变量的初始化

  变量第一次赋值,可以称为 “初始化

  • 4.1 先定义,后初始化
var num;
num = 10;
  • 4.2 定义的同时,进行初始化
var num = 10;
  • 4.3 其它表现形式
  // 部分初始化
  var a, b, c = 10;
  // 完全初始化
  var a , b, c;
  a = b = c = 10;

  思考:不初始化里面存储什么? (undefined

  var num;
  console.log(num); // undefined
  // 如果变量没有初始化, 里面存储的是undefined

5.如何修改变量值?

  • 利用等号 = 重新赋值即可,每次赋值都会覆盖原来的值
 var num;
 num = 10;
 num = 20;
 console.log(num); // 20

6.注意点

  • 6.1 在JavaScript中,变量之间是可以相互赋值的
        var num;
        var value;
        num = 123;
        value = num;     // 将Num中的值拷贝一份给value
        console.log(num);
        console.log(value);
  • 6.2 在JavaScript中,如果定义了同名的变量,那么后定义的变量会覆盖先定义的变量
        var num = 666;
        // num = 888;   // 如果num前面没有var, 那么就是修改变量中存储的值
        var num = 888;  // 如果num前面有var, 那么就不是修改变量中存储的值, 而是重新定义一个新的变量
        console.log(num);
  • 6.3 在老版本的标准的(ES6之前)JavaScript中可以先使用变量, 再定义变量, 并不会报错
    • 由于JavaScript是一门解释型的语言, 会边解析边执行, 浏览器在解析JavaScript代码之前还会进行一个操作 预解析
    • 预解析(预处理)步骤:将当前JavaScript代码中所有变量的定义函数的定义放到所有代码的最前面
        console.log(num);
        var num = 123;
        /*
        预处理之后的代码
        var num;
        console.log(num);     // undefined
        num = 123;
        */
  • ES6之前定义变量
    • var  变量名称;
  • ES6定义变量
    • let  变量名称;
  • let与var定义变量区别在于
    • 使用 let 方式定义变量时,不能定义同名变量
    • 必须先定义变量,后使用变量,否则会报错的!
        // ES6之前
        var sum;
        // ES6之后
        let sub;
        // 两者的区别在于:
        // 1.使用let方式定义变量时,不能定义同名变量!
        let q = 20;
        let q = 30;   // Uncaught SyntaxError: Identifier 'q' has already been declared
        // 2.必须先定义变量,后使用变量,否则会报错的!
        console.log(p);
        let p;  // Uncaught ReferenceError: Cannot access 'p' before initialization

五、关键字和保留字

1.关键字

  • 什么是关键字
    • JavaScript关键字的概念和C语言一样, 都是指被赋予特殊含义的单词
    • 关键字在开发工具中会显示特殊颜色
    • 关键字不能用作变量名、函数名等
    • 关键字严格区分大小写, var和Var前者是关键字, 后者不是
关键字        
break do instanceof typeof case
else new var catch finally
return void continue for switch
while default if throw delete
in try function this with
debugger false true null  

  • 什么是保留字?
    • JavaScript 预留的关键字,他们虽然现在没有作为关键字,但在以后的升级版本中有可能作为关键字
保留字          
class enum extends super const export
import implements let private public yield
interface package protected static    

2.标识符

  • 什么是标识符?

    • JavaScript标识符的概念和C语言一样, 都是指程序员在程序中自己起的名称(诸如: 变量名称,函数名称等)
  • 标识符命名规则(必须遵守)

    • JavaScript标识符命名规则的概念和C语言一样
    • 只能由26个英文字母的大小写、10个阿拉伯 数字0~9下划线_美元符号$ 组成
    • 不能以数字开头
    • 严格区分大小写,比如test和Test是2个不同的标识符
    • 不可以使用关键字、保留字作为标识符
    • JS底层保存标识符时实际上是采用的Unicode编码,所以理论上讲,所有的utf-8中含有的内容都可以作为标识符
不建议使用标识符          
abstract double goto native static boolean
enum implements package super byte export
import private synchronize char extends int
protected throws class final interface public
transient const float long short volatile
arguments encodeURI Infinity Number RegExp undefined
isFinite Object String Boolean Error RangeError
parseFloat SyntaxError Date eval JSON ReferenceError
TypeError decodeURI EvalError Math URIError decodeURIComponent
Function NaN isNaN parseInt Array encodeURICOmponent
  • 标识符命名规范(建议遵守)
    • JavaScript标识符命名规范的概念和C语言一样
      • 见名之意:变量的名称要有意义(有利于提高阅读性
      • 驼峰命名:变量的命名遵守 驼峰命名法,首字母小写,第二个单词的首字母大写,比如:userName,myFirstName

3.注释

   JavaScript中的注释和C语言一模一样(格式、特点), 也分为单行注释和多行注释

  • 3.1 单行注释
// 我是被注释的内容
var num;
  • 注意点:单行注释的有效范围是从第二个斜杠开始一直直到这一行的末尾, 也就是被注释的内容不能换行

  • 3.2 多行注释
/*
我是被注释的内容
我是被注释的内容
我是被注释的内容
*/
var num;
  • 注意点:多行注释的有效范围是从第一颗星开始直到第二颗星结束, 也就是说被注释的内容可以换行的
  • 3.3 嵌套规则
    • 1.单行注释可以嵌套单行注释, 但是必须在一行
    • 2.单行注释可以嵌套多行注释, 但是必须在一行
    • 3.多行注释可以嵌套单行注释 3.4多行注释不可以嵌套多行注释

六、数据及数据类型

1.数据

(1).什么是数据?

  • 生活中无时无刻都在跟数据打交道

    • 例如:人的体重、身高、收入、性别等数据等
  • 在我们使用计算机的过程中,也会接触到各种各样的数据

    • 例如: 文档数据、图片数据、视频数据等

(2).数据分类

  • 静态数据

    • 静态数据是指一些永久性的数据,一般存储在硬盘中。硬盘的存储空间一般都比较大,现在普通计算机的硬盘都有500G左右,因此硬盘中可以存放一些比较大的文件
    • 存储的时长:计算机关闭之后再开启,这些数据依旧还在,只要你不主动删掉或者硬盘没坏,这些数据永远都在
    • 哪些是静态数据:静态数据一般是以文件的形式存储在硬盘上,比如文档、照片、视频等。
  • 动态数据

    • 动态数据指在程序运行过程中,动态产生的临时数据,一般存储在内存中。内存的存储空间一般都比较小,现在普通计算机的内存只有8G左右,因此要谨慎使用内存,不要占用太多的内存空间
    • 存储的时长:计算机关闭之后,这些临时数据就会被清除
    • 哪些是动态数据:当运行某个程序(软件)时,整个程序就会被加载到内存中,在程序运行过程中,会产生各种各样的临时数据,这些临时数据都是存储在内存中的。当程序停止运行或者计算机被强制关闭时,这个程序产生的所有临时数据都会被清除。
  • 既然硬盘的存储空间这么大,为何不把所有的应用程序加载到硬盘中去执行呢?

    • 主要原因就是内存访问速度比硬盘N倍
  • 静态数据和动态数据的相互转换
    • 也就是从磁盘加载到内存

  • 动态数据和静态数据的相互转换
    • 也就是从内存保存到磁盘

  • 数据的计量单位

    • 不管是静态还是动态数据,都是0和1组成的
    • 数据越大,包含的0和1就越多
1 B(Byte字节) = 8 bit(位)
// 00000000 就是一个字节
// 111111111 也是一个字节
// 10101010 也是一个字节
// 任意8个0和1的组合都是一个字节
1 KB(KByte) = 1024 B
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB

2.数据类型

(1).JavaScript数据类型概述

  • 作为程序员最关心的是内存中的动态数据, 因为我们写的程序就是在内存中的

  • 程序在运行过程中会产生各种各样的临时数据, 为了方便数据的运算和操作, JavaScript对这些数据进行了分类, 提供了丰富的数据类型

  • 在JS中一共有六种数据类型

  • 基本数据类型
    • Number  数值类型
      • 在JavaScript中无论是整数还是小数都是属于数值类型的
    • String  字符串类型
      • 在JavaScript中无论是通过单引号还是通过双引号括起来的内容都是属于字符串类型的
    • Boolean  布尔类型
      • 在JavaScript中布尔类型比较特殊, 只有两个取值 true / false
    • Undefined  未定义类型
      • 在JavaScript中未定义类型比较特殊, 只有一个取值 undefined
    • Null 空值类型
    • 在JavaScript中空值类型, 只有一个取值 null
  • 引用数据类型
    • Object  对象类型
  • 如何查看数据类型

    • 使用 typeof 操作符 可以用来 检查数据类型
    • 使用格式:typeof 数据;    例如: typeof 123;   typeof num;
  • typeof 操作符会将检查的结果以字符串的形式返回给我们
console.log(typeof 123);   // number
var num = 10;
console.log(typeof num);   // number
var value= 10;
// 此时将value的数据类型number以字符串返回给我们, 存入到res变量中
var res = typeof value; 
// 此时检查res的数据类型为string, 证明typeof返回给我们的是一个字符串
console.log(typeof res);     // string

(2).基本数据类型

2.1 字符串类型

  • String 用于表示一个字符序列,即 字符串
  • 字符串需要使用 单引号 双引号 括起来
var str1 = "hello";
var str2 = 'nj';

console.log(typeof str1 );  // string
console.log(typeof str2);   // string
  • 相同引号不能嵌套,不同引号可以嵌套
    • 双引号不能放双引号,单引号不能放单引号
var str3 = "hello "nj""; // 错误
var str4 = `hello `nj``; // 错误
var str5 = "hello 'nj'"; // 正确
var str6 = `hello "nj"`; // 正确
  • 给变量加上引号, 那么变量将变为一个常量
var num = 110;
console.log(num);      // 输出变量中的值
console.log("num");    // 输出常量num

2.2 Number类型

  • 在JS中所有的数值都是 Number 类型( 整数 小数
var num1= 123;
var num2= 3.14;
console.log(typeof num1);   // number
console.log(typeof num2);   // number
  • 由于内存的限制,ECMAScript 并不能保存世界上所有的数值

  • 最大值:Number.MAX_VALUE
console.log(Number.MAX_VALUE);  // 1.7976931348623157e+308
  • 最小值:Number.MIN_VALUE
console.log(Number.MIN_VALUE);  // 5e-324
  • 无穷大:Infinity, 如果超过了最大值就会返回该值
console.log(Number.MAX_VALUE + Number.MAX_VALUE);   // Infinity
  • 无穷小:-Infinity, 如果超过了最小值就会返回该值
 console.log(typeof Infinity);    // number
 console.log(typeof -Infinity);   // number
  • NaN 非法数字(Not A Number),JS中当对数值进行计算时没有结果返回,则返回NaN
var num3 = NaN;
console.log(typeof num3);  // number
  • Number类型注意点
var sum1 = 10 + 20;
console.log(sum1);     // 30
var sum1 = 10.1 + 21.1;
console.log(sum1);     // 31.200000000000003
  • JS中整数的运算可以保证精确的结果
  • 在JS中浮点数的运算可能得到一个不精确的结果

2.3 Boolean 布尔值

  • 布尔型也被称为逻辑值类型或者真假值类型
  • 布尔型只能够取真(true)和假(false)两种数值
var bool1 = true;
var bool2 = false;
console.log(typeof bool1); // boolean
console.log(typeof bool2); // boolean
  • 虽然Boolean 类型的字面值只有两个,但 ECMAScript 中所有类型的值都有与这两个 Boolean 值等价的值
    • 任何非零数值都是 true , 包括正负无穷大, 只有 0 NaN false
    • 任何非空字符串都是 true, 只有 空字符串 是false
    • 任何对象都是 true, 只有 null undefined 是false
  • 总结如下
    • ture:  非零数值、非空字符串、对象
    • false:  0、NaN、null、undefined
var bool3 = Boolean(0);
console.log(bool3);            // false
var bool4 = Boolean(1);
console.log(bool4);            // true
var bool5 = Boolean(-1);
console.log(bool4);            // true
var bool6 = Boolean(Infinity);
console.log(bool4);            // true
var bool7 = Boolean(-Infinity);
console.log(bool4);            // true
var bool8 = Boolean(NaN);
console.log(bool8);            // false
var bool9 = Boolean(undefined);
console.log(bool8);            // false
var bool10 = Boolean(null);
console.log(bool8);            // false
var bool11 = Boolean("");
console.log(bool8);            // false
var bool12 = Boolean("abc");
console.log(bool12);           // true

2.4 Undefined

  • Undefined 这是一种比较特殊的类型,表示 变量未赋值,这种类型只有一种值就是 undefined
var num;
console.log(num);  //结果是undefined
  • undefined是Undefined类型的字面量
    • 前者undefined和10, "abc"一样是一个常量
    • 后者Undefined和Number,Boolean一样是一个数据类型
    • 需要注意的是typeof对没有初始化和没有声明的变量都会返回undefined。
var value1 = undefined;
console.log(typeof value);    //结果是undefined

var value2;
console.log(typeof  value2);  //结果是undefined

2.5 Null

  • Null 类型是第二个只有一个值的数据类型,这个特殊的值是 null
  • 从语义上看null表示的是一个的对象。所以使用typeof检查null会返回一个Object
var test1= null;
console.log(typeof test1);    //object 
  • undefined值实际上是由null值衍生出来的,所以如果比较undefined和null是否相等,会返回true
var test1 = null;
var test2 = undefined;
console.log(test1 == test2);
console.log(test1 === test2);

七、数据类型转换

1.将其它类型转换为字符串

  •  1.1 调用被转换数据类型的 toString() 方法
        var num1 = 10;
        var res1 = num1.toString();    // 重点
        console.log(res1); // 10
        console.log(typeof res1);      // string

        var num2 = true;
        var res2 = num2.toString();    // 重点
        console.log(res2);             // true
        console.log(typeof res2);      // string
  • null undefined 这两个值 没有toString()方法 ,如果调用他们的方法,会报错
        var num3 = undefined;
        var res3 = num3.toString(); // 报错
        console.log(res3);

        var num4 = null;
        var res4 = num4.toString(); // 报错
        console.log(res4);

        var num5 = NaN;
        var res5 = num5.toString();
        console.log(res5); // NaN
        console.log(typeof res5); // String
  • 该方法不会影响到原变量,它会将转换的结果返回
        var num6 = 10;
        var res6 = num6.toString();
        console.log(typeof num6); // number
        console.log(typeof res6); // string
  • 数值类型的toString(),可以携带一个参数,输出对应进制的值
        var num7 = 20;
        var res7 = num7.toString(2);
        var res8 = num7.toString(8);
        var res9 = num7.toString(10);
        var res10 = num7.toString(16);
        console.log(res7); // 10100
        console.log(res8); // 24
        console.log(res9); // 20
        console.log(res10); // 14
  • 1.2 将被转换的数据传入 String() 函数中
    • String()  函数存在的意义:
      • 有些值没有toString(),这个时候可以使用String(常量/变量)。比如:undefinednull
    • 对于Number和Boolean实际上就是调用的toString()方法
        var num1 = 10;
        var res1 = String(num1);   // 重点
        console.log(res1);         // 10
        console.log(typeof res1);  // string

        var num2 = true;
        var res2 = String(num2);   // 重点
        console.log(res2);         // true
        console.log(typeof res2);  // string
  • 对于null和undefined,就不会调用toString()方法(因为这两个哥们没有这个方法).而是在内部生成一个新的字符串
        var num3 = undefined;
        var res3 = String(num3);
        console.log(res3); // undefined
        console.log(typeof res3); // string

        var num4 = null;
        var res4 = String(num4);
        console.log(res4); // null
        console.log(typeof res4); // string
  • 1.3 将被转换的数据和 + 单引号‘’ 或者 双引号“” 连接到一起
    • 任何数据 和 +""  或者 '' 连接到一起都会转换为字符串
    • 内部实现原理和String()函数一样
        var num1 = 10;
        var res1 = num1 + '';
        console.log(res1); // 10
        console.log(typeof res1); // string

        var num2 = true;
        var res2 = num2 + "";
        console.log(res2); // true
        console.log(typeof res2); // string

        var num3 = undefined;
        var res3 = num3 + "";
        console.log(res3); // undefined
        console.log(typeof res3); // string

        var num4 = null;
        var res4 = num4 + "";
        console.log(res4); // null
        console.log(typeof res4); // string

2.将其它类型转换为Number类型

  • 2.1 将被转换的数据传入 Number() 函数中
  • 字符串 --> 数字
    • 如果是 纯数字 的字符串,则直接将其转换为 数字
          var str1 = "123";
          var res1 = Number(str1);
          console.log(res1);          // 123
          console.log(typeof  res1);  // number
      
    • 如果字符串中 有非数字的内容,则转换为 NaN
          var str2 = "123ab";
          var res2 = Number(str2);
          console.log(res2);       // NaN
      
    • 如果字符串是一个 空串 或者是一个 全是空格 的字符串,则转换为 0
          var str3 = "";
          var res3 = Number(str3);
          console.log(res3); // 0
      
          var str4 = "    ";
          var res4 = Number(str4);
          console.log(res4); // 0
      
  • 布尔 --> 数字
    • true   转成  1
    • false 转成   0
          var bool1 = true;
          var res5 = Number(bool1);
          console.log(res5);         // 1
      
          var bool2 = false;
          var res6 = Number(bool2);
          console.log(res6);        // 0
      
  • null --> 数字( 0
        var str5 = null;
        var res7 = Number(str5);
        console.log(res7);       // 0
    
  • undefined --> 数字NaN
       var str6 = undefined;
       var res8 = Number(str6);
       console.log(res8);        // NaN
    
  • 总结
    • 空字符串 / false  / null   转换后都是 0
    • 包含非数字内容 / undefined   转换后都是 NaN
  • 2.2 利用 +  -  运算符
    • 本质上,还是调用了 Number() 函数,所以,用法和Number()函数相同
      • 添加加号,不会修改数据的正负性
      • 添加减号,会修改数据的正负性
        var str1 = "666";
        var res1 = +str1;
        console.log(res1); // 666
        console.log(typeof res1);

        var str2 = "3.14";
        var res2 = +str2;
        console.log(res2); // 3.14
        console.log(typeof res2);

        var str3 = "666px";
        var res3 = +str3;
        console.log(res3); // NaN
        console.log(typeof res3);

        var flag = false;
        var res4 = +flag;
        console.log(res4); // 0
        console.log(typeof res4);

        var flag = true;
        var res4 = +flag;
        console.log(res4); // 1
        console.log(typeof res4);
  • 2.3 将被转换的数据传入 parseInt() 函数中 parseFloat() 函数中
    • Number()函数中无论混合字符串是否存在有效整数都会返回NaN

    • 利用 parseInt() 可以提取字符串中的有效整数

    • 利用 parseFloat() 可以提取字符串中的有效浮点数

    • parseFloat()会解析第一个. 遇到第二个.或者非数字结束

    • 两者之前的区别是parseInt()只能提取整数,parseFloat()可以提取小数

  • parseInt() 提取字符串中的 整数

    • 从第一位有效数字开始, 直到遇到无效数字
    • 如果第一位不是有效数字, 什么都提取不到, 会返回 NaN
    • 第一个参数是要转换的字符串,第二个参数是要转换的进制
    var str7 = "300px";
    var res9 = parseInt(str7);
    console.log(res9);        // 300

    var str8 = "300px250";
    var res10 = parseInt(str8);
    console.log(res10);       // 300

    console.log(parseInt("abc123"));  //返回NaN,如果第一个字符不是数字或者符号就返回NaN
    console.log(parseInt(""));        //空字符串返回NaN,Number("")返回0

  • parseFloat() 提取字符串中的 小数

    • 会解析第一个. 遇到第二个.或者非数字结束
    • 如果第一位不是有效数字, 什么都提取不到
    • 不支持第二个参数,只能解析10进制数
    • 如果解析的内容里只有整数,解析成整数
    var str9 = "20.5px";
    var res11 = parseInt(str9);
    console.log(res11); // 20

    var str10 = "20.5.5.5px";
    var res12 = parseFloat(str10);
    console.log(res12); // 20.5
  • 对 非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

3.将其它类型转换为布尔类型

  在JavaScript中,如果想将基本数据类型转换为布尔类型,那么只需要调用 Boolean(常量or变量)

        // 将其它类型转换为Number类型
        // 利用 Boolean() 函数
        console.log(Boolean(" "));      //true

        console.log(Boolean(""));       //false
        console.log(Boolean(0));
        console.log(Boolean(NaN));
        console.log(Boolean(undefined));
        console.log(Boolean(null));
  • 规律总结
    • 空字符 / 0 / NaN / undefined / null   会转换为 false
    • 其他  均会转换为  true

进制转换

  • 什么是进制?

    • 是一种计数的方式,数值的表示形式
  • 常见的进制

    • 十进制、二进制、八进制、十六进制
  • 进制数字进位方法

    • 十进制: 0、1、2、3、4、5、6、7、8、9   逢十进一
    • 二进制: 0、1   逢二进一
      • 书写形式:需要以 0b 或者 0B 开头,比如0b101
    • 八进制: 0、1、2、3、4、5、6、7 逢八进一
      • 书写形式:在前面加个 0,比如 045
    • 十六进制: 0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F   逢十六进一
      • 16进制就是逢16进1,但我们只有0~9这十个数字,所以我们用A,B,C,D,E,F这五个字母来分 别表示10,11,12,13,14,15。字母不区分大小写
      • 书写形式:在前面加个 0x 或者 0X,比如0x45
  • 数一下方块的个数, 并用十进制,二进制,八进制,十六进制表示

  • 十进制 转换 二进制
    • 规律:用需要转换的十进制/2取余然后倒叙
    9 / 2 = 4 = 1
    4 / 2 = 2 = 0
    2 / 2 = 1 = 0
    1 / 2 = 0 = 1
    结果1001

    var num = 0b1001;
    console.log(num);
    console.log(parseInt("1001", 2));
  • 二进制 转换 十进制
    • 规律:从低位数开始, 用低位 * 2的多少次幂, 幂数从0开始递增
    1001
    1 * 2(0) = 1
    0 * 2(1) = 0
    0 * 2(2) = 0
    1 * 2(3) = 8
    结果: 1 + 0 + 0 +8 = 9
  • 二进制 转换 八进制
    • 因为八进制逢八进一
    • 规律: 三个二进制位代表一个八进制位, 只需要从低位开始将三个二进制位转换为十进制再链接起来
           00 001 001
 结果:    0     1     1 = 011
 console.log(parseInt("011", 8));     // 9
  • 二进制 转换 十六进制
    • 因为十六进制逢十六进一
    • 规律:四个二进制位代表一个十六进制位, 只需要从低位开始将事个二进制位转换为十进制再链接起来
        0001 1011
  结果:    1      b = 0x1b
  console.log(parseInt("0x1b", 16));    // 27

八、运算符

1.运算符概述

(1).运算符基本概念

  • 1.什么是运算符
    • 运算符就是,告诉程序执行特定算术或逻辑操作的符号
    • 例如告诉程序, 某两个数相加, 相减,相乘等

  • 2.什么是表达式
    • 表达式就是利用运算符连接在一起的有意义,有结果的语句;
    • 例如: a + b; 就是一个算数表达式, 它的意义是将两个数相加, 两个数相加的结果就是表达式的结果
    • 注意: 表达式一定要有结果

(2).运算符分类

  • 按照功能划分

    • 算术运算符
    • 位运算符
    • 关系运算符
    • 逻辑运算符
  • 运算符根据参与运算的操作数的个数分为

    • 单目运算

      • 单目运算:只有一个操作数,如:i++  
    • 双目运算
      • 双目运算:有两个操作数,如: a+b;
    • 三目运算
      • 三目运算也称为问号表达式,如: a>b ? 1 : 0; 

(3).运算符的优先级和结合性

  • 优先级
    • JavaScript中,运算符的运算优先级共分为15 级。1 级最高,15 级最低。
    • 在表达式中,优先级较高的先于优先级较低的进行运算。
    • 在一个运算量两侧的运算符优先级相同时,则按运算符的结合性所规定的结合方向处理
运算符 描述
() 表达式分组
++ -- - ~ ! 一元运算符
* / % 乘法、除法、取模(求余)
+ - + 加法、减法、字符串连接
<< >> >>> 移位
< <= > >= 小于、小于等于、大于、大于等于
== != === !== 等于、不等于、严格相等、非严格相等
& 按位与
^ 按位异或
| 按位或
&& 逻辑与
|| 逻辑或
?: 条件
= += -= *= /= %= 赋值运算
, 多重求值
  • 结合性
    • 左结合性(自左至右)
      • 算术运算符的结合性是自左至右,即先左后右,如:x - y + z;
    • 右结合性(自右至左)
      • 最典型的右结合性运算符是:赋值运算符,如:x = y = z = 10;

2.算术运算符

  • (1).什么是算术运算符
    • +  -  *  /  %
  • (2).算术运算符的优先级和结合性
    •  * / %  优先级   高于  + -
    • 无论是 +  -  *  /  % 都是左结合性(从左至右计算
  • 名称 符号 说明
    加法运算符 + 对两个值进行加法运算,并将结果返回
    减法运算符 - 对两个值进行减法运算,并将结果返回
    乘法运算符 * 对两个值进行乘法运算,并将结果返回
    除法运算符 / 对两个值进行除法运算,并将结果返回
    求余运算符 (模运算符) % 对两个值进行取余运算,并将结果返回
  • 基本使用和C语言一模一样
        var res1 = 1 + 1;
        var res2 = 1 - 1;
        var res3 = 2 * 2;
        var res4 = 10 / 3;
        var res5 = 10 % 3;
        console.log(res1); // 1
        console.log(res2); // 0 
        console.log(res3); // 4
        console.log(res4); // 3.3333
        console.log(res5); // 1
    
  • 加法运算

    • 格式:Number1 + Number2;
      var res = 1 + 1;
      console.log(res); // 2
      
      var num1 = 10;
      var num2 = 20;
      var res2 = num1 + num2;
      console.log(res2); // 30
      
    • 非Number类型的值进行运算时,会将这些值转换为Number然后在运算
      var result = true + 1;
      console.log(result); // 2
      result = true + false;
      console.log(result); // 1
      result = 2 + null; 
      console.log(result);// 2
      
    • 任何值和NaN做运算都得NaN
      var result = 2 + NaN;
      console.log(result); //NaN
      
    • 任何的值和字符串做加法运算,都会先转换为字符串,然后再和字符串做拼串的操作
      var result = 1 + "123";
      console.log(result); // 1123
      result = 2 + "true";
      console.log(result); // 2true
      
  • 减法运算

    • 格式:Number1 - Number2;
      var res = 1 - 1;
      console.log(res); // 0
      
      var num1 = 10;
      var num2 = 20;
      var res2 = num1 - num2;
      console.log(res2); // -10
      
      • 非Number类型的值进行运算时,会将这些值转换为Number然后在运算
      var result = true - 1;
      console.log(result); // 0
      result = true - false;
      console.log(result); // 1
      result = 2 - null;
      console.log(result);// 2
      
    • 任何值和NaN做运算都得NaN
      var result = 2 - NaN;
      console.log(result); //NaN
      
    • 字符串做减法也会转换为Number
      var result = 2 - "1";
      console.log(result); // 1
      result = "2" - "1";
      console.log(result); // 1
      
  • 乘法运算

    • 格式:Number1 * Number2;
      var res = 2 * 2;
      console.log(res); // 4
      
      var num1 = 10;
      var num2 = 20;
      var res2 = num1 * num2;
      console.log(res2); // 200
      
      • 其它规律和减法一样
        • 非Number类型的值进行运算时,会将这些值转换为Number然后在运算
        • 任何值和NaN做运算都得NaN
        • 字符串做乘法也会转换为Number
  • 除法运算

    • 格式:Number1 / Number2;
      var res = 2 / 2;
      console.log(res); // 1
      
      var num1 = 10;
      var num2 = 20;
      var res2 = num1 / num2;
      console.log(res2); // 0.5
      
      • 其它规律和减法一样
        • 非Number类型的值进行运算时,会将这些值转换为Number然后在运算
        • 任何值和NaN做运算都得NaN
        • 字符串做除法也会转换为Number
  • 取余运算

    • 格式:Number1 % Number2;
    • m%n 求余,相当于m/n 获取余数
      var res = 10 % 3;
      console.log(res); // 1
      
      var num1 = 10;
      var num2 = 2.5;
      var res2 = num1 % num2;
      console.log(res2); // 0
      
      • 规律
        • n=0,返回NaN
        • m=0,结果为0
        • m>n,正常求余,如:8%3 = 2
        • m<n,结果是m,如:2%4 = 2
        var result = 10 % 0;
        console.log(result); // NaN
        result = 0 % 10;
        console.log(result); // 0
        result = 2 % 10;
        console.log(result); // 2
        
      • 其它规律和减法一样
        • 非Number类型的值进行运算时,会将这些值转换为Number然后在运算
        • 任何值和NaN做运算都得NaN
        • 字符串做取余也会转换为Number
  •     console.log(result); // 123
        result = true * 1;
        console.log(result); // 1
        result = null / 1;
        console.log(result); // 0
  • 总结
  • 3.1 加法运算的注意点
    • 1.任何非数值类型的数据在参与加法运算之前, 都会被自动的转换成数值类型之后, 再参与运算
    • 2.任何数据和NaN进行运算, 结果都是NaN
    • 3.任何数据和字符串相加, 都会被先转换成字符串之后再运算
  • 3.2 减法运算的注意点
    • 1.任何非数值类型的数据在参与减法运算之前, 都会被自动的转换成数值类型之后, 再参与运算
    • 2.任何数据和NaN进行运算, 结果都是NaN
    • 3.任何数据和字符串相减, 都会先把字符串转换成数值类型之后再运算
  • 3.3 乘法和除法运算的注意点
    • 减法运算的注意点一模一样
  • 3.4 取余运算注意点
    • 格式: m % n = 余数
    • 1.如果m>n, 那么就正常取余
    • 2.如果m<n, 那么结果就是m
    • 3.如果n=0, 那么结果就是NaN
    • 4.如果m=0,那么结果就是0
    • 5.取余运算结果的正负性, 取决于m而不是n

3.正负运算符

  • 正号
    • + 正号不会对数字产生任何影响
      var num = 123;
      num = +num;
      console.log(num); // 123
      
    • 对于非Number类型的值,会将先转换为Number,然后再运算
      var bool = true;
      var res = +bool;
      console.log(res); // 1
      
      var str = "123";
      res = +str;
      console.log(res); // 123
      
      var str2 = "123abc";
      res = +str2;
      console.log(res); // NaN, 所以内部不是调用parseInt, 而是Number()函数
      
      var temp = null;
      res = +temp;
      console.log(res); // 0
      
  • 负号
    • - 负号可以对数字进行负号的取反
      var num = 123;
      num = -num;
      console.log(num); // -123
      

4.赋值运算符

  • 1.什么是赋值运算符
    • 赋值运算符就是将等号右边的值存储到等号左边的变量中
  • 2.分类
    • 简单类型
      • =
    • 复杂类型
      • +=  -=   *=   /=   %=
  • 3.赋值运算符的优先级和结合性
    • 赋值运算符的优先级 低于 算数运算符
    • 赋值运算符的结合性是 右结合性(从右至左的计算)
    • 赋值运算符的左边 只能放变量, 不能放常量
  • 优先级 名称 符号 说明
    14 赋值运算符 = 双目运算符,具有右结合性
    14 除后赋值运算符 /= 双目运算符,具有右结合性
    14 乘后赋值运算符 (模运算符) *= 双目运算符,具有右结合性
    14 取模后赋值运算符 %= 双目运算符,具有右结合性
    14 加后赋值运算符 += 双目运算符,具有右结合性
    14 减后赋值运算符 -= 双目运算符,具有右结合性

5.自增自减运算符

  • (1).什么是自增自减运算符
    • 自增运算符:++
    • 自减运算符:--
  • (2).自增自减运算符的作用
    • 自增运算符: 可以快速的对一个变量中保存的数据进行 +1 操作
    • 自减运算符: 可以快速的对一个变量中保存的数据进行 -1 操作
  • (3).自增和自减写在变量的前面和后面的区别?
    • 写在变量的后面, 表示变量先参与其它的运算, 然后再自增或者自减
    • 写在变量的前面, 表示变量先自增或者自减, 然后再参与其它的运算
  • 优先级 名称 符号 说明
    2 自增运算符(在后) i++ 单目运算符,具有左结合性
    2 自增运算符(在前) ++i 单目运算符,具有右结合性
    2 自减运算符(在后) i-- 单目运算符,具有左结合性
    2 自减运算符(在前) --i 单目运算符,具有右结合性
  • (4).注意点
    • 自增自减运算符只能出现在变量的前面或者后面, 不能出现在常量或者表达式的前面或者后面
      • 表达式就是用运算符连接在一起有意义有结果的语句, 我们就称之为表达式
        • 1 + 1;  表达式
        • a * 5;  表达式
    • 在企业开发中自增自减运算符最好单独出现, 不要出现在表达式中

6.关系运算符

  • (1).为什么要学习关系运算符

    • 默认情况下,我们在程序中写的每一句正确代码都会被执行。但很多时候,我们想在某个条件成立的情况下才执行某一段代码
    • 这种情况的话可以使用条件语句来完成,但是学习条件语句之前,我们先来看一些更基础的知识:如何判断一个条件成不成立。
  • (2).JavaScript中的真假性

    • 在JavaScript中,条件成立称为“真”,条件不成立称为“假”,因此,判断条件是否成立就是判断条件的“真假”。
    • 在JavaScript已经给我们定义好了一个Boolean类型的值, 取值是true和false, true代表真, false代表假
    • 而接下来我们要学习的关系运算符它的返回值正好就是Boolean类型的值, 也就是说关系运算符的返回值要么是true,要么是false
  • (3).什么是关系运算符
    • 在程序中经常需要比较两个量的大小关系,以决定程序下一步的工作。比较两个量的运算符称为关系运算符。
      • >    <    >=    <=    ==    !=    ===    !==
  • (4).关系运算符的返回值
    • 只有两个, 要么是 true, 要么是 false
    • 如果关系成立, 就返回 true
    • 如果关系不成立, 就返回 false
  • (5).关系运算符的注意点
    • 对于非数值类型的数据, 会先转换成数值类型, 再进行判断
    • 对于关系运算符来说, 任何数据和NaN进行比较, 返回值都是 false
    • 如果参与比较的都是字符串类型, 那么不会转换成数值类型再比较, 而是直接比较字符对应的Unicode编码
    • 特殊比较的结果
  • 优先级 名称 符号 说明
    6 大于运算符 > 双目运算符,具有左结合性
    6 小于运算符 < 双目运算符,具有左结合性
    6 大于等于运算符 >= 双目运算符,具有左结合性
    6 小于等于运算符 <= 双目运算符,具有左结合性
    7 等于运算符 == 双目运算符,具有左结合性
    7 不等于运算符 != 双目运算符,具有左结合性
    7 恒等于运算符 === 双目运算符,具有左结合性
    7 不恒等于运算符 !== 双目运算符,具有左结合性
  • (6).示例
  •         // let res = 1 > true;   // let res = 1 > 1;
            // let res = 1 > false;  // let res = 1 > 0;
            // let res = 1 > null;   // let res = 1 > 0;
            // let res = 1 > "10";   // let res = 1 > 10;
    
            // let res = 666 > NaN;
            // let res = 666 > undefined;  // let res = 666 > NaN;
    
            // let res = "a" > "b";   // let res = 0061 > 0062;
            // let res = "b" > "a";   // let res = 0062 > 0061;
            // 如果字符串中有多个字符, 那么会从左至右的依次比较, 直到条件不满足为止
            // let res = "ab" > "ac";
    
            // let res = null == 0;          // false
            // let res = undefined == 0;     // false
            // let res = null == undefined;  // true
            // 在企业开发中千万不要通过==来判断某一个数据是否是NaN
            // 如果想判断某一个数据是否是NaN那么可以通过isNaN来进行判断
            // let res = NaN == NaN;        // false
  • (7).注意点
    • 对于非数值进行比较时,会将其转换为数字然后再比较
      console.log(1 > true);      //false
      console.log(1 > false);     //true
      console.log(1 > "0");       //true
      console.log(1 > null);      //true
      
    • 如果符号两侧的值都是字符串时,不会将其转换为数字进行比较, 而会分别比较字符串中字符的Unicode编码
    • // a的Unicode编码是:0061
      // b的Unicode编码是:0062
      console.log("a" < "b");    //true
      
      // 比较多位时则是从前往后一位一位比较
      // 第一位相同比较第二位, 直到比较到不一样或者结束为止
      // c的Unicode编码是:0063
      // 类似于C语言strcmp函数, 只不过JavaScript中比较的是Unicode编码
      console.log("ab" < "ac");  //true
      
    • nullundefinedNaN 比较
      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
      
      // undefined 衍生自 null, 所以返回true
      console.log(null == undefined);   // true;
      console.log(null === undefined);  // false;
      
      // == 判断值是否相等
      // == 会进行数据类型转换
      console.log("123" == 123);    // true
      // === 判断值和类型时候同时相等
      // === 不会进行数据类型转换
      console.log("123" === 123);   // false
      

7.逻辑运算符

  • (1).为什么要学习逻辑运算符
    • 有时候,我们需要在多个条件同时成立的时候才能执行某段代码
    • 比如:用户只有同时输入了QQ和密码,才能执行登录代码,如果只输入了QQ或者只输入了密码,就不能执行登录代码。这种情况下,我们就要借助于JavaScript提供的逻辑运算符。
  • (2).JavaScript中提供了三种逻辑运算符
  • 优先级 名称 符号 说明
    2 逻辑运算符 ! 单目运算符,具有右结合性
    11 逻辑运算符 && 双目运算符,具有左结合性
    12 逻辑运算符 || 双目运算符,具有左结合性

 

  • (3).什么是逻辑运算符
  • 逻辑与 &&
    • 格式:条件表达式A  &&  条件表达式B
    • 返回值:true false
    • 特点:一假则假
  • 逻辑或 ||
    • 格式:条件表达式A  ||  条件表达式B
    • 返回值:true false
    • 特点:一真则真
  • 逻辑非 !
    • 格式:! 条件表达式
    • 返回值: true false
    • 特点:真变假, 假变真
  • 逻辑
    • 格式:条件A  &&  条件B

    • 运算结果

      • 只有当条件A和条件B都成立时,结果才为true;其余情况的结果都为false。因此,条件A或条件B只要有一个不成立,结果都为false
      • 口诀:一假则假
    • 逻辑与运算过程
      • 总是先判断条件A是否成立
      • 如果条件A成立,接着再判断条件B是否成立:如果条件B成立,“条件A && 条件B”的结果就为true,如果条件B不成立,结果就为false
      • 如果条件A不成立,就不会再去判断条件B是否成立:因为条件A已经不成立了,不管条件B如何结果肯定是false
        //如果两个值都是true则返回true
        var result = true && true;
        
        //只要有一个false,就返回false
        result = true && false;
        result = false && true;
        result = false && false;
        
  • 逻辑与短路现象

      //第一个值为true,会检查第二个值
      true && alert("这哥们来了!!");
    
      //第一个值为false,不会检查第二个值
      false && alert("这哥们不会来了!!");
    
  • 注意点

    • 对于非Boolean类型的数值, 逻辑与会将其转换为Boolean类型来判断
    • 如果条件A不成立, 则返回条件A的数值本身
    • 如果条件A成立, 不管条件B成不成立都返回条件B数值本身
      var result =  "123" && "abc";
      console.log(result); // "abc"
      result =  "123" && 0;
      console.log(result); // 0
      result =  null && 0;
      console.log(result); // null
      
  • 逻辑
  • 格式:条件A  ||  条件B

  • 运算结果

    • 当条件A或条件B只要有一个成立时(也包括条件A和条件B都成立),结果就为true;只有当条件A和条件B都不成立时,结果才为false
    • 口诀:一真则真
  • 逻辑或运算过程

    • 总是先判断条件A是否成立
    • 如果条件A成立,就不会再去判断条件B是否成立:因为条件A已经成立了,不管条件B如何结果肯定是1,也就是true
    • 如果条件A不成立,接着再判断条件B是否成立:如果条件B成立,“条件A || 条件B”的结果就为true,如果条件B不成立,结果就为false
      //两个都是false,则返回false
      var result = false || false;
      
      //只有有一个true,就返回true
      result = true || false;
      result = false || true ;
      result = true || true ;
      
  • 逻辑或短路现象

      //第一个值为false,则会检查第二个值
      false || alert("123");
    
      //第一个值为true,则不再检查第二个值
      true || alert("123");
    
  • 注意点

    • 对于非Boolean类型的数值, 逻辑与会将其转换为Boolean类型来判断
    • 如果条件A不成立, 则不管条件B成不成立都返回条件B数值本身
    • 如果条件A成立, 则返回条件A的数值本身
      var  result =  null || 0;
      console.log(result); // 0
      
      result =  "123" || "abc";
      console.log(result); // "123"
      result =  "123" || 0;
      console.log(result); // "123"
      
  • 逻辑
  • 格式: ! 条件A

  • 运算结果:

    • 其实就是对条件A进行取反:若条件A成立,结果就为false;若条件A不成立,结果就为true。也就是说:真的变假,假的变真。
    • 口诀:真变假,假变真
      var bool1 = true;
      var res1 = !bool1;
      console.log(res1); // false
      
      var bool2 = false;
      var res2 = !bool2;
      console.log(res2); // true
      
  • 注意点

    • 对一个值进行两次取反,它不会变化
      var bool = true;
      var res = !!bool;
      console.log(res); // true
      
    • 对非布尔值进行元素,则会将其转换为布尔值,然后再取反
      var num = 10;
      var res = !num; // 先将10转换为true, 然后再取反
      console.log(res); // false
      
    • 所以, 要想将其它类型转换为Number类型除了Boolean()函数, 还可以使用 !!数值;
    • 值得注意的是!!数值;的形式,实现原理和Boolean()函数一样
  • 4.逻辑运算符的优先级和结合性 
    • 逻辑运算符的结合性是左结合性(从左至右的运算)
    • 在逻辑运算中 && 优先级高于 ||
  • 5.逻辑运算符的注意点
    • 5.1 在逻辑运算中如果不是布尔类型, 那么会先转换成布尔类型, 再参与其它的运算
    • 5.2 在逻辑与运算中, 如果参与运算的不是布尔类型, 返回值有一个特点
    • 格式: 条件A && 条件B
      • 如果条件A不成立, 那么就返回条件A
      • 如果条件A成立, 无论条件B是否成立, 都会返回条件B
    • 5.3 在逻辑或运算中, 如果参与运算的不是布尔类型, 返回值有一个特点
      • 格式:条件A || 条件B
        • 如果条件A成立, 那么就返回条件A
        • 如果条件A不成立, 无论条件B是否成立, 都会返回条件B
    • 5.4 在逻辑与运算中,有一个逻辑短路现象
      • 格式:条件A && 条件B
        • 由于逻辑与运算的规则是一假则假, 所以只要条件A是假, 那么条件B就不会运算
    • 5.5 在逻辑或运算中,有一个逻辑短路现象
      • 格式:条件A || 条件B
        • 由于逻辑或运算的规则是一真则真, 所以只要条件A是真, 那么条件B就不会运算

8.逗号运算符

  • (1).逗号运算符
    • 在JavaScript中逗号运算符一般用于简化代码
  • (2).逗号运算符优先级和结合性
    • 逗号运算符的结合性是左结合性(从左至右的运算)
    • 逗号运算符的优先级是所有运算符中最低的
  • (3).逗号运算符也是一个运算符, 所以也有运算符结果
    • 逗号运算符的运算符结果就是最后一个表达式的结果
  • (4).格式
  • 表达式1,表达式2,… …,表达式n;
    var a, b, c, d;
    /*
    1.先计算表达式1, a = 2
    2.再计算表达式2, b = 12
    3.再计算表达式3, c = 5
    4.将表达式3的结果返回给d
    */
    d = (a = 1 + 1,b = 3 * 4, c = 10 / 2);
    console.log(d); // 5
    

9.三目运算符

  • (1).什么是三目运算符
    • 三目运算符又称之为条件运算符
  • (2).三目运算符格式
    • 条件表达式 ?  A : B;
    • 在三目运算符中当条件为真的时候, 就会返回结果 A
    • 在三目运算符中当条件为假的时候, 就会返回结果 B
  • (3).三目运算符注意点
    • 在三目运算符中?:不能单独出现, 要么一起出现, 要么一起不出现
  • // 弹第一个
    true?alert("语句1") : alert("语句2");
    // 弹第二个
    false?alert("语句1") : alert("语句2");

九、流程控制

  • 流程控制包括 顺序结构、选择结构 和 循环结构

1.选择结构-if语句

(1).第一种形式

 
  • 1.1 格式
    • if(条件表达式){
          条件满足执行的语句;
      }
  • 1.2 特点
    • 条件表达式为真,就会执行{}中所有的代码, 并且只会执行一次

(2).第二种形式

 
  • 2.1 格式
if(条件表达式){
    条件成立执行的语句;
}else{
    条件不成立执行的语句;
}
  • 2.2 特点
    • 当条件满足,就执行if后面{}中的代码
    • 当条件不满足,就执行else后面{}中的代码
    • 并且两个{}只有一个会被执行, 并且只会被执行一次

(3).第三种形式

 
  • 3.1 格式
if(条件表达式A){
    条件A满足执行的语句;
}else if(条件表达式B){
    条件B满足执行的语句;
}
... ...
else{
    前面所有条件都不满足执行的语句;
}
  • 3.2 特点
    • 会从上至下的依次判断每一个条件表达式, 哪一个条件表达式满足, 就执行哪一个条件表达式后面{}中的代码
    • 如果前面所有的条件表达式都不满足, 就会执行else后面{}中的代码
    • 并且众多的大括号只有一个会被执行, 并且只会执行一次

(4).注意点

  • 4.1 对于非布尔类型的数据, 会先转换成布尔类型再判断
        if(1){
            console.log("语句A");
        }
        console.log("语句B");
  • 4.2 对于==或===的判断, 将常量写在前面
        let num = 10;
        // if(num = 5){
        if(5 == num){
            console.log("语句A");
        }
        console.log("语句B");
  • 4.3 if else 后面的大括号都可以省略, 但是省略之后只有紧随其后的语句受到控制
        if(false)
            console.log("语句A");
            console.log("语句B");
  • 4.4 在JavaScript中分号;也是一条语句(空语句)
        if(false);
        {
            console.log("语句A");
            console.log("语句B");
        }
  • 4.5 if选择结构可以嵌套使用
        if(true){
            if(false){
                console.log("语句A1");
            }else{
                console.log("语句B1");
            }
        }else{
            if(false){
                console.log("语句A2");
            }else{
                console.log("语句B2");
            }
        }
  • 4.6 当if选择结构省略大括号时, else if/else会自动和距离最近没有被使用的if匹配
        if(0)
            if(1)
                console.log("A");
            else
                console.log("B");
        else
            if (1)
                console.log("C");
            else
                console.log("D");
什么时候使用三目运算符,什么时候使用选择结构?
  • 在企业开发中, 如果条件满足之后只有一句代码需要执行, 那么就使用三目运算符
  • 在企业开发中, 如果条件满足之后有多句代码需要执行, 那么就使用选择结构

2. 选择结构-switch语句

(1).格式

switch(表达式){
    case 表达式A:
        语句A;
        break;
    case 表达式B:
        语句B;
        break;
    ... ...
    default:
        前面所有case都不匹配执行的代码;
        break;
}

(2).特点

  • 会从上至下的依次判断每一个case,是否和()中表达式的结果相等
  • 如果相等就执行对应case后面的代码
  • 如果前面所有的case都不匹配, 那么就会执行default后面的代码
  • 并且所有的case和default只有一个会被执行, 并且只会被执行一次

(3).注意点

  • 3.1 case判断的是===, 而不是==
     
        let num = 123;
        switch (num) {
            case "123":
                console.log("字符串123");
                break;
            case 123:
                console.log("数值的123");
                break;
            default:
                console.log("Other");
                break;
        }
  • 3.2 ()中可以是常量也可以是变量还可以是表达式
     
        // let num = 123;
        // switch (num) { // 变量
        // switch (123) { // 常量
        switch (122 + 1) { // 表达式
            case 123:
                console.log("数值的123");
                break;
            default:
                console.log("Other");
                break;
        }
  • 3.3 case后面可以是常量也可以是变量还可以是表达式
     
        let num = 123;
        switch (123) {
            // case 123: // 常量
            // case num: // 变量
            case 100 + 23: // 变量
                console.log("数值的123");
                break;
            default:
                console.log("Other");
                break;
        }
  • 3.4 break的作用是立即结束整个switch语句
     
        // 在switch语句中一旦case或者default被匹配, 那么其它的case和default都会失效
        /*
        let num = 1;
        switch (num) {
            case 1:
                console.log("1");
                break;
            case 2:
                console.log("2");
                break;
            default:
                console.log("Other");
                break;
        }
  • 3.5 default不一定要写在最后
     
        // switch中的default无论放到什么位置, 都会等到所有case都不匹配再执行
        /*
        let num = 7;
        switch (num) {
            // default:
            //     console.log("Other");
            //     break;
            case 1:
                console.log("1");
                break;
            default:
                console.log("Other");
                break;
            case 2:
                console.log("2");
                break;
        }
  • 3.6 和if/else中的else一样, default也可以省略
     
        let num = 7;
        switch (num) {
            case 1:
                console.log("1");
                break;
            case 2:
                console.log("2");
                break;
        }
  • 什么情况使用if,什么情况使用switch?
    • 在企业开发中如果是对区间进行判断, 那么建议使用if
    • 在企业开发中如果是对几个固定的值的判断, 那么建议使用switch
    • 总而言之,就是遵循原则: 能用 if 就用 if

3. 循环结构-while语句

(1).格式

while(条件表达式){
    条件满足执行的语句;
}

(2).特点

  • 只有条件表达式为真,才会执行后面{}中的代码
  • 大括号中的代码,有可能会被执行多次

(3).执行流程

  • 3.1 首先会判断条件表达式是否为真, 如果为真就执行后面{}中的代码
  • 3.2 执行完后面{}中的代码, 会再次判断条件表达式是否还为真
  • 3.3 如果条件表达式还为真, 那么会再次执行后面{}中的代码
  • 3.4 重复3.1~3.3, 直到条件表达式不为真为止

(5).循环结构书写规则

  • ​​​​​4.1 先写上循环结构的代码
  • 4.2 将需要重复执行的代码拷贝到{}中
  • 4.3 在()中指定循环的结束条件

(6).示例

        let num = 1;
        while(num <= 10){
            console.log("发射子弹" + num);
            num++;
        }

(7).注意点

  • 1.什么是死循环?
    • 条件表达式永远为真的循环结构我们称之为死循环
        while (true){
            console.log("123");
        }
  • 2.什么是循环体?
    • 循环结构后面的{},我们称之为循环体
  • 3. 和if一样对于非Boolean类型的值, 会先转换为Boolean类型再判断
        // while (null){
        // while (1){
        //     console.log("被执行了");
        // }
  • 4.while 后如果只有一条语句它可以省略大括号
    • 如果省略了后面的{}, 那么只有紧随其后的那条语句受到控制
        while (false)
            console.log("语句A");
        console.log("语句B");
  • 5.不能在()后面写分号;
        while (false);

        {
            console.log("语句A");
            console.log("语句B");
        }
  • 6.最简单死循环写法
    • while(1);

4. 循环结构-do while语句

(1).格式

do{
    需要重复执行的代码;
}while(条件表达式);

(2).特点

  • 无论条件表达式是否为真, 循环体都会被执行一次

(3).示例

        do{
            console.log("www.it666.com");
        }while (false);

(4).注意点

  • 与while语句相同

什么时候使用while,什么时候使用do while?

  • 1.在企业开发中大部分情况下while循环和dowhile循环是可以互换的
  • 2.在企业开发中如果循环体中的代码无论如何都需要先执行一次, 那么建议使用do while循环
  • 3.在企业开发中其它的情况都建议使用while循环

5.变量的作用域

  • (1).在JavaScript中定义变量有两种方式
    • ES6之前: var 变量名称;
    • ES6开始: let 变量名称;
  • (2).两种定义变量方式的区别
    • 如果通过var定义变量, 可以重复定义同名的变量, 并且不会报错, 并且后定义的会覆盖先定义的

    • 如果通过var定义变量, 可以先使用后定义(预解析)
    • 如果通过let定义变量, 不可以重复定义同名的变量
    • 如果通过let定义变量, 不可以先使用再定义, 因为浏览器不会对let定义的变量进行预解析
  • (3).什么是全局变量
    • 全局变量就是定义在{ }外面的变量, 我们就称之为全局变量
  • (4).什么是局部变量
    • 局部变量就是定义在{ }里面的变量, 我们就称之为局部变量
  • (5).全局变量和局部变量的区别
    • 如果是全局变量, 那么有效范围是从定义变量的那一行开始直到文件的末尾都可以使用
    • 如果是局部变量, 那么有效范围是从定义变量的那一行开始直到大括号结束为止(只有在大括号中才能使用)
        {
            // 如果是通过var定义的局部变量, 和全局变量一样, 后续都可以被使用
            // 如果是通过let定义的局部变量, 那么这个变量只能在当前定义变量的{}中使用
            // var num = 666; // 局部变量
            let num = 666; // 局部变量
            console.log(num);
        }
        console.log(num);
        if(true){
            console.log(num);
        }

6. 循环结构-for语句

  • (1).格式
        for(初始化表达式;条件表达式;循环后增量表达式){
            需要重复执行的代码;
        }
  • (2).特点
    • for循环的特点和while循环的特点一样, 只有条件表达式为真, 才会执行循环体
  • (3).执行流程
    • 3.1首先会执行初始化表达式, 并且只会执行一次
    • 3.2判断条件表达式是否为真, 如果条件表达式为真, 就执行循环体
    • 3.3执行完循环体就会执行循环后增量表达式
    • 3.4重复3.2~3.3, 直到条件表达式不为真为止
  • 写法1
        // 1.初始化表达式
        let num = 1;
        // 2. 条件表达式
        while (num <= 10){
            console.log("发射子弹" + num);
            // 3.循环后增量表达式
            num++;
        }
  • 写法2
        for(let num = 1;num <= 10;num++){
            console.log("发射子弹" + num);
        }
  • (4).注意点
  • 4.1.在for循环中"初始化表达式""条件表达式""循环后增量表达式"都可以不写
    • 如果不写就相当于while(1);
        for(;;){      //for循环是可以省略条件表达式的, 默认就是真
            console.log("123");
        }
  • 4.2.其它注意点和while循环一样

什么时候使用while,什么时候使用for?

  • 1.如果是while循环, 在循环结束之后还可以使用用于控制循环结束的变量 
  • 2.如果是for循环, 在循环结束之后可以让外界使用, 也可以不让外界使用
  • 3.在企业开发中由于for循环比while循环要灵活, 所以能用for循环就用for循环

7. 关键字break

  • (1).什么是break关键字?
    • break关键字可以用于switch语句和循环结构中
    • 在switch语句中,break关键字的作用是,立即结束当前的switch语句
    • 在循环结构中,break关键字的作用是,立即结束当前的循环结构
  • (2).break关键字的注意点
    • 2.1 break关键字后面不能编写任何的语句, 因为永远执行不到
    • ​
              let num = 123;
              switch (num) {
                  case 123:
                      console.log("123");
                      break;
                      console.log("break后面的语句"); // 永远执行够不到
                  case 124:
                      console.log("124");
                      break;
                  default:
                      console.log("other");
                      break;
              }

       

    • 2.2 如果在循环嵌套的结构中, break结束的是当前所在的循环结构
    •         for(let i = 0; i < 5; i++){
                  console.log("外面的循环结构" + i);
                  for(let j = 0; j < 5; j++){
                      console.log("里面的循环结构-----" + j);
                      break;
                  }
              }

 

8.关键字continue

  • (1).什么是continue关键字?
    • continue关键字只能用于循环结构
    • 在循环结构中continue关键字的作用是跳过本次循环, 进入下一次循环
  • (2).注意点
    • 2.1 continue关键字后面和break一样, 不能编写其它的代码, 因为执行够不到
    • 2.2 和break一样, 如果continue出现在循环嵌套结构中, 只会跳过当前所在的循环

9.循环嵌套

  • 需求1: 要求往界面中打印****
     
        // 打印一行
        // document.write("****");
        /*
        document.write("*");
        document.write("*");
        document.write("*");
        document.write("*");
        */
     
        for(let i = 0; i < 4; i++){
            document.write("*");
        }
  • 需求2: 要求往界面中打印三行 ****
     
        // 打印三行
        /*
        for(let i = 0; i < 4; i++){
            document.write("*");
        }
        document.write("<br>");
        for(let i = 0; i < 4; i++){
            document.write("*");
        }
        document.write("<br>");
        for(let i = 0; i < 4; i++){
            document.write("*");
        }
        */
        for(let j = 0; j < 3; j++){
            for(let i = 0; i < 4; i++){
                document.write("*");
            }
            document.write("<br>");
        }
  • 规律:在循环嵌套中外循环控制的是行数, 内循环控制的是列数

十、数组

1.数组的基本操作

  • (1).什么是数组?
    • 数组,就是专门用于存储一组数据的
    • 注意: 和我们前面学习的 Number / String / Boolean / Null / undefined不同(基本数据类型),而我们今天学习的数组(Array)不是基本数据类型, 是引用数据类型(对象类型)
  • (2).如何创建一个数组?
    • let 变量名称 = new Array(size);
  • (3).如何操作数据
    • 3.1 如何往数组中存储数据: 变量名称[索引号] = 需要存储的数据;
    • 3.2 如何从数组中获取存储的数据: 变量名称[索引号];
        /*
        // 需求: 要求定义变量保存一个人的姓名
        // 需求: 要求定义变量保存一个班级所有人的姓名
        let name1 = "lnj";
        let name2 = "ls";
        let name3 = "ww";
        */

        let arr = new Array(3);
        console.log(arr);
        arr[0] = "lnj";
        arr[1] = "ls";
        arr[2] = "ww";
        console.log(arr);
        console.log(arr[0]);
  • (4).注意点
  • 1.和其它编程语言不同, 如果数组对应的索引中没有存储数据, 默认存储的就是undefined(其它编程语言中,默认保存的是垃圾数据或者0)
        let arr = new Array(3);
        console.log(arr[0]);
        console.log(arr[1]);
        console.log(arr[2]);
  • 2.和其它编程语言不同, JavaScript中访问了数组中不存在的索引不会报错, 会返回undefined(其它编程语言,一旦超出索引范围就会报错或者返回脏数据)
        let arr = new Array(3); // 0 1 2
        console.log(arr[666]);
  • 3.和其它编程语言不同, 当JavaScript中数组的存储空间不够时数组会自动扩容(其它编程语言中,数组的大小是固定的)
        let arr = new Array(3);
        arr[0] = "lnj";
        arr[1] = "zs";
        arr[2] = "ls";
        arr[3] = "it666";
        console.log(arr);
  • 4.和其它编程语言不同, JavaScript的数组可以存储不同类型数据(在其它编程语言中,数组只能存储相同类型数据(要么全部都是字符串, 要么全部都是数值等)
        let arr = new Array(4);
        arr[0] = 123;
        arr[1] = "123";
        arr[2] = true;
        arr[3] = null;
        console.log(arr);
  • 5.和其它编程语言不同, JavaScript中数组分配的存储空间不一定是连续的(其它语言数组分配的存储空间都是连续的, JavaScript数组是采用"哈希映射"方式分配存储空间)
    • 什么是哈希映射? 好比字典可以通过偏旁部首找到对应汉字, 我们可以通过索引找到对应空间
    • 在浏览器中各大浏览器也对数组分配存储空间进行了优化 
      • 如果存储的都是相同类型的数据, 那么会尽量分配连续的存储空间
      • 如果存储的不是相同的数据类型, 那么就不会分配连续的存储空间
  • (5).创建数组的其它方式
    • 6.1 通过构造函数创建数组
      • let 变量名称 = new Array(size);                        # 创建一个指定大小数组
      • let 变量名称 = new Array();                              # 创建一个空数组
      • let 变量名称 = new Array(data1, data2, ...);     # 创建一个带数据的数组
    let arr1 = Array();
    let arr2 = Array(3);   // 指定长度
    let arr3 = Array("hi", 12, false);
    console.log(arr3);
  • 6.2 通过字面量创建数组
    • let 变量名称 = [ ];                                             # 创建一个空数组
    • let 变量名称 = [data1, data2, ...];                     # 创建一个带数据的数组
    let arr4 = [];
    let arr5 = ["hello", 66, null];
    console.log(arr5);
 
        /*
        // let arr = new Array(3);
        // let arr = new Array();
        // arr[0] = "lnj";
        // arr[1] = "zs";
        // arr[2] = "ls";
        // arr[3] = "ww";
        // console.log(arr);

        let arr = new Array("lnj", "zs", "ls", "ww");
        console.log(arr);
        */

        // let arr = [];
        // arr[0] = "lnj";
        // arr[1] = "zs";
        // arr[2] = "ls";
        // arr[3] = "ww";
        // console.log(arr);

最简单的写法:

        let arr = ["lnj", "zs", "ls", "ww"];
        console.log(arr);
  • (6).数组的遍历
    • 数组的遍历,就是依次取出数组中存储的所有数据, 我们就称之为数组的遍历
    • length:数组的长度
        let arr = ["a", "b", "c"];
        // console.log(arr[0]);
        // console.log(arr[1]);
        // console.log(arr[2]);

        arr[3] = "d";
        arr[4] = "f";

        // console.log(arr.length);

        for(let i = 0; i < arr.length; i++){
            console.log(arr[i]);
        }

2.数组的解构赋值

  • (1).什么是数组的解构赋值?
    • 解构赋值是ES6中新增的一种赋值方式
        let arr = [1, 3, 5];
        // let a = arr[0];
        // let b = arr[1];
        // let c = arr[2];

        // 解构赋值
        let [a, b, c] = arr;

        console.log("a = " + a);
        console.log("b = " + b);
        console.log("c = " + c);
  • (2).注意点
  • 2.1 在数组的解构赋值中, 等号左边的格式必须和等号右边的格式一模一样, 才能完全解构
        // let [a, b, c] = [1, 3, 5];
        // let [a, b, c] = [1, 3, [2, 4]];
        let [a, b, [c, d]] = [1, 3, [2, 4]];
        console.log("a = " + a);
        console.log("b = " + b);
        console.log("c = " + c);
        console.log("d = " + d);
  • 2.2 在数组的解构赋值中, 左边的个数可以和右边的个数不一样
        let [a, b] = [1, 3, 5];
        console.log("a = " + a);
        console.log("b = " + b);

  • 2.3 在数组的解构赋值中, 右边的个数可以和左边的个数不一样
        let [a, b, c] = [1];
        console.log("a = " + a);
        console.log("b = " + b);
        console.log("c = " + c);

  • 2.4 在数组的解构赋值中,如果右边的个数和左边的个数不一样, 那么我们可以给左边指定默认值
        let [a, b = 666, c = 888] = [1];
        console.log("a = " + a);
        console.log("b = " + b);
        console.log("c = " + c);

  • 2.5 在数组的解构赋值中, 如果左边的个数和右边的个数一样, 那么如果设置默认值会被覆盖
        let [a, b = 666] = [1, 3, 5];
        console.log("a = " + a);
        console.log("b = " + b);

  • 2.6 在数组的解构赋值中, 还可以使用ES6中新增的扩展运算符来打包剩余的数据
    • ES6中新增的扩展运算符...
  • 2.7 在数组的解构赋值中, 如果使用了扩展运算符, 那么扩展运算符只能写在最后
        // let [a, b] = [1, 3, 5];
        // let [a, ...b] = [1, 3, 5];
        let [a, ...b] = [1, 3, 5];
        console.log("a = " + a);
        console.log(b);

3.数组的增删改查

  • (1).数组的查看
    • 获取数组中索引为1的那个数据
        let arr = ["a", "b", "c"];        
        console.log(arr[1]);
  • (2).数组的修改
    • 2.1 将索引为1的数据修改为m
        arr[1] = "m";
        console.log(arr);
  • 2.2 将索引为1的数据修改为d, 索引为2的修改为e
  • 方法1
        arr[1] = "d";
        arr[2] = "e";
        console.log(arr);
  • 方法2:利用 splice 函数,实现数组的 修改
    • 参数1:从什么位置开始
    • 参数2:需要替换多少个元素
    • 参数3开始:新的内容
        arr.splice(1, 2, "d", "e");
        console.log(arr);

(3).数组的新增

  • 3.1 在数组最后添加一条数据
  • 方法1:
        arr[3] = "d";
        console.log(arr);
  • 方法2:利用 push 函数,实现 在数组最后 添加 一条或多条 数据
        // push方法可以在数组的最后新增一条数据, 并且会将新增内容之后数组当前的长度返回给我们
        let res = arr.push("d");
        console.log(res);
        console.log(arr);
  • 3.2 在数组最后添加两条数据
        // arr.push("d");
        // arr.push("e");
        // 数组的push方法可以接收1个或多个参数
        arr.push("d", "e");
        console.log(arr);
  • 3.3 在数组最前面添加一条数据
  • 利用 unshift 函数,实现 在数组最前面 添加一条或多条 数据
        // unshift方法和push方法一样, 会将新增内容之后当前数组的长度返回给我们
        let res = arr.unshift("m");
        console.log(res);
        console.log(arr);
  • 3.4 在数组最前面添加两条数据
        // arr.unshift("m");
        // arr.unshift("w");
        // unshift方法和push方法一样, 可以接收1个或多个参数
        arr.unshift("m", "w");
        console.log(arr);

(4).数组的删除

  • 4.1 删除数组最后一条数据
  • 利用 pop 函数,实现 删除 数组中的 最后 一条数据
        // 数组的pop方法可以删除数组中的最后一条数据,  并且将删除的数据返回给我们
        let res = arr.pop();
        console.log(res);
        console.log(arr);
  • 4.2 删除数组最前面一条数据
  • 利用 shift 函数,实现 删除 数组中的 最前面 一条数据
        // 数组的shift方法可以删除数组中的最前面一条数据,  并且将删除的数据返回给我们
        let res = arr.shift();
        console.log(res);
        console.log(arr);
  • 4.3 删除数组中索引为1的数据
  • 利用 splice 函数,实现 删除 指定数组位置与数组个数的 数据
    • 参数1:从什么位置开始
    • 参数2:需要删除多少个元素
        // 从索引为1的元素开始删除1条数据
        arr.splice(1, 1);
        console.log(arr);
  • 4.4 删除数组中除了第0条以外的所有数据
        arr.splice(1, 2);
        console.log(arr);

总结

 

函数 功能
push 在数组 最后 添加 一条或多条 数据
unshift 在数组 最前面 添加一条或多条 数据
pop 删除 数组中的 最后 一条数据
shift 删除 数组中的 最前面 一条数据
splice 

删除指定数组位置与数组个数的 数据

        参数1:从什么位置开始

        参数2:需要删除多少个元素

修改数组

        参数1:从什么位置开始

        参数2:需要替换多少个元素

        参数3开始:新的内容

4.数组的常用方法

  • (1).如何清空数组
    • 1.1 直接重新定义为空数组
    • 1.2 将数组的长度设置为0
    • 1.3 利用 splice 函数实现
        let arr = [1, 2, 3, 4, 5];
        arr = [];
        arr.length = 0;
        arr.splice(0, arr.length);
        console.log(arr);
  • (2).如何将数组转换为字符串
    • 利用 toString 函数
        let str = arr.toString();
        console.log(str);
        console.log(typeof str);
  • (3).如何将数组转换成指定格式字符串
    • 利用 join 函数,它还可以指定连接符号
        // join方法默认情况下如果没有传递参数, 就是调用toString();
        // join方法如果传递了参数, 就会将传递的参数作为元素和元素的连接符号
        let str =  arr.join("+");
        console.log(str);
        console.log(typeof str);
  • (4).如何将两个数组拼接为一个数组
    • 4.1 利用 concat 函数 合并两个数组
    • 4.2 利用 扩展运算符 ... ) 将数组中的所有数据解开,再放到所在位置(不会修改原有数组)
        let arr1 = [1, 3, 5];
        let arr2 = [2, 4, 6];
        // 注意点: 数组不能够使用加号进行拼接, 如果使用加号进行拼接会先转换成字符串再拼接
        // let res = arr1 + arr2;
        let res = arr1.concat(arr2);
        // 注意点: 扩展运算符在解构赋值中(等号的左边)表示将剩余的数据打包成一个新的数组
        //         扩展运算符在等号右边, 那么表示将数组中所有的数据解开, 放到所在的位置
        let res = [...arr1, ...arr2]; // let res = [1, 3, 5, 2, 4, 6];
        console.log(res);
        console.log(typeof res);
        // 注意点: 不会修改原有的数组, 会生成一个新的数组返回给我们
        console.log(arr1);
        console.log(arr2);
  • (5).如何对数组中的内容进行反转( [1, 2, 3, 4, 5]   --->  [5, 4, 3, 2, 1] )
    • 利用 reverse 函数,实现数组的反转(会修改原有数组)
        let res = arr.reverse();
        console.log(res);
        // 注意点: 会修改原有的数组
        console.log(arr);
  • (6).如何截取数组中指定范围内容
    • 利用 slice 函数,实现截取指定范围的数组内容(左闭右开)
        //  0  1  2  3
        // [1, 2, 3, 4, 5]
        // slice方法是包头不包尾(包含起始位置, 不包含结束的位置)
        let res = arr.slice(1, 3)
        console.log(res);
        console.log(arr);
  • (7).如何查找元素在数组中的位置
    • 利用 indexOf 函数
      • 如果找到了指定的元素, 就会返回元素对应的位置
      • 如果没有找到指定的元素, 就会返回 -1
      • 默认是从左至右的查找, 一旦找到就会立即停止查找
        • 参数1:需要查找的元素
        • 参数2:从什么位置开始查找(此参数可以不写)
    • 利用 lastindexOf 函数
      • 如果找到了指定的元素, 就会返回元素对应的位置
      • 如果没有找到指定的元素, 就会返回 -1
      • 默认是从右至左的查找, 一旦找到就会立即停止查找
        • 参数1:需要查找的元素
        • 参数2:从什么位置开始查找(此参数可以不写)
        //  0  1  2  3  4
        // [1, 2, 3, 4, 5]
        //         0  1  2  3  4  5
        let arr = [1, 2, 3, 4, 5, 3];
        // 注意点: indexOf方法默认是从左至右的查找, 一旦找到就会立即停止查找
        let res = arr.indexOf(3);
        let res = arr.indexOf(6);
        // 参数1: 需要查找的元素
        // 参数2: 从什么位置开始查找
        let res = arr.indexOf(3, 4);
        // 注意点: lastIndexOf方法默认是从右至左的查找, 一旦找到就会立即停止查找
        let res = arr.lastIndexOf(3);
        let res = arr.lastIndexOf(3, 4);
        console.log(res);
  • (8).如何判断数组中是否包含某个元素
    • 8.1 判断返回值是否为-1
      • 利用 indexOf 函数
      • 利用 lastindexOf 函数
    • 8.2 利用 includes 函数 来判断
        let arr = [1, 2, 3, 4, 5];
        // 我们可以通过indexOf和lastIndexOf的结果, 判断是否是-1即可
        let res = arr.indexOf(8);
        let res = arr.lastIndexOf(8);
        // 也可以利用includes函数来判断
        let res = arr.includes(8);
        let res = arr.includes(4);
        console.log(res);

5.数组的排序

(1).索引法 0-9

  • 从接盘接收5个0~9的数字, 排序后输出
  • 方法1
        // 方法1:因为数组的索引0-9输出时肯定都顺序输出
        // 缺陷:如果用户输入的有重复的数字,排序输出时只会输出一个(覆盖)
        // 比如:用户输入2,6,3,8,3  该方法会返回:2,3,6,8
        let strs = prompt("请输入5个0-9的数字:");
        let ary1 = strs.split(",");
        // 假设用户输入的是 1,3,0,9,5
        //   0       1          2          3           4         5       6          7          8       9
        // [666,    666,    undefined,    666,     undefined,   666, undefined, undefined, undefined, 666]
        let ary2 = new Array(10);
        let i;
        for(i=0;i<ary1.length;i++){
            let str = ary1[i];
            ary2[str] = 666;
        }
        for(i=0;i<ary2.length;i++){
            if(ary2[i]===666){
                console.log(i);
            }
        }
  • 方法2
        // 方法2:解决方法1中出现重复数据的bug
        // 但是该方法仍旧存在很大的问题:无法比较除0-9以外的数据,比如-5,88等
        let strs = prompt("请输入5个0-9的数字:");
        let ary1 = strs.split(",");
        // 假设用户输入的是 2,6,3,8,3
        //  0  1     2     3    4  5    6    7    8    9
        // [0, 0, 0+1=1, 0+1=1, 0, 0, 0+1=1, 0, 0+1=1, 0]
        //               1+1=2
        // [0, 0, 1, 2, 0, 0, 1, 0, 1, 0]
        let ary2 = new Array(10);
        ary2.fill(0);
        // console.log(ary2);
        let i;
        for(i=0;i<ary1.length;i++){
            let str = ary1[i];
            ary2[str] += 1;
        }
        // console.log(ary2);
        let j;
        for(i=0;i<ary2.length;i++){
            for(j=0;j<ary2[i];j++){
                // 此时3一定会输出2次
                // 第一次 0<2 成立
                // 第二次 1<2 成立
                // 第三次 2<2 不成立!
                // console.log(i);
                document.write(i);
            }
        }

(2).选择排序

  • 分析

        // 索引:       0  1   2  3
        // 比如用户输入:4, 3, -5, 1

        // 0, [1,2,3]
        // 1, [2,3]
        // 2, [3]    如果第2个数大于第3个数,则需要交换两个数的位置
        // 类似于
        //      ***
        //      **
        //      *
  • 代码
        let strs = prompt("请输入4个数:");
        let ary = strs.split(",");

        for(let i=0;i<3;i++){
            for(let j=i;j<3;j++){
                // document.write(i,j+1);
                if(ary[i]>ary[j+1]){
                    //交换两个数组的值
                    let tmp = ary[i];
                    ary[i] = ary[j+1];
                    ary[j+1] = tmp;
                }
            }
        }
        console.log(ary);

(3).冒泡排序

  • 分析

        // 索引:       0  1   2  3
        // 比如用户输入:4, 3, -5, 1

        // [0,1] [1,2] [2,3]
        // [0,1] [1,2]
        // [0,1] 如果第1个数大于第2个数,则需要交换两个数的位置
        // 类似于
        //      *
        //      **
        //      ***
  • 代码
        let strs = prompt("请输入4个数:");
        let ary = strs.split(",");

        for(let i=0;i<3;i++) {
            for (let j=0; j<3-i; j++) {
                // console.log(j,j+1);
                if(ary[j]>ary[j+1]){
                    //交换两个数组的值
                    let tmp = ary[j];
                    ary[j] = ary[j+1];
                    ary[j+1] = tmp;
                }
            }
        }
        console.log(ary);

6.二维数组

  • (1).什么是二维数组?
    • 二维数组就是数组的每一个元素又是一个数组, 我们就称之为二维数组
        let arr = [1, 3, 5];           // 一维数组
        let arr = [[1, 3], [2, 4]];    // 二维数组
  • (2).如何操作二维数组?
    • 2.1 如何从二维数组中获取数据
      • 数组名称[二维数组索引];                                    # 得到一个一维数组
      • 数组名称[二维数组索引][一维数组索引];            # 得到一维数组中的元素
        let arr = [[1, 3], [2, 4]];

        let arr1 = arr[0];
        console.log(arr1);

        let ele = arr[0][1];
        console.log(ele);
  • 2.2 如何往二维数组中存储数据
    • 数组名称[二维数组索引] = 一维数组;
    • 数组名称[二维数组索引][一维数组索引] = 值;
        // 注意点: 在定义二维数组的时候, 将来需要存储多少个一维数组, 就写上多少个[]即可
        let arr = [[],[]];
        arr[0] = [1, 3];
        arr[1][0] = 2;
        console.log(arr);
  • (3). 二维数组的遍历
    • 两个for循环嵌套即可
        let arr = [[1, 3], [2, 4]];
        for(let i = 0; i < arr.length; i++){
            let subArray = arr[i];
            // console.log(subArray);
            for(let j = 0; j < subArray.length; j++){
                console.log(subArray[j]);
            }
        }

十一、函数

1.函数的基本概念

  • (1).什么是函数?
    • 函数是专门用于封装代码的, 函数是一段可以随时被重复执行的代码块
  • (2).格式
        function 函数名称(形参列表){

            被封装的代码;

        }
  • (3).不使用函数的弊端
    • 3.1 冗余代码太多
    • 3.2 需求变更, 需要修改很多的代码
  • (4).使用函数的好处
    • 4.1 冗余代码变少了
    • 4.2 需求变更, 需要修改的代码变少了
  • (5).函数的定义步骤
    • 1.1 书写函数的固定格式
    • 1.2 给函数起一个有意义的名称
      • 为了提升代码的阅读性
      • 函数名称也是标识符的一种, 所以也需要遵守标识符的命名规则和规范
    • 1.3 确定函数的形参列表
      • 看看使用函数的时候, 是否需要传入一些辅助的数据
    • 1.4 将需要封装的代码拷贝到 { } 中
    • 1.5 确定函数的返回值
      • 可以通过 return 数据;  的格式, 将函数中的计算结果返回给函数的调用者
  • (6).示例
    • 需求:计算两个变量的和
        let num1 = 10;
        let num2 = 20;
        let res = num1 + num2;
        console.log(res);

        let value1 = 30;
        let value2 = 20;
        let res2 = value1 +value2;
        console.log(res2);
        // 定义函数
        function getSum(a, b){      // a = num1, b = num2;
            let res = a + b;        // let res = 10 + 20; let res = 30;
            // 将res返回给函数的调用者
            return res;
        }
       
        let num1 = 10;
        let num2 = 20;
        // 调用函数
        let result = getSum(num1, num2);   // let result = res; let result = 30;
        console.log(result);

2.函数的注意点

  • (1).一个函数可以有形参也可以没有 形参(零个或多个)
    • 什么是形参? 定义函数时 函数( ) 中的变量我们就称之为 形参
        // 没有形参的函数
        // function say() {
        //     console.log("hello world");
        // }
        // say();

        // 有形参的函数
        function say(name) {
            console.log("hello " + name);
        }
        say("lnj");
  •  (2).一个函数可以有返回值也可以没有 返回值
        // 没有返回值的函数
        // function say() {
        //     console.log("hello  world");
        // }
        // say();

        // 有返回值的函数
        function getSum(a, b) {
            return a + b;
        }
        let res = getSum(10 , 20);
        console.log(res);
  • (3).函数没有通过 return 明确返回值, 默认返回 undefined
        function say() {
            console.log("hello world");
            return; // undefined
        }
        let res = say();
        console.log(res);
  • (4).return的作用和break相似, 所以 return 后面不能编写任何语句(永远执行不到)
    • break:立即结束switch语句或者循环语句
    • return:立即结束当前所在函数
  • (5).调用函数时实参的个数和形参的个数可以不相同
    • 什么是实参? 调用函数时传入的数据我们就称之为 实参
    • 什么是形参? 定义函数时 函数( ) 中的变量我们就称之为 形参
        function getSum(a, b) {
            console.log(a, b);
            return a + b;
        }
        // let res = getSum(10, 20); // 这里的10和20就是实参
        // let num1 = 10;
        // let num2 = 20;
        // let res = getSum(num1, num2); // 这里的num1和num2也是实参
        // console.log(res);
        // let res = getSum(10);
        let res = getSum(10, 20, 30);
  • (6).JavaScript中的函数和数组一样, 都是引用数据类型(对象类型)
    • 既然是一种数据类型, 所以也可以保存到一个变量中
    • 可以将一个函数保存到一个变量中
    • 可以通过变量名称找到函数并执行函数
        let say = function () {
            console.log("hello world");
        }
        say();
  • (7).如何给 形参 设置默认值
        function getSum(a, b) {
            // 在ES6之前可以通过逻辑运算符来给形参指定默认值
            // 格式: 条件A || 条件B
            // 如果条件A成立, 那么就返回条件A
            // 如果条件A不成立, 无论条件B是否成立, 都会返回条件B
            a = a || "指趣学院";
            b = b || "知播渔教育";
            console.log(a, b);
        }
        getSum(123, "abc");
        // 从ES6开始, 可以直接在形参后面通过=指定默认值
        // 注意点: ES6开始的默认值还可以从其它的函数中获取
        // function getSum(a = "指趣学院", b = "知播渔教育") {
        function getSum(a = "指趣学院", b = getDefault()) {
            console.log(a, b);
        }
        getSum();
        // getSum(123, "abc");
        function getDefault() {
            return "李南江"
        }
  • (8).函数可以作为参数与返回值
    • 将函数作为其他函数的参数
        let say = function () {
            console.log("hello world");
        }
        // say();
        // let fn = say;
        // fn();
        // 将函数作为其他函数的参数
        function test(fn) {         // let fn = say;
            fn();
        }
        test(say);
  • 将函数作为其他函数的返回值
        // 将函数作为其他函数的返回值
        function test() {
            // 注意点: 在其它编程语言中函数是不可以嵌套定义的,
            // 但是在JavaScript中函数是可以嵌套定义的
            let say = function () {
                console.log("hello world");
            }
            return say;
        }
        let fn = test();    // let fn = say;
        fn();

3.函数中的 arguments 与 扩展运算符(...)

(1). arguments

  • 1.1 log 也是一个函数
    • 因为console.log();也是通过()来调用的, 所以log也是一个函数
        // console.log();
        function say() {
            console.log("hello world");
        }
        window.say();
  • 1.2 log函数的特点
    • 可以接收1个或多个参数
        console.log(1);
        console.log(1, 2);
        console.log(1, 2, 3);
  • 1.3 为什么log函数可以接收 1个或多个参数
    • 内部的实现原理就用到了arguments
  • 1.4 arguments的作用
    • 保存传递给函数的所有实参
        // 需求:求任意个变量的和
        function getSum() {
            // 注意点: 每个函数中都有一个叫做arguments的东东
            // arguments其实是一个伪数组
            // console.log(arguments);
            // console.log(arguments[0]);
            // console.log(arguments[1]);
            // console.log(arguments[2]);

            let sum = 0;
            for (let i = 0; i < arguments.length; i++){
                let num = arguments[i];
                // console.log(num);     // 10 20 30
                sum += num; 
            }
            return sum;
        }
        let res = getSum(10, 20, 30);
        console.log(res);

(2).扩展运算符

  • 2.1 扩展运算符( ... )在等号左边, 将剩余的数据打包到一个新的数组中
    • 注意点:只能写在最后
let [a, ...b] = [1, 3, 5];    // a = 1; b = [3, 5];
  • 2.2 扩展运算符在等号右边, 将数组中的数据解开
        let arr1 = [1, 3, 5];
        let arr2 = [2, 4, 6];
        let arr = [...arr1, ...arr2];     // let arr = [1, 3, 5, 2, 4, 6];
  • 2.3 扩展运算符在函数的形参列表中的作用
    • 将传递给函数的所有实参打包到一个数组中
    • 注意点:和在等号左边一样, 也只能写在形参列表的最后
        function getSum(...values) {
            // console.log(values);
            let sum = 0;
            for (let i = 0; i < values.length; i++){
                let num = values[i];
                sum += num;
            }
            return sum;
        }
        let res = getSum(10, 20, 30, 40);
        console.log(res);
        function getSum(a, ...values) {
            console.log(a);
            console.log(values);
        }
        getSum(10, 20 , 30);

4.三个特殊函数

(1).匿名函数

  • 1.什么是匿名函数?
    • 匿名函数:就是没有名称的函数
        // 有名称的函数
        // function say() {
        //     console.log("hello lnj");
        // }
        // let say = function() {
        //     console.log("hello lnj");
        // }

        // 匿名函数
        function() {
            console.log("hello lnj");
        }
  • 2.匿名函数的注意点
    • 匿名函数:不能只定义不使用
  • 3.匿名函数的应用场景
    • 3.1 作为其他函数的参数
        function test(fn) {    // let fn = say;
            fn();
        }
        test(function () {
            console.log("hello world");
        });
  • 3.2 作为其他函数的返回值
        function test() {
            return function () {
                console.log("hello lnj");
            };
        }
        let fn = test();    // let fn = say;
        fn();
  • 3.3 作为一个立即执行的函数
        // 注意点: 如果想让匿名函数立即执行, 那么必须使用()将函数的定义包裹起来才可以
        (function () {
            console.log("hello it666");
        })();

(2).箭头函数

  • 1.什么是箭头函数?
    • 箭头函数是ES6中新增的一种定义函数的格式
    • 目的:就是为了简化定义函数的代码
        // 两种定义数组的方式
        let arr = new Array();
        let arr = [];
  • 2.在ES6之前如何定义函数
        function 函数名称(形参列表){
            需要封装的代码;
        }
        let 函数名称 = function(形参列表){
            需要封装的代码;
        }
  • 3.从ES6开始如何定义函数
        let 函数名称 = (形参列表) =>{
            需要封装的代码;
        }
​        // function say() {
        //     console.log("hello lnj");
        // }
        let say = () => {
            console.log("hello lnj");
        }
        say();
  • 4.箭头函数的注意点
    • 4.1 在箭头函数中如果只有一个形参, 那么 ( ) 可以省略
        // function say(name) {
        //     console.log("hello  " + name);
        // }
        // let say = (name) => {
        //     console.log("hello  " + name);
        // }
        let say = name => {
            console.log("hello  " + name);
        }
  • 4.2 在箭头函数中如果 { } 中只有一句代码, 那么 { } 也可以省略
        let say = name => console.log("hello  " + name);
        say("it666");

(3).递归函数

  • 1.什么是递归函数?
    • 递归函数:就是在函数中自己调用自己, 我们就称之为递归函数
    • 递归函数在一定程度上可以实现循环的功能
  • 2.递归函数的注意点
    • 每次调用递归函数都会开辟一块新的存储空间, 所以性能不是很好
  • 3.示例
  • 需求:要求用户输入密码, 判断输入密码是否正确(假设正确密码是123456)
               如果正确, 输出"欢迎回来"
               如果不正确, 要求用户重新输入
  • 方式1:利用循环实现
        let pwd = -1;
        do{
            pwd = prompt("请输入密码");
        }while (pwd !== "123456");
        alert("欢迎回来");
  • 方式2 :利用递归函数实现
       function login() {
            // 1.接收用户输入的密码
            let pwd = prompt("请输入密码");
            // 2.判断密码是否正确
            if(pwd !== "123456"){
                login();
            }
            // 3.输出欢迎回来
            alert("欢迎回来");
        }
        login();
​

注意点:使用该方法会返回3次欢迎回来!(因为输了3次密码就调用了三次login函数)

5.函数中变量作用域

(1).定义变量的两种方式以及区别

  • 1.在JavaScript中定义变量有两种方式
    • ES6之前:   var 变量名称;
    • ES6开始:   let 变量名称;
  • 2.两种定义变量方式的区别
    • 2.1 是否能够定义同名变量
      • 1.通过 var 定义变量,可以重复定义同名的变量,并且后定义的会覆盖先定义的
      •         var num = 123;
                var num = 456;
                console.log(num);

         

      • 2.通过 let 定义变量,  "相同作用域内"不可以重复定义同名的变量
      •         let num = 123;
                let num = 456;     // 报错

         

    • 2.2 是否能够先使用后定义
      •  1.通过 var 定义变量, 可以先使用后定义(预解析
      •         console.log(num);
                var num = 123;

         

      • 2.通过 let 定义变量, 不可以先使用再定义(不会预解析)
      •         console.log(num);     // 报错
                let num = 123;

         

    • 2.3 是否能被 { } 限制作用域

      • 1.无论是 var 还是 let ,定义在 { } 外面 都是 全局变量

      •         var num = 123;     //全局变量
                let num = 123;     //全局变量

         

      • 2.将 var 定义的变量放到一个单独的 { } 里面, 还是一个 全局变量

      •         {
                    var num = 123;    //全局变量
                }
                console.log(num);     //不会报错

         

      • 3.将 let 定义的变量放到一个单独的 { } 里面, 是一个 局部变量

      •         {
                    let num = 123;    //局部变量
                }
                console.log(num);     //会报错

         

(2).作用域

  • 1.在JavaScript中 { } 外面的作用域, 我们称之为 全局作用域
  • 2.在JavaScript中函数后面 { } 中的的作用域, 我们称之为 局部作用域
  • 3.在ES6中只要 { } 没有和函数结合 在一起, 那么就是 块级作用域
​
        {
            // 块级作用域
        }
        if(false){
            // 块级作用域
        }
        while (false){
            // 块级作用域
        }
        for(;;){
            // 块级作用域
        }
        do{
            // 块级作用域
        }while (false);
        switch () {
            // 块级作用域
        }

        function say() {
            // 局部作用域
        }
  • 4.块级作用域和局部作用域区别
    • 4.1 在块级作用域中,通过 var 定义的变量是 全局变量
    • 4.2 在局部作用域中,通过 var 定义的变量是 局部变量
        {
            // 块级作用域
            var num = 123; // 全局变量
        }
        console.log(num);

        function test() {
            // 局部作用域
            var value = 666; // 局部变量
        }
        test();
        console.log(value);
  • 5.无论是在块级作用域,还是在局部作用域,省略变量前面的 let 或者 var 就会变成一个 全局变量
        {
            // 块级作用域
            // var num = 678;   // 全局变量
            // let num = 678;   // 局部变量
            num = 678;          // 全局变量
        }
        console.log(num);

        function test() {
            // var num = 123;   // 局部变量
            // let num = 123;   // 局部变量
            num = 123;          // 全局变量
        }
        test();
        console.log(num);

6.作用域链

(1).ES6之前 作用域链

  • 1.需要明确
    • 1.1.ES6之前 定义变量 通过 var
    • 1.2.ES6之前 没有块级作用域,只有全局作用域和局部作用域
    • 1.3.ES6之前 函数大括号外的都是 全局作用域
    • 1.4.ES6之前 函数大括号中的都是 局部作用域
  • 2.ES6之前作用域链
    • 2.1.全局作用域,我们又称之为 0级作用域
    • 2.2.定义函数 开启的作用域,就是 1级 / 2级 / 3级 / ...作用域
    • 2.3.JavaScript会将这些作用域链接在一起形成一个链条, 这个链条就是 作用域链
      • 0  --->  1 ---->  2  ---->  3 ----> 4
    • 2.4.除0级作用域以外, 当前作用域级别等于上一级+1
  • 3.变量在作用域链 查找规则
    • 3.1 先在当前找, 找到就使用当前作用域找到的
    • 3.2 如果当前作用域中没有找到, 就去上一级作用域中查找
    • 3.3 以此类推直到0级为止, 如果0级作用域还没找到, 就报错
        // 全局作用域(0级作用域)
        // var num = 123;
        function demo() {
            // 1级作用域
            // var num = 456;
            function test() {
                // 2级作用域
                // var num = 789;
                console.log(num);
            }
            test();
        }
        demo();

(2).ES6之后 作用域链

  • 1.需要明确
    • 1.1.ES6 定义变量 通过 let
    • 1.2.ES6除了全局作用域、局部作用域以外, 还新增了块级作用域
    • 1.3.ES6虽然新增了块级作用域, 但是通过let定义变量并无差异(都是局部变量)
  • 2.ES6作用域链
    • 2.1.全局作用域,我们又称之为0级作用域
    • 2.2.定义函数或者代码块都会开启的作用域就是1级/2级/3级/...作用域
    • 2.3.JavaScript会将这些作用域链接在一起形成一个链条, 这个链条就是作用域链
      • 0  --->  1 ---->  2  ---->  3 ----> 4
    • 2.4.除0级作用域以外, 当前作用域级别等于上一级+1
  • 3.变量在作用域链查找规则
    • 3.1 先在当前找, 找到就使用当前作用域找到的
    • 3.2 如果当前作用域中没有找到, 就去上一级作用域中查找
    • 3.3 以此类推直到0级为止, 如果0级作用域还没找到, 就报错
        // 全局作用域(0级作用域)
        // let num = 123;
        {
            // 1级作用域
            // let num = 456;
            function test() {
                // 2级作用域
                // let num = 789;
                console.log(num);
            }
            test();
        }

7.浏览器的预解析

  • 1.什么是预解析?
    • 浏览器在执行JS代码的时候会分成两部分操作:预解析 以及 逐行执行代码
    • 也就是说浏览器不会直接执行代码, 而是加工处理之后再执行
    • 这个加工处理的过程, 我们就称之为预解析
  • 2.预解析规则
    • 2.1 将变量声明函数声明提升到当前作用域最前面
    • 2.2 将剩余代码按照书写顺序依次放到后面
  • 3.注意点
    • 通过 let 定义的变量不会被提升(不会被预解析
        // 预解析之前
        console.log(num);    //undefined
        var num = 123;

        // 预解析之后
        var num;
        console.log(num);
        num = 123;
        // 不会预解析之前
        console.log(num); // 报错
        let num = 456;
        // ES6之前定义函数的格式
        /*
        console.log(say);
        say();
        // ES6之前的这种定义函数的格式, 是会被预解析的, 所以可以提前调用
        function say() {
            console.log("hello it666");
        }

        // 预解析之后的代码
        function say() {
            console.log("hello it666");
        }
        say();
        console.log(say);
        say(); // say is not a function
        // 如果将函数赋值给一个var定义的变量, 那么函数不会被预解析, 只有变量会被预解析
        var say = function() {
            console.log("hello itzb");
        }

        var say; //undefined
        say();
        say = function() {
            console.log("hello itzb");
        }
        // ES6定义函数的格式
        // 箭头函数也不会被预解析
        say(); // say is not defined
        let say = () => {
            console.log("hello itzb");
        }

十二、面向对象

1.面向对象基本概念

  • (1).面向对象和面向过程区别
    • 面向过程

      • 强调的是功能行为
      • 关注的是解决问题需要哪些步骤
        • 完成一个需求的步骤:

          • 首先搞清楚我们要做什么
          • 然后分析怎么做
          • 最后我用代码体现
          • 一步一步去实现,而具体的每一步都需要我们去实现和操作
        • 在上面每一个具体步骤中我们都是参与者, 并且需要面对具体的每一个步骤和过程, 这就是面向过程最直接的体现

    • 面向对象

      • 将功能封装进对象,强调具备了功能的对象
      • 关注的是解决问题需要哪些对象
  • (2).面向对象思想

    • 当需求单一, 或者简单时, 我们一步一步去操作没问题, 并且效率也挺高。

    • 可随着需求的更改, 功能的增加, 发现需要面对每一个步骤非常麻烦, 这时就开始思索,

    • 能不能把这些步骤和功能再进行封装, 封装时根据不同的功能,进行不同的封装,功能类似的封装在一起。

    • 这样结构就清晰多了, 用的时候, 找到对应的类就可以了, 这就是面向对象思想

  • (3).示例

    • 吃饭
      • 面向过程

        • 买菜
        • 摘菜
        • 洗菜
        • 切菜
        • 炒菜
        • 盛菜
      • 面向对象

        • 去饭店
        • 点菜

(4).面向对象的特点

  • 是一种符合人们思考习惯的思想
  • 可以将复杂的事情简单化
  • 将程序员从执行者转换成了指挥者
  • 完成需求时
    • 先要去找具有所需的功能的对象来用
    • 如果该对象不存在,那么创建一个具有所需功能的对象
    • 这样简化开发并提高复用

(5).类与对象的关系

  • 面向对象的核心就是对象,那怎么创建对象?

    • 现实生活中可以根据模板创建对象,编程语言也一样,也必须先有一个模板,在这个模板中说清楚将来创建出来的对象有哪些属性行为(方法)
  • JavaScript中的类相当于图纸,用来描述一类事物。

  • JavaScript中可以自定义类, 但是也提供了一个默认的类叫做 Object

2.使用默认类创建对象

  • (1).JavaScript中提供了一个默认的类 Object , 我们可以通过这个类来创建对象
  • (2).由于我们是使用系统默认的类创建的对象, 所以系统不知道我们想要什么属性和行为, 所以我们必须手动的添加我们想要的属性和行为
    • 2.1 如何给一个对象添加 属性对象名称.属性名称 = 值;
    • 2.2 如何给一个对象添加 行为对象名称.行为名称 = 函数;
  • (3).创建对象的三种方式
  • 方法1:通过 new Object() 创建对象
        // 创建对象的第一种方式
        // 1.创建对象
        let obj = new Object();
        // 2.定义属性(特征)
        obj.name = "lnj";
        obj.age = 33;
        // 3.定义方法(行为)
        obj.say = function () {
            console.log("hello world");
        }
        console.log(obj.name);
        console.log(obj.age);
        obj.say();
  • 方法2:通过字面量创建对象
        // 创建对象的第二种方式
        // 1.创建对象(简化形式)
        let obj = {};         // let obj = new Object();
        // 2.定义属性
        obj.name = "lnj";
        obj.age = 33;
        // 3.定义方法
        obj.say = function () {
            console.log("hello world");
        }
        console.log(obj.name);
        console.log(obj.age);
        obj.say();
  • 方法3
        // 创建对象的第三种方式
        // 注意点: 属性名称和取值之间用冒号隔开, 属性和属性之间用逗号隔开
        
        // 创建对象的同时,定义属性与方法
        let obj = {
            // 定义属性
            name: "lnj",
            age: 33,
            // 定义方法
            say: function () {
                console.log("hello world");
            }
        };
        console.log(obj.name);
        console.log(obj.age);
        obj.say();

3.函数与方法的区别

  • (1).什么是函数?
    • 函数就是没有和其它的类显示的绑定在一起的, 我们就称之为函数
  • (2).什么是方法?
    • 方法就是显示的和其它的类绑定在一起的, 我们就称之为方法
  • (3).函数和方法的区别
    • 3.1 函数可以直接调用, 但是方法不能直接调用, 只能通过对象调用
    • 3.2 函数内部的 this 输出的是 window , 方法内部的 this 输出的是 当前调用的那个对象
  • (4).无论是函数还是方法, 内部都有一个叫做 this
    • this 是什么?
      • 谁调用了当前的函数或者方法, 那么当前的this就是谁
  • 函数:
        function demo() {
            // console.log("hello demo");
            console.log(this);
        }
        // demo();       // 可以直接调用
        window.demo();
  • 方法:
        let obj = {
            name: "lnj",
            test: function () {
                // console.log("hello test");
                console.log(this);
            }
        };
        // test();       // 报错
        obj.test();      // 必须通过对象来调用

4.关键字 this

  • 每个函数中都有一个this关键字, 谁调用当前函数, this关键字就是谁
    function test() {
        console.log(this);
    }
    // 默认情况下直接调用的函数都是由window调用的
    // 所以test函数中的this是window
    test(); // 相当于window.test();
    
    var obj = new Object()
    obj.name = "lnj";
    obj.age = 33;
    obj.say = function () {
        console.log(this.name, this.age);
    }

    // 这里的say是一个方法, 方法必须通过对象来调用
    // 所以这里的say方法是由obj对象调用的, 所以say方法中的this是obj对象
    obj.say();

5.使用工厂函数创建对象

  • (1).什么是工厂函数?
    • 工厂函数就是专门用于创建对象的函数, 我们就称之为工厂函数
        let obj1 = {
            name: "lnj",
            age: 33,
            say: function () {
                console.log("hello world");
            }
        };
        let obj2 = {
            name: "zs",
            age: 44,
            say: function () {
                console.log("hello world");
            }
        };
        
        // 定义的两个对象拥有相同的say方法
  • (2).工厂函数的定义
        // 定义工厂函数
        function createPerson(myName, myAge) {
            // 1.创建对象
            let obj = new Object();
            // 2.定义属性
            obj.name = myName;
            obj.age = myAge;
            // 3.定义方法
            obj.say = function () {
                console.log("hello world");
            }
            // 4.返回对象
            return obj;
        }
        // 调用工厂函数
        let obj1 = createPerson("lnj", 34);
        let obj2 = createPerson("zs", 44);
        console.log(obj1);
        console.log(obj2);

6.使用构造函数创建对象

  • (1).什么是构造函数
    • 1.1 构造函数和工厂函数一样, 都是专门用于创建对象
    • 1.2 构造函数本质上是工厂函数的简写
  • (2).构造函数和工厂函数的区别
    • 2.1 构造函数的函数名称首字母必须大写
    • 2.2 构造函数只能够通过new调用
  • (3).构造函数的定义
        // 定义构造函数
        function Person(myName, myAge) {

            // let obj = new Object();      // 1.系统自动添加(创建对象)
            // let this = obj;              // 2.系统自动添加(将对象赋值给this)

            // 1.通过this直接定义属性
            this.name = myName;
            this.age = myAge;
            // 2.通过this直接定义方法
            this.say = function () {
                console.log("hello world");
            }
            
            // return this;                 // 3.系统自动添加的(返回this)
        }

        // 通过new来调用构造函数
        let obj1 = new Person("lnj", 34);
        let obj2 = new Person("zs", 44);
        console.log(obj1);
        console.log(obj2);
  • 当我们 new Person("lnj", 34);  系统做了什么事情?
    • 4.1 会在构造函数中自动创建一个对象
    • 4.2 会自动将刚才创建的对象赋值给 this
    • 4.3 会在构造函数的最后自动添加 return this;
  • 使用该方法定义构造函数的弊端
    • obj1对象与obj2对象中的say方法的实现都是一样的, 但是却保存到了不同的存储空间中,所以存在性能问题
        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
            this.say = function () {
                console.log("hello world");
                // 方法中的this谁调用就是谁
                // console.log(this.name, this.age);
            }
        }

        let obj1 = new Person("lnj", 34);
        let obj2 = new Person("zs", 44);
        // 由于两个对象中的say方法的实现都是一样的, 但是保存到了不同的存储空间中
        // 所以有性能问题
        console.log(obj1.say === obj2.say);       // false
        function demo() {
            console.log("demo");
        }
        // 通过三个等号来判断两个函数名称, 表示判断两个函数是否都存储在同一块内存中
        console.log(demo === demo);      // true
  • 优化1
        // 单独定义一个函数
        function mySay() {
            console.log("hello world");
        }

        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
            this.say = mySay;
        }

        let obj1 = new Person("lnj", 34);
        let obj2 = new Person("zs", 44);
        console.log(obj1.say === obj2.say);     // true
  • 当前这种方式解决之后存在的弊端
    • 1.阅读性降低了
      • 2.污染了全局的命名空间
  • 优化2
        // 在定义对象时,定义方法
        let fns = {
            mySay: function () {
                console.log("hello world");
            }
        }

        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
            this.say = fns.mySay;
        }

        let obj1 = new Person("lnj", 34);
        let obj2 = new Person("zs", 44);
        console.log(obj1.say === obj2.say);     // true
  • 优化3
        // 1.定义构造函数
        function Person(myName, myAge) {
            // 定义属性
            this.name = myName;
            this.age = myAge;
        }
        
        // 2.利用构造函数自身的prototype对象来定义方法
        Person.prototype = {
            say: function () {
                console.log("hello world");
            }
        }
        
        // 3.调用构造函数来创建对象
        let obj1 = new Person("lnj", 34);
        obj1.say();
        let obj2 = new Person("zs", 44);
        obj2.say();
        console.log(obj1.say === obj2.say);     // true
  • (4).prototype属性
    • 4.1 prototype特点
      • 1.存储在prototype中的方法,可以被对应构造函数创建出来的所有对象共享
      • 2.prototype中除了可以存储方法以外, 还可以存储属性
      • 3.prototype如果出现了和构造函数中同名属性或者方法, 对象在访问的时候, 访问到的是构造函数中的数据
    • 4.2 prototype应用场景
      • prototype 中一般情况下,用于存储所有对象都相同的一些属性以及方法
      • 如果是对象特有的属性或者方法, 我们会存储到构造函数
        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;

            this.currentType = "构造函数中的type";
            this.say = function () {
                console.log("构造函数中的say");
            }
        }

        Person.prototype = {
            currentType: "人",
            say: function () {
                console.log("hello world");
            }
        }

        let obj1 = new Person("lnj", 34);
        obj1.say();
        console.log(obj1.currentType);
        let obj2 = new Person("zs", 44);
        obj2.say();
        console.log(obj2.currentType);

7.对象三角恋关系

(1).自定义构造函数

  • 1.每个构造函数中都有一个默认的属性, 叫做 prototype
    • prototype 属性保存着一个对象, 这个对象我们称之为 原型对象
  • 2.每个 原型对象 中都有一个默认的属性, 叫做 constructor
    • constructor 指向当前原型对象对应的那个 构造函数 
  • 3.通过构造函数创建出来的对象我们称之为 实例对象
    • 每个 实例对象 中都有一个默认的属性, 叫做 __proto__
    • __proto__ 指向 创建它的那个构造函数的 原型对象 

        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }
        let obj1 = new Person("lnj", 34);

        console.log(Person.prototype);
        console.log(Person.prototype.constructor);
        console.log(obj1.__proto__);

(2).Function构造函数

  • 1.JavaScript中函数是引用类型(对象类型)
    • 既然是对象,所以也是通过构造函数创建出来的,所有函数是通过Function构造函数创建出来的对象
  • 2.JavaScript中只要是函数,就有prototype属性
    • Function函数的prototype属性指向Function原型对象
  • 3.JavaScript中只要原型对象,就有constructor属性
    • Function原型对象的constructor指向它对应的构造函数
  • 4.Person构造函数是Function构造函数的实例对象, 所以也有__proto__属性
    • Person构造函数的__proto__属性指向Function原型对象

        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }
        let obj1 = new Person("lnj", 34);

        // console.log(Function);
        // console.log(Function.prototype);
        // console.log(Function.prototype.constructor);
        // console.log(Function === Function.prototype.constructor); // true

        // console.log(Person.__proto__);
        console.log(Person.__proto__ === Function.prototype); // true

(3).Object构造函数

  • 1. JavaScript函数是引用类型(对象类型),所以Function函数也是对象
  • 2.Function构造函数也是一个对象,所以也有__proto__属性
    • Function构造函数__proto__属性指向Function原型对象
  • 3. JavaScript中还有一个系统提供的构造函数叫做 Object
    • 只要是函数都是Function构造函数实例对象
  • 4.只要是对象就有__proto__属性, 所以Object构造函数也有__proto__属性
    • Object构造函数的__proto__属性指向创建它那个构造函数的原型对象
  • 5.只要是构造函数都有一个默认的属性, 叫做 prototype
    • prototype属性保存着一个对象, 这个对象我们称之为原型对象
  • 6.只要是原型对象都有一个默认的属性, 叫做 constructor
    • constructor指向当前原型对象对应的那个构造函数

        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }
        let obj1 = new Person("lnj", 34);
        // console.log(Function.__proto__);
        // console.log(Function.__proto__ === Function.prototype); // true

        // console.log(Object);
        // console.log(Object.__proto__);
        // console.log(Object.__proto__ === Function.prototype); // true
        // console.log(Object.prototype);
        // console.log(Object.prototype.constructor);

        // console.log(Object.prototype.constructor === Object); // true
        // console.log(Object.prototype.__proto__); // null

(4).函数对象关系

  • 1.所有的构造函数都有一个prototype属性, 所有prototype属性都指向自己的原型对象
  • 2.所有的原型对象都有一个constructor属性, 所有constructor属性都指向自己的构造函数
  • 3.所有函数都是Function构造函数实例对象
  • 4.所有函数都是对象, 包括Function构造函数
  • 5.所有对象都有__proto__属性
  • 6.普通对象的__proto__属性指向创建它的那个构造函数对应的原型对象
  • 7.所有对象的__proto__属性最终都会指向Object原型对象
  • 8.Object原型对象的__proto__属性指向NULL

完整关系图:

        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }
        let obj1 = new Person("lnj", 34);

        console.log(Function.prototype.__proto__);
        console.log(Person.prototype.__proto__);
        console.log(Function.prototype.__proto__ === Person.prototype.__proto__);
        console.log(Function.prototype.__proto__ === Object.prototype);
        console.log(Person.prototype.__proto__ === Object.prototype);

(5).原生链

  • 1.对象中__proto__组成的链条我们称之为 原型链
  • 2.对象在查找属性和方法的时候,
    • 2.1 会先在当前对象查找
    • 2.2 如果当前对象中找不到想要的, 会依次去上一级原型对象中查找
    • 2.3 如果找到Object原型对象都没有找到, 就会报错

        // 构造函数
        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
            // this.currentType = "构造函数中的type";
            // this.say = function () {
            //     console.log("构造函数中的say");
            // }
        }

        // 原型对象
        Person.prototype = {
            // 注意点: 为了不破坏原有的关系, 在给prototype赋值的时候, 需要在自定义的对象中手动的添加constructor属性, 手动的指定需要指向谁
            constructor: Person,
            // currentType: "人",
            // say: function () {
            //     console.log("hello world");
            // }
        }
        let obj1 = new Person("lnj", 34);
        // obj1.say();
        console.log(obj1.currentType);
        // console.log(Person.prototype.constructor);
        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }
        Person.prototype = {
            constructor: Person,
            currentType: "人",
            say: function () {
                console.log("hello world");
            }
        }
        let obj = new Person("lnj", 34);
        // console.log(obj.currentType); // "人"
        // console.log(obj.__proto__.currentType); // "人"

        // 注意点: 在给一个对象不存在的属性设置值的时候, 不会去原型对象中查找, 如果当前对象没有就会给当前对象新增一个不存在的属性
        obj.currentType = "新设置的值";
        console.log(obj.currentType); // 新设置的值
        console.log(obj.__proto__.currentType); // "人"

8.面向对象三大特性

(1).封装性

  • 1.局部变量和局部函数
    • 无论是ES6之前还是ES6, 只要定义一个函数就会开启一个新的作用域
    • 只要在这个新的作用域中, 通过 let / var 定义的变量就是局部变量
    • 只要在这个新的作用域中, 定义的函数就是局部函数
  • 2.什么是对象的私有变量和私有函数
    • 默认情况下对象中的属性和方法都是公有的, 只要拿到对象就能操作对象的属性和方法
    • 外界不能直接访问的变量和函数就是私有变量和私有函数
    • 构造函数的本质也是一个函数, 所以也会开启一个新的作用域, 所以在构造函数中定义的变量和函数,就是私有变量与私有函数
  • 3.什么是封装性
    • 封装性就是隐藏实现细节,仅对外公开接口
    • 类是数据与功能的封装,数据就是成员变量,功能就是方法
  • 4.为什么要封装
    • 不封装的缺点:当一个类把自己的成员变量暴露给外部的时候,那么该类就失去对该成员变量的管理权,别人可以任意的修改你的成员变量
    • 封装就是将数据隐藏起来,只能用此类的方法才可以读取或者设置数据,不可被外部任意修改是面向对象设计本质(将变化隔离)。这样降低了数据被误用的可能 (提高安全性灵活性
    function Person(name) {
        this.name = name;         // 公有属性

        // 由于构造函数也是一个函数, 所以也会开启一个新的作用域
        // 所以在构造函数中通过var/let定义的变量也是局部变量
        // 所以在构造函数中定义的函数也是局部函数
        // 只能在内部使用, 不能在外部使用
        var age = 666;           // 私有属性

        // 公有方法
        this.say = function () {
            console.log(this.name, age);
            test();
        }
        this.setAge = function (num) {
            age = num;
        }
        this.getAge = function () {
            return age;
        }

        // 私有方法
        function test() {
            console.log("test");
        }
    }
    var p = new Person("lnj");
    console.log(p.name);
    p.say();
    console.log(p.age);         // undefined
    p.setAge(123);
    console.log(p.getAge());
    p.test();                  // 报错
  • 5.封装原则
    • 将不需要对外提供的内容都隐藏起来,把属性都隐藏,提供公共的方法对其访问
        function Person() {
            this.name = "lnj";  
            
            // 私有属性  
            let age = 34;    
            // 1.定义共有方法,修改私有属性   
            this.setAge = function (myAge) {
                if (myAge >= 0) {
                    age = myAge;
                }
            };
            // 2.定义共有方法,获取私有属性
            this.getAge = function () {
                return age;
            };

            this.say = function () {
                console.log("hello world");
            };
        }
        let obj = new Person();
        // 1.操作的是私有属性(局部变量)
        obj.setAge(-3);
        console.log(obj.getAge());

        /*
        // 注意点:
        // 在给一个对象不存在的属性设置值的时候, 不会去原型对象中查找, 如果当前对象没有就会给当前对象新增一个不存在的属性
        // 由于私有属性的本质就是一个局部变量, 并不是真正的属性, 所以如果通过 对象.xxx的方式是找不到私有属性的, 所以会给当前对象新增一个不存在的属性
        */
        // 2.操作的是公有属性
        obj.age = -3;
        console.log(obj.age);
  • 6.属性与方法分类
  • 6.1 实例属性和实例方法
    • 在企业开发中,通过实例对象访问的属性, 我们就称之为实例属性
    • 在企业开发中,通过实例对象调用的方法, 我们就称之为实例方法
    // 定义构造函数
    function Person(name) {
        this.name = name;               // 实例属性
        this.eat = function () {        // 实例方法
            console.log("eat");
        }
    }

    Person.prototype.age = "0";              // 实例属性
    Person.prototype.say = function () {     // 实例方法
        console.log("hello");
    }
     
    // 通过构造函数创建的对象, 我们称之为实例对象
    var p = new Person("lnj");

    console.log(p.name);     // 通过对象访问
    console.log(p.age);      // 通过对象访问
    p.eat();                 // 通过对象访问
    p.say();                 // 通过对象访问
  • 6.2 静态属性和静态方法
    • 在企业开发中,通过构造函数访问的属性, 我们就称之为静态属性
    • 在企业开发中,通过构造函数调用的方法, 我们就称之为静态方法
    function Person() {
        Person.name = "lnj";           // 静态属性
        Person.eat = function () {     // 静态方法
            console.log("eat");
        }
    }
    Person.count = 0;                  // 静态属性
    Person.say = function () {         // 静态方法
        console.log("hello");
    };

    console.log(Person.name);         // 通过构造函数访问
    console.log(Person.count);        // 通过构造函数访问
    Person.eat();                     // 通过构造函数访问
    Person.say();                     // 通过构造函数访问

(2).继承性

  • 1.什么是继承
    • 儿子继承父亲的物品,就是继承最好的体现
  • 2.继承的作用
    • js中继承目的: 把子类型中共同属性和方法提取到父类型中
    • 减少代码的冗余度, 提升代码的复用性

  • 3.继承的4种方式
  • 方式1:借用原型链实现继承
    • 直接将子类的原型对象修改为父类对象, 这样就能使用原型链上的属性和方法
        // 父类
        function Person() {
            this.name = null;
            this.age = 0;
            this.say = function () {
                console.log(this.name, this.age);
            }
        }
        let per = new Person();
        per.name = "lnj";
        per.age = 34;
        per.say();
        
        // 子类
        // 在企业开发中如果构造函数和构造函数之间的关系是is a关系, 那么就可以使用继承来优化代码, 来减少代码的冗余度
        // 学生 is a 人 , 学生是一个人
        function Student() {
            // this.name = null;
            // this.age = 0;
            // this.say = function () {
            //     console.log(this.name, this.age);
            // }
            this.score = 0;
            this.study = function () {
                console.log("day day up");
            }
        }

        // 继承方式1
        // 1.修改Student的原型对象Person的实例对象   
        Student.prototype = new Person();
        // 2.修改constructor属性指向Student构造函数 
        Student.prototype.constructor = Student;

        let stu = new Student();
        stu.name = "zs";
        stu.age = 18;
        stu.score = 99;
        stu.say();
        stu.study();
  • 弊端:无法直接给子类传递继承自父类的属性
        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
            this.say = function () {
                console.log(this.name, this.age);
            }
        }
       // let per = new Person("lnj", 34);
       //  per.say();

        // 学生 is a 人 , 学生是一个人
        function Student(myName, myAge, myScore) {
            this.score = myScore;
            this.study = function () {
                console.log("day day up");
            }
        }

        Student.prototype = new Person();
        Student.prototype.constructor = Student;

        let stu = new Student("zs", 18, 99);
        console.log(stu.score);
        stu.say();
        stu.study();
  • 函数/方法中的 this 是谁 ?
    • 谁调用当前函数或者方法,this 就是谁
  • 如何修改 this ?
  • 1. bind方法
    • 修改函数或者方法中的this为指定的对象, 并且会返回一个修改之后的新函数给我们
    • 注意点:bind方法除了可以修改this以外, 还可以传递参数, 只不过参数必须写在this对象的后面
  • 2. call方法
    • 修改函数或者方法中的this为指定的对象, 并且会立即调用修改之后的函数
    • 注意点:call方法除了可以修改this以外, 还可以传递参数, 只不过参数必须写在this对象的后面
  • 3. apply方法
    • 修改函数或者方法中的this为指定的对象, 并且会立即调用修改之后的函数
    • 注意点:apply方法除了可以修改this以外, 还可以传递参数, 只不过参数必须通过数组的方式传递
        let obj = {
            name: "zs"
        };
        function test(a, b) {
            console.log(a, b);
            console.log(this);
        }
        
        // test(10, 20);
        // window.test();

        // 修改test函数中的this为obj对象

        let fn = test.bind(obj, 10, 20);
        fn();

        test.call(obj, 10, 20);

        test.apply(obj, [10, 20]);
        let obj = {
            name: "zs"
        };
        function Person() {
            this.name = "lnj";
            this.say = function () {
                console.log(this);
            }
        }
        let p = new Person();
        // p.say();
        
        // 修改Person构造函数中的say方法中的this

        // let fn = p.say.bind(obj);
        // fn();
        
        // p.say.call(obj);
        
        p.say.apply(obj);
  • 方式2:借用构造函数实现继承
    • 在子类中调用父类构造函数, 并且将父类构造函数的this修改为子类对象
        function Person(myName, myAge) {

            // let per = new Object();        // 自动生成的(构造函数)
            // let this = per;

            // this = stu;

            this.name = myName;               // stu.name = myName;
            this.age = myAge;                 // stu.age = myAge;
            this.say = function () {          // stu.say = function () {}
                console.log(this.name, this.age);
            }
            // return this;
        }
        function Student(myName, myAge, myScore) {
            
            // let stu = new Object();            // 自动生成的(构造函数)
            // let this = stu;

            // 通过call修改Person构造函数中的this
            Person.call(this, myName, myAge);     //  Person.call(stu);

            this.score = myScore;
            this.study = function () {
                console.log("day day up");
            }
            
            // return this;
        }
        
        // 此时便可以在创建Student(子类)对象时直接传参(name,age)
        let stu = new Student("ww", 19, 99);

        // stu.name = "zs";
        // stu.age = 18;
        // stu.score = 99;

        console.log(stu.score);
        stu.say();
        stu.study();
  • 方式3:借用构造函数+借用原型链组合继承
    • 通过借用构造函数实现属性继承
    • 通过借用原型链实现方法继承
        function Person(myName, myAge) {

            // let per = new Object();
            // let this = per;
            
            // this = stu;

            this.name = myName;                   // stu.name = myName;
            this.age = myAge;                     // stu.age = myAge;

            // this.say = function () {           // stu.say = function () {}
            //     console.log(this.name, this.age);
            // }
            // return this;
        }

        Person.prototype.say = function () {
            console.log(this.name, this.age);
        }

        function Student(myName, myAge, myScore) {

            // 通过call修改Person构造函数中的this
            Person.call(this, myName, myAge);

            this.score = myScore;
            this.study = function () {
                console.log("day day up");
            }
        }
        
        // 注意点: 要想使用Person原型对象中的属性和方法, 那么就必须将Student的原型对象改为Person的原型对象才可以
        Student.prototype = Person.prototype;
        Student.prototype.constructor = Student;

        let stu = new Student("ww", 19, 99);
        console.log(stu.score);
        stu.say();
        stu.study();
  • 弊端:
    • 1.由于修改了Person原型对象的constructor属性, 所以破坏了Person的三角恋关系
    • 2.由于Person和Student的原型对象是同一个, 所以给Student的元素添加方法, Person也会新增方法
  • 终极方案
        function Person(myName, myAge) {
            this.name = myName;                   
            this.age = myAge;                    
        }

        Person.prototype.say = function () {
            console.log(this.name, this.age);
        }

        function Student(myName, myAge, myScore) {

            // 1.通过call修改Person构造函数中的this
            Person.call(this, myName, myAge);

            this.score = myScore;
            this.study = function () {
                console.log("day day up");
            }
        }
        
        // 2.修改Student的原型对象为Person实例对象
        Student.prototype = new Person();
        Student.prototype.constructor = Student;

        let stu = new Student("ww", 19, 99);
        console.log(stu.score);
        stu.say();
        stu.study();


        Student.prototype.run = function(){
            console.log("run");
        }
        let per = new Person();
        per.run();    //报错


(3).多态

  • 1.什么是强类型语言, 什么是是弱类型语言
    • 1.1 什么是强类型语言
      • 一般编译型语言都是强类型语言
      • 强类型语言,要求变量的使用要严格符合定义 例如定义 int num; 那么num中将来就只能够存储整型数据
    • 1.2 什么是弱类型语言
      • 一般解释型语言都是弱类型语言
      • 弱类型语言, 不会要求变量的使用要严格符合定义 例如定义 let num; num中既可以存储整型, 也可以存储布尔类型等
    • 1.3 由于js语言是弱类型的语言, 所以我们不用关注多态
  • 2.什么是多态?
    • 多态是指事物的多种状态 例如: 按下 F1 键这个动作, 如果当前在 webstorm 界面下弹出的就是 webstorm 的帮助文档; 如果当前在 Word 下弹出的就是 Word 帮助; 同一个事件发生在不同的对象上会产生不同的结果。
  • 3.多态在编程语言中的体现
    • 父类型变量保存子类型对象, 父类型变量当前保存的对象不同, 产生的结果也不同

9.类与对象

(1).如何设计一个类

  • 生活中描述事物无非就是描述事物的属性行为
    • 如:人有身高,体重等属性,有说话,打架等行为。
事物名称(类名):人(Person)
属性:身高(height)、年龄(age)
行为(功能):跑(run)、打架(fight)
  • JavaScript中用类来描述事物也是如此
    • 属性:对应类中的成员变量
    • 行为:对应类中的成员方法
  • 定义类其实在定义类中的成员(成员变量和成员方法)
  • 拥有相同或者类似属性(状态特征)和行为(能干什么事)的对象都可以抽像成为一个类
 

(2).如何定义一个类

  • 2.1 在ES6之前:通过构造函数来定义一个类
    • 构造函数也是一个函数, 只不过函数的名称必须 大写(帕斯卡命名)
    • 构造函数也是一个函数, 只不过调用时必须通过 new 来调用
        // 1.利用构造函数定义类(实质还是一个函数)
        function Person(myName, myAge) {
            // 实例属性
            // this.name = "lnj";
            // this.age = 34;
            this.name = myName;
            this.age = myAge;

            // 实例方法
            this.say = function () {
                console.log(this.name, this.age);
            }
            // 静态属性
            Person.num = 666;
            // 静态方法
            Person.run = function () {
                console.log("run");
            }
        }
        
        // 2.通过类创建对象
        // let p = new Person();
        let p = new Person("zs", 18);
        p.say();
        console.log(Person.num);
        Person.run();
  • 2.2 从ES6开始系统提供了一个名称叫做 class 的关键字, 这个关键字就是专门用于定义类
        // 1.通过关键字class创建类
        class Person{
            // 当我们通过new创建对象的时候, 系统会自动调用constructor
            // constructor我们称之为构造函数
            constructor(myName, myAge){
                this.name = myName;
                this.age = myAge;
            }

            // 注意点1:以下定义"实例属性"的方式并不是ES6正式版标准中的写法, 大部分的浏览器不支持
            // 在ES6标准中添加实例属性都需要在constructor中添加
            // 实例属性
            // name = "lnj";
            // age = 34;

            // 定义实例属性的正确方式!
            constructor(){
                this.name = "lnj";
                this.age = 34;
            }

            // 实例方法
            say(){
                console.log(this.name, this.age);
            }

            // 注意点2:以下定义"静态属性"的方式并不是ES6正式版标准中的写法, 大部分的浏览器不支持
            // 在ES标准中static只支持定义静态方法不支持定义静态变量            
            // 静态属性
            // static num = 666;

            // 静态方法
            static run() {
                console.log("run");
            }
        }
        
        // 定义静态属性的正确方式!
        Person.num = 666;
    
        // 2.通过类创建对象
        // let p = new Person();
        let p = new Person("zs", 18);
        p.say();
        console.log(Person.num);
        Person.run();

默认say方法会添加到原型上

        class Person{
            constructor(myName, myAge){
                this.name = myName;
                this.age = myAge;
                this.hi = function () {
                    console.log("hi");
                }
            }
            // 默认会添加到原型上
            say(){
                console.log("hi");
            }
        }
        let p = new Person("lnj", 34);
        console.log(p);

        function Person(myName, myAge) {
            // 实例属性
            this.name = myName;
            this.age = myAge;
            // 实例方法
            this.hi = function () {
                console.log("hi");
            }
        }
        // 定义Person原型的属性与方法
        // 方法1
        // Person.prototype.type = "人";
        // Person.prototype.say = function () {
        //     console.log(this.name, this.age);
        // };
        // 方法2
        Person.prototype = {
            constructor: Person,
            type: "人",
            say: function () {
                console.log(this.name, this.age);
            }
        };

        class Person{
            constructor(myName, myAge){
                this.name = myName;
                this.age = myAge;
                this.hi = function () {
                    console.log("hi");
                }
            }
            run(){
                console.log("run");
            }
        }
        
        // 方法1可行
        // Person.prototype.type = "人";
        // Person.prototype.say = function () {
        //     console.log(this.name, this.age);
        // };

        // 方法2不可行
        let obj = {
            constructor: Person,
            type: "人",
            say: function () {
                console.log(this.name, this.age);
            }
        };
        Person.prototype = obj;

        let p = new Person("lnj", 34);
        console.log(p);

        // 注意点:
        // 如果通过class定义类, 那么不能自定义这个类的原型对象
        // 如果想将属性和方法保存到原型中, 只能动态给原型对象添加属性和方法

(3).如何通过类创建一个对象 

  • 不过就是创建结构体的时候, 根据每个对象的特征赋值不同的属性罢了
    // 通过构造函数创建对象
    // new 的执行过程
    // 1 在内存中创建了一个空的对象
    // 2 让构造函数中的this指向刚刚创建的对象
    // 3 执行构造函数,在构造函数中设置属性和方法(当然也可以做其它事情)
    // 4 返回了当前对象
    var p1 = new Person("lnj", 33);
    var p2 = new Person("zq", 18);
    p1.say();
    p2.say();

10.ES6继承

  • (1).ES6之前的继承
    • 1.1 在子类中通过 call / apply 方法借助父类的构造函数
    • 1.2 将子类的原型对象设置为父类的实例对象
        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }
        Person.prototype.say =  function () {
            console.log(this.name, this.age);
        }
        function Student(myName, myAge, myScore) {
            // 1.在子类中通过call/apply方法借助父类的构造函数
            Person.call(this, myName, myAge);
            this.score = myScore;
            this.study = function () {
                console.log("day day up");
            }
        }
        // 2.将子类的原型对象设置为父类的实例对象
        Student.prototype = new Person();
        Student.prototype.constructor = Student;

        let stu = new Student("zs", 18, 99);
        stu.say();
  • (2).ES6开始的继承
    • 2.1 在子类后面添加 extends 并指定父类的名称
    • 2.2 在子类的constructor构造函数中通过 super 方法借助父类的构造函数
        class Person{
            constructor(myName, myAge){
                // this = stu;
                this.name = myName;       // stu.name = myName;
                this.age = myAge;         // stu.age = myAge;
            }
            say(){
                console.log(this.name, this.age);
            }
        }
        // 继承
        class Student extends Person{
            constructor(myName, myAge, myScore){
                // Person.call(this, myName, myAge);
                super(myName, myAge);
                this.score = myScore;
            }
            study(){
                console.log("day day up");
            }
        }
        let stu = new Student("zs", 18, 98);
        stu.say();

11. 对象相关操作

(1). instanceof 关键字

  • 1.1 什么是instanceof关键字
    • instanceof:用于判断 "对象" 是否是指定构造函数的 "实例"
        class Person{
            name = "lnj";
        }
        let p = new Person();
        console.log(p instanceof Person);         // true

        class Cat{
            name = "mm";
        }
        let c = new Cat();
        console.log(c instanceof Person);         // false

1.2 注意点

只要 构造函数的原型对象出现在实例对象的原型链中都会返回 true

        function Person(myName) {
            this.name = myName;
        }
        function Student(myName, myScore) {
            Person.call(this, myName);
            this.score = myScore;
        }
        Student.prototype = new Person();
        Student.prototype.constructor = Student;

        let stu = new Student();
        console.log(stu instanceof Person);     // true

(2). isPrototypeOf 属性

  • 1.什么是isPrototypeOf属性
    • isPrototypeOf:用于判断 一个对象是否是另一个对象原型
        class Person{
            name = "lnj";
        }
        let  p = new Person();
        console.log(Person.prototype.isPrototypeOf(p));     // true

        class Cat{
            name = "mm";
        }
        console.log(Cat.prototype.isPrototypeOf(p));        // false
  • 2.isPrototypeOf注意点
    • 2.1 只要调用者在传入对象的原型链上都会返回true
        function Person(myName) {
            this.name = myName;
        }
        function Student(myName, myScore) {
            Person.call(this, myName);
            this.score = myScore;
        }
        Student.prototype = new Person();
        Student.prototype.constructor = Student;

        let stu = new Student();
        console.log(Person.prototype.isPrototypeOf(stu));     // true    

(3).如何获取对象的类型?

  • let p = new Person();        // 创建对象
  • console.log(p.constructor.name);                 
        /*
        let obj = new Object();  --> object
        let arr = new Array(); --> Array
        let p = new Person(); --> Person
        */
        let obj = new Object();
        console.log(typeof obj);                 // object

        let arr = new Array();
        // console.log(typeof arr);              // object
        console.log(arr.constructor.name);       // Array


        function Person() {
            // let obj = new Object();
            // let this = obj;
            this.name = "lnj";
            this.age = 34;
            this.say = function () {
                console.log(this.name, this.age);
            }
            // return this;
        }
        let p = new Person();
        // console.log(typeof p);             // object
        console.log(p.constructor.name);      // Person

(4).如何判断某一个对象是否拥有某一个属性?

  • 方式一:in
    • 特点: 只要中或者原型对象中有, 就会返回true
        class Person{
            name = null;
            age = 0;
        }
        Person.prototype.height = 0;
        let p = new Person();
        // in的特点: 只要类中或者原型对象中有, 就会返回true
        console.log("name" in p);           // true
        console.log("width" in p);          // false
        console.log("height" in p);         // true

    
  • 方式二:hasOwnProperty
    •  只会去中查找有没有, 不会去原型对象中查找
        class Person{
            name = null;
            age = 0;
        }
        Person.prototype.height = 0;     
        let p = new Person();
        // 特点: 只会去类中查找有没有, 不会去原型对象中查找
        console.log(p.hasOwnProperty("name"));         // true
        console.log(p.hasOwnProperty("height"));       // false

(5).对象的增删改查

  • p["name"] = "zs";                     // 增
  • delete p["name"];                    // 删

  • p["name"] = "ww";                  // 改

  • console.log(p["name"]);         // 查

        class Person{}            // 定义类
        let p = new Person();     // 通过类创建对象
        
        // 增加(C)
        // p.name = "lnj";
        p["name"] = "zs";
        // p.say = function(){
        //     console.log("hello world");
        // }
        p["say"] = function(){
            console.log("hello world");
        }
        // console.log(p);

        // 修改(U)
        // p.name = "lnj";
        p["name"] = "ww";
        console.log(p.name);
        // p.say = function(){
        //     console.log("hi");
        // }
        p["say"] = function(){
            console.log("hi");
        }
        p.say();

        // 查询(D)
        // console.log(p.name);
        console.log(p["name"]);
        p.say();

        // 删除(R)
        // delete p.name;
        delete p["name"];
        // delete p.say;
        delete p["say"];
        console.log(p);

(6).对象的遍历

  • 1.在JavaScript中对象和数组一样是可以遍历的
  • 2.什么是对象的遍历?
    • 对象的遍历就是依次取出对象中所有的属性和方法
  • 3.如何遍历一个对象?
    • 在JS中可以通过高级for循环来遍历对象
    • for (let key in obj) { }           # 将指定对象中 所有的 属性和方法 的名称取出来了依次的赋值给 key 这个变量
  • 3.1 通过 class 关键字 创建类
        class Person{
            constructor(myName, myAge){
                this.name = myName;
                this.age = myAge;
            }
            // 注意点: ES6定义类的格式, 会将方法默认放到原型对象中
            say(){
                console.log(this.name, this.age);
            }
        }
        let p = new Person("lily", 18);
        console.log(p);
        for(let key in p){
            // name age ; 没有say方法,因为ES6定义类的格式, 会将方法默认放到原型对象中
            console.log(key);
        }

3.2 通过 构造函数 创建类

        function Person(myName, myAge){
            this.name = myName;
            this.age = myAge;
            this.say = function(){
                console.log(this.name, this.age);
            }
        }
        let p = new Person("LNJ", 34);
        console.log(p);
        for(let key in p){
            // 取出对象中的所有属性与方法; name age say
            // console.log(key);
            // 取出对象中的所有属性与方法的取值
            // console.log(p[key])          // LNJ 34  f(){console.log(this.name, this.age);}
            // 注意点: 以下代码的含义取出p对象中名称叫做key的属性的取值
            // console.log(p.key);          // undefined

            // 判断p[key]如果是函数,则跳出本次循环,不输出
            if (p[key] instanceof Function) {
                continue;
            }
            console.log(p[key]);
        }

(7).对象的解构赋值

1.解构赋值

  • 对象的解构赋值和数组的解构赋值 
  • 除了符号不一样, 其它的一模一样
    • 数组解构,使用:[ ]
    • 对象解构,使用:{ }
        let obj = {
            name: "lnj",
            age: 34
        }

        // let name = obj.name;
        // let age = obj.age;

        // let {name, age} = obj;
        let {name, age} = {name: "lnj",age: 34};
        console.log(name, age);

        let {name} = {name: "lnj",age: 34};
        console.log(name);

        let {name, age, height} = {name: "lnj",age: 34};
        console.log(name, age, height);   // lnj  34  undefined

        let {name, age, height = 1.80} = {name: "lnj",age: 34};
        console.log(name, age, height);

        // 注意点: 在对象解构赋值中, 左边的变量名称必须和对象的属性名称一致, 才能解构出数据
        let {a, b} = {name: "lnj",age: 34};
        console.log(a, b);      // undefined undefined

        let {age} = {name: "lnj",age: 34};
        console.log(age);       // 34

2.应用场景

  • 数组
        let arr = [1, 3];
        // function sum(a, b) {
        function sum([a, b]) {
            return a + b;
        }
        // let res = sum(arr[0], arr[1]);
        let res = sum(arr);
        console.log(res);
  • 对象
        let obj = {
            name: "lnj",
            age: 34
        }
        // function say(name, age) {
        function say({name, age}) {
            console.log(name, age);
        }
        // say(obj.name, obj.age);
        say(obj);

12.深拷贝与浅拷贝

  • (1).深拷贝
    • 修改新变量的值不会影响原有变量的值
    • 默认情况下基本数据类型都是深拷贝
        // 深拷贝
        let num1 = 123;
        let num2 = num1;
        num2 = 666;             // 修改新变量的值
        console.log(num1);      // 123
        console.log(num2);      // 666

  • (2).浅拷贝
    • 修改新变量的值会影响原有的变量的值
    • 默认情况下引用类型都是浅拷贝
        // 浅拷贝
        class Person{
            name = "lnj";
            age = 34;
        }
        let p1 = new Person();
        let p2 = p1;
        p2.name = "zs";         // 修改变量的值
        console.log(p1.name);   // zs
        console.log(p2.name);   // zs

(3).对象的深拷贝

  • 默认情况下对象是浅拷贝
        class Person{
            name = "lnj";
            age = 34;
        }
        let p1 = new Person();
        // 默认对象是浅拷贝
        // let p2 = p1;
        // p2.name = "zs"; // 修改变量的值

        // 如何将对象设置为深拷贝?
        let p2 = new Object();
        // 方式1
        // p2.name = p1.name;
        // p2.age = p1.age;
        // p2.name = "zs";

        // 方式2
        // for(let key in p1){
        //     p2[key] = p1[key];
        // }
        // console.log(p2);
        // p2.name = "zs";

        // 方式3
        // assign:将第二个参数的对象的属性和方法拷贝到第一个参数的对象中
        Object.assign(p2, p1);
        // console.log(p2);
        p2.name = "zs";
        console.log(p1.name);
        console.log(p2.name);
        // 注意点: 只有被拷贝对象中所有属性都是基本数据类型, 以上代码才是深拷贝

  • 存在弊端:被拷贝对象中的属性如果存在引用类型,那么深拷贝就会失效
  • 优化
        class Person{
            name = "lnj";
            // 非基本数据类型
            // 对象object
            cat = {
                age : 3
            };
            // 数组Array
            scores = [1, 3, 5];
        }
        let p1 = new Person();
        let p2 = new Object();
        depCopy(p2, p1);
        // console.log(p2);

        p2.cat.age = 666;
        console.log(p1.cat.age);
        console.log(p2.cat.age);

        function depCopy(target, source) {
            // 1.通过遍历拿到source中所有的属性
            for(let key in source){
                // console.log(key);
                // 2.取出当前遍历到的属性对应的取值
                let sourceValue = source[key];
                // console.log(sourceValue);
                // 3.判断当前的取值是否是引用数据类型
                if(sourceValue instanceof Object){
                    // console.log(sourceValue.constructor);
                    // console.log(new sourceValue.constructor);
                    let subTarget = new sourceValue.constructor;
                    target[key] = subTarget;
                    depCopy(subTarget, sourceValue);
                }else{
                    target[key] = sourceValue;
                }
            }
        }

  • 代码的调试

十三、数组的高级使用

1.数组的遍历

(1).利用传统循环来遍历数组

        let arr = [1, 3, 5, 7, 9];
        for(let i = 0; i < arr.length; i++){
            console.log(arr[i]);
        }

(2).利用 for in 循环来遍历数组

  • 注意点:在企业开发中不推荐使用forin循环来遍历数组
  • 因为 for in 循环是专门用于遍历对象的, 但是对象的属性是无序的, 所以 for in 循环就是专门用于遍历无序的东西的
      for(let key in arr){
            // console.log(key);
            console.log(arr[key]);
        }

(3).利用ES6中,推出的 for of 循环来遍历数组

        for(let value of arr){
            console.log(value);
        }

(4).还可以利用Array对象的 forEach 方法来遍历数组

  • currentValue        # 当前遍历到的元素
  • currentIndex        # 当前遍历到的索引
  • currentArray        # 当前遍历到的数组
        // forEach方法会自动调用传入的函数
        // 每次调用都会将当前遍历到的元素和当前遍历到的索引和当前被遍历的数组传递给这个函数
        arr.forEach(function (currentValue, currentIndex, currentArray) {
            // console.log(currentValue, currentIndex, currentArray);
            console.log(currentValue);
        });

2.数组的查找

        // 从左往右查找, 找到返回索引, 找不到返回-1
        let index1 = arr.indexOf(6);
        console.log(index1); // 2
        // 从右至左查找, 找到返回索引, 找不到返回-1
        let index2 = arr.lastIndexOf(6);
        console.log(index2); // 4
        // 从左往右查找, 找到返回true, 找不到返回false
        let result = arr.includes(6);
        console.log(result); // true

(1).数组的findIndex方法

  • findIndex方法,定制版的 indexOf
    • 找到,返回 索引
    • 找不到,返回 -1
        let index = arr.findIndex(function (currentValue, currentIndex, currentArray) {
            // console.log(currentValue, currentIndex, currentArray);
            // if(currentValue === 6){
            if(currentValue === 10){
                return true;
            }
        });
        console.log(index);

(2).数组的find方法

  • find方法如果找到了,就返回找到的 元素
  • 如果找不到,就返回 undefined
        let value = arr.find(function (currentValue, currentIndex, currentArray) {
            // console.log(currentValue, currentIndex, currentArray);
            // if(currentValue === 6){
            if(currentValue === 10){
                return true;
            }
        });
        console.log(value);
        // findIndex实现
        Array.prototype.myFindIndex = function (fn) {
            for(let i = 0; i < this.length; i++){
                let result = fn(this[i], i, this);
                if(result){
                    return i;
                }
            }
            return -1;
        }

        // findIndex实现
        Array.prototype.myFind = function (fn) {
            for(let i = 0; i < this.length; i++){
                let result = fn(this[i], i, this);
                if(result !== undefined){
                    return result;
                }
            }
            return undefined;
        }

3.数组的过滤与映射

(1).数组的filter方法

  • 将满足条件的元素,添加到一个新的数组中
  • 需求:将数组中的偶数提取出来,放入一个新的数组中
        let arr = [1, 2, 3, 4, 5];
        let newArray = arr.filter(function (currentValue, currentIndex, currentArray) {
            // console.log(currentValue, currentIndex, currentArray);
            if(currentValue % 2 === 0){
                return true;
            }
        });
        console.log(newArray);     // [2, 4]

(2).数组的map方法

  • 将满足条件的元素,映射到一个新的数组中
        let newArray = arr.map(function (currentValue, currentIndex, currentArray) {
            // console.log(currentValue, currentIndex, currentArray);
            if(currentValue % 2 === 0){
                return currentValue;
            }
        });
        console.log(newArray);     // [undefined, 2, undefined, 4, undefined]
        // filter实现
        Array.prototype.myFilter = function (fn) {
            let newArray = [];
            for(let i = 0; i < this.length; i++){
                let result = fn(this[i], i, this);
                if(result){
                    newArray.push(this[i]);
                }
            }
            return newArray;
        }

        // map实现
        Array.prototype.myMap = function (fn) {
            let newArray = new Array(this.length);
            newArray.fill(undefined);
            for(let i = 0; i < this.length; i++){
                let result = fn(this[i], i, this);
                if(result !== undefined){
                    newArray[i] = result;
                }
            }
            return newArray;

4.数组的删除与清空

(1).删除数组元素的两种方式

  • splice
  • delete
        let arr = [1, 2, 3, 4, 5];
        // 1.从索引为2的元素开始,删除1个元素
        arr.splice(2, 1);
        // 2.删除索引为2的元素
        delete arr[2];

(2).遍历数组的同时删除数组中所有元素

  • 方式1
        let arr = [1, 2, 3, 4, 5];
        console.log(arr);

        //       i   arr.length
        //       0     0 < 5
        //       1     1 < 4
        //       2     2 < 3
        //       3     3 < 2

        for(let i = 0; i < arr.length; i++){
            console.log(arr.length);     // 5, 4, 3
            arr.splice(i, 1); 
        }
        console.log(arr);

  • 发现并没有删除干净
  • 原因:删除元素后,数组的长度也会相应发生改变
  • 方式2
        let arr = [1, 2, 3, 4, 5];
        console.log(arr);
        // 将数组的长度存储到len变量中,防止长度发生变化
        let len = arr.length;
        for (let i = 0; i < len; i++) {
            console.log(len);
            arr.splice(i, 1);
        }
        console.log(arr);

  • 发现依旧没有删除干净
  • 原因:删除元素后,数组元素的位置也会向前挪动

  • 正确方式1
        let arr = [1, 2, 3, 4, 5];
        console.log(arr);
        let len = arr.length;
        // 从最后一个元素开始删除
        for (let i = len - 1; i >= 0; i--) {
            console.log(len);
            arr.splice(i, 1);
        }
        console.log(arr);

  • 正确方式2
            let arr = [1, 2, 3, 4, 5];
            console.log(arr);      
            for(let i = 0; i < arr.length; i++){
            console.log(arr.length);
            // 注意点: 通过delete来删除数组中的元素, 数组的length属性不会发生变化, 元素位置也不会上移
            delete arr[i];
        }
        console.log(arr);

5.数组的排序

(1).数组元素均为字符串

  • 升序
  • 方法1:
        let arr = ["c", "a", "b"];
        arr.sort();     // 顺序
        console.log(arr);
  • 方法2:
        let arr = ["c", "a", "b"];
        arr.sort(function (a, b) {
                if (a > b) {
                    return 1;
                } 
                else if (a < b) {
                    return -1;
                } 
                else {
                    return 0;
                }
            });
            console.log(arr);
  • 降序
        let arr = ["c", "a", "b"];
        arr.sort(function (a, b) {
                if (a > b) {
                    return -1;
                } 
                else if (a < b) {
                    return 1;
                } 
                else {
                    return 0;
                }
            });
            console.log(arr);
  • 规律
    • 如果 compareFunction(a, b) 小于 0 , a 会被排列到 b 之前(降序)
    • 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变
    • 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前(升序
    • 注意点:如果元素是字符串类型, 那么比较的是字符串的Unicode编码

(2).数组元素均为数字

  • 升序
  • 方法1:
        let arr = [3, 4, 2, 5, 1];
        arr.sort();
        console.log(arr);
  • 方法2:
        let arr = [3, 4, 2, 5, 1];
        arr.sort(function (a, b) {
            return a - b;
        });
        console.log(arr);
  • 降序
        let arr = [3, 4, 2, 5, 1];
        arr.sort(function (a, b) {
            return b - a;
        });
        console.log(arr);
  • 规律
    • 如果数组中的元素是数值类型
    • 如果需要升序排序,那么就返回 a - b
    • 如果需要降序排序,那么就返回 b - a

(3).数组元素均为数值型的字符串

  • 根据字符串的长度进行排序
  • 升序
        let arr = ["1234", "21", "54321", "123", "6"];
        arr.sort(function (str1, str2) {
            return str1.length - str2.length;
        });
        console.log(arr);
  • 降序
        let arr = ["1234", "21", "54321", "123", "6"];
        arr.sort(function (str1, str2) {
            return str2.length - str1.length;
        });
        console.log(arr);

(4).数组元素均为对象

  • 根据对象的age进行排序
  • 升序
        let students = [
            {name: "zs", age: 34},
            {name: "ls", age: 18},
            {name: "ww", age: 22},
            {name: "mm", age: 28},
        ];
        students.sort(function (o1, o2) {
            return o1.age - o2.age;
        });
        console.log(students);
  • 降序
        let students = [
            {name: "zs", age: 34},
            {name: "ls", age: 18},
            {name: "ww", age: 22},
            {name: "mm", age: 28},
        ];
        students.sort(function (o1, o2) {
            return o2.age - o1.age;
        });
        console.log(students);

十四、字符串的常用方法

1.字符串与数组的共有方法

  • 在js中字符串可以看做一个特殊的数组, 所以大部分数组的属性/方法字符串都可以使用

 (1).获取字符串的长度

        let str = "abcd";
        console.log(str.length);

(2).根据索引获取某个字符

        let str = "abcd";
        let ch = str[1];
        let ch = str.charAt(1);
        console.log(ch);

(3).字符串的查找

        let str = "vavcd";

        // indexOf: 从左往右查找,找到则返回对应的索引,找不到则返回-1
        console.log(str.indexOf("v"));

        // lastIndexOf: 从右往左查找,找到则返回对应的索引,找不到则返回-1
        console.log(str.lastIndexOf("v"));

        // includes: 从左往右查找,找到则返回true,找不到则返回false
        console.log(str.includes("v"));

(4).字符串的拼接

        let str1 = "www";
        let str2 = "it666";
        let str = str1 + str2; // 推荐
        let str = str1.concat(str2);
        console.log(str);

(5).截取子串

        let str = "abcdef";
        let subStr = str.slice(1, 3);        //截取索引为 1 到 索引为3,但不包括3 (bc)  字符
        let subStr = str.substring(1, 3);    //截取索引为 1 到 索引为3,但不包括3 (bc)  字符
        let subStr = str.substr(1, 3);       //从索引为 1 开始,截取3个(bcd)字符
        console.log(subStr);

2.字符串特有的方法

(1).字符串切割

        // let arr = [1, 3, 5];
        // 数组的连接
        // let str = arr.join("-");
        // console.log(str);

        // let str = "1-3-5";
        // 字符串的分割
        // let arr = str.split("-");
        // console.log(arr);

(2).判断是否以指定字符串开头 

        let str = "http://www.it666.com";
        let result = str.startsWith("www");
        console.log(result);

(3).判断是否以指定字符串结尾 

        let str = "lnj.jpg";
        let result = str.endsWith("png");
        console.log(result);

(4).字符串模板 

        // 定义字符串的三种方式
        let str = "";
        let str = '';
        let str = `www.it666.com`;
        console.log(str);
        console.log(typeof str);
        let name = "lily";
        let age = 18;

        // 方式1:
        let strs1 = "我的名字是"+ name + ",我的年龄是" + age;
        console.log(strs1);

        // 方式2: (推荐)
        let strs2 = `我的名字是${name},我的年龄是${age}`;
        console.log(strs2);

十五、基本数据类型和基本包装类型

(1).有哪些基本数据类型?

  • 字符串类型      string
  • 数值类型         number
  • 布尔类型         boolean
  • 空类型             null
  • 未定义类型     undefined

(2).通过字面量创建的基本数据类型的数据都是常量

(3).常量注意点

  • 常量是不能被修改的,每次修改或者拼接都是生成一个新的
        //将字符串中的b替换为m
        let str = "abc";
        // str[1] = "m";        // abc
        // replace不会更改原有字符串,而是新增一个字符串
        let newStr = str.replace("b", "m");
        console.log(str);       // abc
        console.log(newStr);    // amc
        let str1 = "www";
        let str2 = "it666";
        // 字符串的拼接,也是生成一个新的字符串
        let str3 = str1 + str2;
        console.log(str1);
        console.log(str2);
        console.log(str3);

(4).基本数据类型的特点

  • 没有属性和方法
        let str = "lnj";
        str.age = 34;
        str.say = function () {
            console.log("hello");
        }
        console.log(str.age);     // undefined
        str.say();               // str.say is not a function
        let str = "www.it666.com";
        // string类型(基本数据类型)可以使用length方法等,是因为
        // 在运行时,系统自动将string字符串类型,包装成了对象类型
        // let str = new String(str);
        console.log(str.length);
        arr = str.split(".");
        console.log(arr);

十六、JS自带的对象

  • JavaScript中提供三种自带的对象,分别是本地对象、内置对象、宿主对象

(1).什么是宿主

  • 宿主就是指JavaScript运行环境,js可以在浏览器中运行,也可以在服务器上运行(nodejs)

(2).本地对象

  • 与宿主无关,无论在浏览器还是服务器中都有的对象,就是ECMAScript标准中定义的(构造函数)。
  • 在使用过程中需要我们手动 new 创建,例如:Boolean、Number、String、Array、Function、Object、Date、RegExp等。

(3).内置对象

  • 与宿主无关,无论在浏览器还是服务器中都有的对象,ECMAScript已经帮我们创建好的对象
  • 在使用过程中无需我们手动 new 创建,例如:Global、Math、JSON

(4).宿主对象

  • 对于嵌入到网页中的JS来说,其宿主就是浏览器, 所以宿主对象就是浏览器提供的对象,包含: WindowDocument等。 所有的DOM和BOM对象都属于宿主对象。

(5).自定义对象

  • 我们自己编写的类创建的对象

(6).Math内置对象

  • Math.floor()             # 向下取整
  • Math.ceil()              # 向上取整
  • Math.round()          # 四舍五入
  • Math.abs()             # 绝对值
  • Math.random()      # 生成随机数
        // Math.floor()    向下取整
        // 直接砍掉所有的小数位就是向下取整
        // let num = 3.9;
        // let value = Math.floor(num);
        // console.log(value);

        // Math.ceil()     向上取整
        // 只要有小数位就会给整数位+1, 然后砍掉所有小数位
        // let num = 3.9;
        // let value = Math.ceil(num);
        // console.log(value);

        // Math.round()    四舍五入
        // 和小学数学一样, 如果小数位满5就会进1
        // let num = 3.5;
        // let value = Math.round(num);
        // console.log(value);

        // Math.abs()      绝对值
        // 和小学数学一样, 统一变为正数
        // let num = -3;
        // let value = Math.abs(num);
        // console.log(value);
  • 生成0-1的随机数(包含0,但不包含1)
        // 生成一个0~1的随机数, 但是不包括1
        let value = Math.random();
        console.log(value);
  • 生成1-10的随机数(包含1,且包含10)
        function getRandomIntInclusive(min, max) {
            min = Math.ceil(min);
            max = Math.floor(max);
            return Math.floor(Math.random() * (max - min + 1)) + min;   //含最大值,含最小值
        }
        let value = getRandomIntInclusive(1, 10);
        console.log(value);

 

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐