JavaScript 个人学习笔记

JavaScript介绍

  • JavaScript介绍

    为什么要学习Javascript

    我们之前学的HTML只能完成页面的结构,CSS完成页面的样式, HTML+CSS静态页面,没有任何页面效果的,页面也没有行为

    如果想要页面有行为,比如某个元素可以被点击, 那么就需要学习JS,给页面某个元素添加行为

  • JavaScript是什么

    官方概念:这是一个跨平台的脚本语言

    平台这里指的是操作系统(window系统, mac系统 , linux系统)

    跨平台的意思就是在不同的操作系统下, 都可以运行

    脚本语言意思是js不能单独运行,需要有一个运行环境,这个运行环境就是js的运行环境,比如浏览器环境,node.js环境

    为什么js可以跨平台,因为在不同操作系统下, 都有浏览器, 有浏览器就可以运行JS代码

  • Javascript的发展历史

    1995年,由一个名为布莱登·艾奇创造的脚本语言,原名livescript; 以前js为了验证表单信息

    JavaScript现在的意义(应用场景)

    JavaScript 发展到现在几乎无所不能。

    1. 网页特效 比如图片轮播,选项卡....
    2. 服务端开发(Node.js)
    3. 命令行工具(Node.js)
    4. 桌面程序(Electron)
    5. App(Cordova)
    6. 控制硬件-物联网(Ruff)
    7. 游戏开发(cocos2d-js , 贪吃蛇 , 微信打飞机....)
    8. 表单验证
    9. 与服务器进行交互(ajax->谷歌于2005年推出的)
  • JavaScript和HTML、CSS的区别

    1. HTML:提供网页的结构,提供网页中的内容
    2. CSS: 用来美化网页
    3. JavaScript: 可以用来控制网页内容,控制样式, 给网页增加动态的效果
  • JavaScript的组成

    • ECMAScript - JavaScript的核心

      ECMA 欧洲计算机制造联合会

      网景:JavaScript

      微软:JScript

      定义了JavaScript的语法规范

      JavaScript的核心,描述了语言的"基本语法"和"数据类型",ECMAScript是一套标准,定义了一种语言的标准与具体实现无关

    • DOM - 文档对象模型

      一套操作页面元素的API

      DOM可以把HTML看做是文档树,通过DOM提供的API可以对树上的节点进行操作

    • BOM - 浏览器对象模型

      一套操作浏览器功能的API

      通过BOM可以操作浏览器窗口,比如:控制浏览器跳转、获取分辨率、获取浏览器的可视区域大小等

  • JS注释分为单行注释与多行注释(块注释)

    被JS注释的代码,不会执行

    // 单行注释  ctrl+/
    /* 多行注释 */
    

JS代码的语法规则

  1. JavaScript严格区分英文字母大小写
console.log(123);
console.Log( 123 );// 报错
console.log(true);
console.log(True);// 报错
console.log(nUll);// 报错
  1. JavaScript脚本程序需嵌入在HTML文件中才可以执行代码(当然现在也可以用node.js执行单独的js文件)

  2. JavaScript脚本程序可以独立保存为一个外部文件 这个文件是不能自己运行的它必须要依赖于HTML文件或者node.js环境

  3. JavaScript脚本程序中不能包含HTML标记代码

    <h2>12312312</h2>
    
  4. 每条语句末尾如果加分号一定是"英文"状态下的分号(😉,最好加分号,不要省略

    console.log(123)
    // console.log(456);// 报错,中文状态的分号
    console.log(789);
    console.log(123456789);
    
  5. 一行写了多条JS语句 这个时候每一条语句就必须要加分号(最后一条语句可以不加分号)

console.log(123);console.log(456);console.log(789);
console.log(123)console.log(456);console.log(789);// 报错

js常用输出语句

js常用输出语句有三个

  1. document.write("内容")

    • 把指定内容输出到body标签中

    • 可以解析HTML标签

  2. console.log("内容")

    • 把指定内容输出到浏览器调试工具的控制台中

    • 无法解析HTML标签

    • 我们经常会使用console.log调试bug

  3. window.alert("内容")

    • 以弹窗的形式输出内容

    • 无法解析HTML标签

    • window对象的属性或者方法都可以省略window对象也就是window.alert()可以直接写成alert()

    • 注意: 如果"内容"是纯数字或者js中的关键字 可以不加引号, 其他内容都需要加一对双引号或者单引号包裹

window.prompt()

  • window.prompt() 用户输入对话框

    这个方法是属性window对象的方法 所以window可以省略

    这个方法是用来向浏览器中弹出一个用户输入对话框

  • 语法:

    window.prompt(text,defaultText)
    

    第一个参数:text表示提示信息

    第二个参数:defautlText 表示输入框的中默认文本 默认值

    但是注意:这两个参数都可以省略不写!

  • 这个方法有两个按钮 :确定按钮、取消按钮

    当用户点击"确定"按钮时会得到一个"String类型"的数据

    当用户点击"取消"按钮会得到一个关键字 null

window.prompt();

window.prompt("请输入您的银行卡密码");

window.prompt("请输入您的银行卡密码" , 123456 );

JavaScript的书写位置

js一般写在script标签, script也属于HTML标签, 所以script标签可以出现页面文档的任何地方和出现任意次

script标签一般都是写在head标签中

  • JavaScript的书写位置-嵌入式就是把js代码写script标签中
<script>
    console.log(111);
</script>
  • JavaScript的书写位置-外链式就是把js代码写在单独的js文件中

    外链式js文件是通过script标签的src属性引入

    注意: 不要在引入js文件的script标签中,写js代码; 因为写的js代码不会生效

<script src="js文件所在路径"></script> 
  • JavaScript的书写位置-行内式,就是把js代码写在标签的事件属性中
<button onclick="console.log(123);" onmouseover="console.log('abc')" onmouseout="console.log('qwe')">按钮</button>

变量

概念:变量就是可以变化的量, 变量是一个容器, 方便存取数据

创建变量(定义变量,声明变量)

  • 语法一 定义单个变量, 变量不赋值

    var 变量名;

    var a;
    

    使用变量,就是直接使用变量名即可,变量名就可以代表这个变量的值

    console.log("a=>", a);
    
  • 语法二 定义多个变量, 变量不赋值
    var 变量名1, 变量名2, 变量名3 ... ;

    var x, y, z;
    
  • 语法三 定义单个变量, 变量并赋值
    var 变量名 = 值;

    var b = 20;
    console.log("b=>", 20);
    
  • 语法四 定义多个变量, 变量并赋值
    var 变量名1 = 值1, 变量名2 = 值2, 变量名3 = 值3 ...;

    var num1 = 30, num2 = 40, num3 = 50;
    console.log("num1=>", num1);
    console.log("num2=>", num2);
    console.log("num3=>", num3);
    

    注意: 如果变量名声明了,但是没有赋值,那么这个变量的值是会undefined
    注意: = 号是赋值运算符, 把 = 号右边的值,赋值给左边的变量; 那么以后这个变量名就可以代表这个值

  • 修改变量的值, 就是更新变量的值, 通过 = 号 赋值运算符

    var age = 18;
    console.log("age=>", age);
    // 更新age的值(修改age值)
    age = 20;
    age = 30;
    age = 40;
    console.log("age=>", age);
    

变量的命名规则和规范

  1. 由字母(AZaz)、数字(0~9)、下划线(_)、$符号组成,不能以数字开头

    var @_@ = "abc";
    console.log( @_@ ); //报错
    
    var a$B_5 = 123;
    console.log( a$B_5 );
    

    多个单词可以使用驼峰法

    var userName = "zhangsan";
    console.log(userName);
    

    多个单词可以使用下划线连接法

    var user_name = "lisisi";
    console.log(user_name);
    
  2. 不能是JS中关键字和保留字,例如:for、while等(其中变量名为name需要注意,不会报错,但是不推荐使用,因为name是window对象下的一个属性, 不管设置的属性值是什么, 都会被加上双引号,成为字符串类型)

    /* var for = 456;
    console.log( for ); */
    
    /* var name = 123;
    console.log(typeof name);
    var name2 = 123;
    console.log(typeof name2); */
    
  3. 区分大小写

    var age = 18;
    var Age = 20;
    console.log(age);
    console.log(Age);
    console.log(aGe);
    
  • 规范 - 建议遵守的,不遵守不会报错

    1. 变量名需要有意义,希望大家在声明变量的时候变量名要做到"见名知意"

    2. 变量名可以由多个英文单词组成 建议使用下划线连接法 或者是驼峰法

      • 下划线连接法 每一个单词之间使用下划线进行连接 比如:get_user-name

      • 驼峰法 第一个英文单词的首字母全部小写 第二个开始其它的英文单词首字母大写 比如:getUserName

变量的数据类型

  • 为什么变量需要有数据类型

    变量主要是用于存储数据的,现实生活中的数据有很多种比如有数值、有字母等等 那么为了将这些数据进行分门别类,所以就引出了变量的数据类型。

  • JavaScript是一种弱类型的语言。

    在声明变量的时候不需要指定变量的数据类型强类型的语言,在声明变量的时候一定要先指明这个变量的数据类型是什么 并且值也是这个数据类型

    因为JS是一门弱类型的脚本语言,"在声明变量的时候不需要先声明变量的类型"。但是它也是有类型,JS变量的类型是由"变量的值"来决定 。

  • 变量的数据类型分为:两大类、七小种

    • 两大类:
      1. 基本数据类型(标量数据类型,值类型)
      2. 复合数据类型(引用数据类型,对象类型)!
    • 七小种:
      1. 基本数据类型: 字符串,数值,布尔,未定义型,空型 (只能存储一个值)
      2. 复合数据类型: 数组,对象 (至少存储一个值,可以存储多个值)

获取变量的数据类型

在JavaScript中有一个内置的函数可以检测变量的数据类型

语法一: typeof( 值或者变量名 )

语法二: typeof 值或者变量名

var a = 10
console.log(typeof (a));
console.log(typeof a);
console.log(typeof true);
console.log(typeof null);
console.log(typeof undefined);

基本(标量)数据类型

字符串(string)

  • 只要值被一对引号(不管双引号还是单引号)包裹, 那么就是字符串
console.log("abc", typeof ("abc"));
console.log('abc', typeof ('abc'));
console.log("123", typeof ("123"));
  • 字符串中不允许双包双,单包单; 但是允许双包单, 允许单包双
console.log("我是一个'帅气'的人");
console.log('我是一个"帅气"的人');
console.log("");
  • 也可以使用转义符 转义符是反斜杠\ 进行一些符号的转义
// console.log( \ );// 报错
// console.log( "\" );// 报错
// console.log( '\' );// 报错
// console.log( \\ );// 报错
console.log("\\"); // 不报错
console.log("我是一个\"帅气\"的人");
console.log('我是一个\'帅气\'的人');
  • 获取字符串的长度

    通过 字符串变量名.length 可以获取字符串的总字符个数

var str = "abc123张三疯@_@";
console.log(str, str.length);
  • 字符串拼接

    字符串拼接就是把小字符串拼成大字符串, 每次拼接都会拼接到后面

    字符串拼接是使用+号,但是在js中的+号会分情况

// 情况1: 如果加号左右两个都是数值,那么+号就是加法运算
console.log(11 + 22, typeof (11 + 22));
// 情况2: 如果加号左右两个有一个是字符串,那么+号就是字符串连接运算符
console.log("11" + 22, typeof ("11" + 22));
console.log(11 + "22", typeof (11 + "22"));
console.log("11" + "22", typeof ("11" + "22"));

数值(number)

数值包括 整数(正整数,负整数), 小数(正小数,负小数), 0, 以及 NaN关键字

备注: NaN当数据类型强制转换数值类型失败的时候,就会得到NaN(not a number)关键字

console.log(5, typeof 5);
console.log(-2, typeof - 2);
console.log(0, typeof 0);
console.log(5.123, typeof 5.123);
console.log(-4.789, typeof - 4.789);
console.log(NaN, typeof NaN);

布尔(boolean)

布尔值 只有两个 true(表示真,成立) 和 false(表示假,不成立)

console.log(true, typeof true);
console.log(false, typeof false);

未定义(undefined)

得到未定义的情况有很多,列几个常见情况给大家

  1. 定义变量,不赋值,那么变量的值就为undefined

    var a;
    console.log(a, typeof a);
    
  2. 定义变量,赋值为undefined

    var b = undefined;
    console.log(b, typeof b);
    
  3. 访问不存在的数组元素值(数组后面讲)

    var arr = [10, 20, 30];
    console.log(arr[0], typeof arr[0]);
    console.log(arr[1], typeof arr[1]);
    console.log(arr[2], typeof arr[2]);
    console.log(arr[3], typeof arr[3]);
    console.log(arr[4], typeof arr[4]);
    
  4. 访问不存在的对象属性(对象后面讲)

    var obj = {
        username: "zhangsan",
        age: 23
    }
    console.log(obj.username, typeof obj.username);
    console.log(obj.age, typeof obj.age);
    console.log(obj.sex, typeof obj.sex);
    
  5. 函数没有返回值(函数后面讲)

    function fn() {
        console.log("我是fn函数");
    }
    var res = fn();
    console.log(res, typeof res);
    

空型(null)

得到空型的方式主要给变量赋值一个null 或者 dom对象获取失败也会返回null
注意: typeof null的结果不是null, 而是object

var x = null;
console.log(x, typeof x);

var objDiv = document.getElementById("abc");
console.log(objDiv, typeof objDiv);

运算符

  • 含义:运算符就是可以运算的符号

算术运算符

  • 算术运算符 + - * / %(求余数, 取模)
var x = 10;
var y = 3;
console.log(x + y); // 13
console.log(x - y); // 7
console.log(x * y); // 30
console.log(x / y); // 3.33333333333...

自操作运算符

  • 自操作运算符 ++ --
  1. 不管前加加还是后加加,最终结果都会加1
  2. 如果有其他代码跟加加一起使用, 后加加, 运算后自增
  3. 如果有其他代码跟加加一起使用, 前加加, 自增后运算

赋值运算符

  • 赋值运算符 = += -= *= /= %=

  • += -= *= /+ %= 用法几乎一样

以+=为例

var x = 20;
console.log("x=>", x);
x = x + 3;
x += 3; // 左边x的值 加上 右边的3 ,再赋值自己x
console.log("x=>", x);

字符串连接运算符

  • 字符串连接运算符 + +=

  • +和+= 需要左右两个运算符至少有一个是字符串类型, 那么+和+=才会是字符串连接运算符

var str = ""; // 定义一个空字符串
str += "<h2>" + username + "的个人信息</h2>";
str += "<ul>";
str += "<li>姓名: " + username + "</li>";
str += "<li>性别: " + sex + "</li>";
str += "<li>年龄: " + age + "</li>";
str += "<li>体重: " + weight + "</li>";
str += "</ul>";
console.log(str);

比较运算符

  • 比较运算符 > < >= <= == != === !==

  • 比较运算符的结果是布尔值(true或者false)

  • == 等于, "比较值是否相等"

    进行比较时,运算符==会考虑其操作数(要比较的两个值)的类型。

    如果两个值的类型相同,就直接进行比较,如果两个值的类型不同,则尝试将它们转换为相同的类型,再进行比较

    console.log(10 == 10);
    console.log(10 == "10");
    
  • === 全等于, 先比较"数据类型"是否相同, 如果不相同, 直接返回false; 如果"数据类型"相同,再进行值的比较

    === 全等于,需要类型和值都相等,才会返回true

    console.log(10 === 10);
    console.log(10 === "10");
    

逻辑运算符

  • 逻辑运算符 && || !
  1. 逻辑与&& 表示并且的关系 需要&&左右两边都为true的时候,最终得到true

    console.log(false && true);
    console.log(false && false);
    console.log(10 > 5 && 10 === "10" && "a" == "a");
    
  2. 逻辑或|| 表示或者的关系 ||左右只有一个为true的时候,最终得到true

    console.log(false || true); // true
    console.log(false || false); // false
    console.log(3 != "3" || "人" == "阿凡达");
    
  3. 逻辑非! 表示取反的关系 !true=>false !false=>true

    console.log(true);
    console.log(!true);
    console.log(false);
    console.log(!false);
    

三目运算符

  • 三目运算符语法

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

  • 如果条件表达式成立,执行语句1;

    如果条件表达式不成立,执行语句2;

var a = 5;
var b = 3;
a > b ? console.log( "a大于b") : console.log( "a不大于b");var a = 5;
var b = 3;
a > b ? console.log( "a大于b") : console.log( "a不大于b");

运算符的优先级

  • 优先级从高到底

    1. () 优先级最高

    2. 一元运算符 ++ -- !

    3. 算数运算符 先* / % 后 + -

    4. 关系(比较)运算符 > >= < <=

    5. 相等(比较)运算符 == != === !==

    6. 逻辑运算符 先&& 后||

    7. 赋值运算符 = 优先级最低

console.log(4 >= 6 || "人" != "阿凡达" && !(12 * 2 == 144) && true);

流程控制

  • 什么是流程控制?

    控制流程(也称为流程控制)是计算机运算领域的用语,意指在"程序运行"时,个别的指令(或是陈述、子程序)运行或求值的"顺序"。

    不论是在声明式编程语言或是函数编程语言中,都有类似的概念。

  • 流程控制分为三种结构:顺序结构、分支结构、循环结构

    • 顺序结构: 代码从上到下, 按顺序执行

    • 分支结构: 代码有选择的执行, 选择某个分支的代码执行

    • 循环结构: 满足一定条件的时候,代码重复执行

分支结构

if语句实现分支

  • 分支结构又可以分为单分支,双分支以及多分支,条件表达式就是可以得到布尔值的式子

    • 单分支,只有一种选择, if语句实现

      语法

      if( 条件表达式 ){

      ​ 条件表达式成立时,执行的代码块;

      }

      var age = 20;
      if (age >= 18) {
          console.log("成年了,可以去网吧上网了");
      
    • 双分支,有两种选择, if...else语句实现

      语法

      if( 条件表达式 ){

      ​ 条件表达式成立时,执行的代码块;

      }else{

      ​ 条件表达式不成立时,执行的代码块;

      }

      var age = 20;
      if (age >= 18) {
          console.log("成年了,可以去网吧上网了");
      } else {
          console.log("未成年,禁止上网");
      }
      
    • 多分支,有多种选择, if...else if...else语句, switch语句

      语法

      if( 条件表达式1 ){

      条件表达式1成立时,执行的代码块;

      }else if( 条件表达式2 ){

      条件表达式1不成立时,条件表达式2成立,执行的代码块;

      }else if( 条件表达式3 ){

      条件表达式1和2都不成立时,条件表达式3成立,执行的代码块;

      }else if( 条件表达式4 ){

      条件表达式1,2,3都不成立时,条件表达式4成立,执行的代码块;

      }else{

      以上条件表达式都不成立,执行的代码块

      }

      var age = 12;
      if (10 <= age && age <= 16) {
          console.log("少年");
      } else if (17 <= age && age <= 44) {
          console.log("青年");
      } else if (45 <= age && age <= 64) {
          console.log("中年");
      } else if (65 <= age) {
          console.log("老年");
      } else {
          console.log("幼年");
      }
      

switch语句实现多分支

  • 运算规则:

    1. 拿到switch()里面的变量名的值跟 case后面值进行"全等全等全等"比较, 如果比较成功, 则执行对应的代码块, 执行完代码块以后, 检查是否存在break关键字; 如果有break关键字,则结束switch分支; 如果没有break关键字,会一直往下执行代码块,直到遇到break关键字为止
    2. 变量值跟case所有的所有值比较的时候,都不成立, 如果存在default语句,就会执行default默认的代码块
  • 扩展知识: 如果多个case 值的代码块是相同的情况下,可以把多个case值写在一起

  • 语法

    switch(变量名){
    
        case1: 
    
        case2:
    
        case3:
            满足case1或者case2或者case3,都会执行的代码块;
    
            break;
            
        case4:
    
            代码块4;
    
            break;
    
        case5:
    
            代码块5;
    
            break;
    
        default:
            如果给定default 子句,这条子句会在 表达式 的值与任一 case 语句都不匹配时执行
    
    }
    
var week = 3;
switch (week) {
    case 1:
        console.log("星期一打篮球");
        break;

    case 2:
        console.log("星期二打乒乓球");
        break;

    case 3:
        console.log("星期三唱歌");
        break;

    case 4:
        console.log("星期四打桌球");
        break;


    case 5:
        console.log("星期五游泳");
        break;


    case 6:
    case 7:
        console.log("周末睡觉休息");
        break;

    default:
        console.log("请输入1~7之间的数字!!!");
}

if多分支语句与switch之间的区别

  1. switch语句它在功能上和if语句多分支很相似
  2. 如果是"范围"的判断推荐使用if语句多分支
  3. 如果是"固定值"之间的比较 推荐使用switch语句
  4. 因为switch能够实现的功能 我们其实完全可以使用if语句多分支来实现!

循环结构

循环结构有循环四要素

  1. 初始化变量, 循环从什么值开始
  2. 循环条件, 循环什么时候结束
  3. 循环体, 重复执行的代码块就是循环体
  4. 变量更新, 更新变量的值,如果不更新变量的值,那么循环会一直成立,就会造成死循环

while循环

  • while循环语法

    初始化变量;
    
    while( 循环条件 ){
    
        循环体;
    
        变量更新;
    
    }
    
    var i = 1; // 初始化变量;
    while (i <= 5) { // 循环条件
        // 循环体
        console.log("hello" + i);
        // 变量更新
        i++;
    }
    

do...while循环

  • do...while循环语法

    初始化变量;
    
    do{
    
        循环体;
    
        变量更新;
    
    }while( 循环条件 );
    

    注意:while先判断再执行循环体, do...while先执行一次循环体,再判断 所以 do...while执行会执行一次循环体

    var i = 1;
    while (i <= 5) {
        console.log(i);
        i++;
    }
    console.log("");
    console.log("");
    
    var i = 1;
    do {
        console.log(i);
        i++;
    } while (i <= 5);
    console.log("");
    console.log("");
    console.log("");
    

for循环

  • for循环语法:

    for(①初始化变量; ②循环条件; ④变量更新 ){
    
        ③循环体;
    
    } 
    
for (var i = 1; i <= 3; i++) {
    console.log(i);
}
  • 双重for循环
// 九九乘法表
var str = "";
str += "<table border='1'>";
for (var i = 1; i <= 9; i++) { // 外层循环控制行
    str += "<tr>";
    for (var j = 1; j <= i; j++) { // 内层循环控制列
        str += "<td>" + j + " &times; " + i + " = " + i*j + "</td>";
    }
    str += "</tr>";
}
str += "</table>";
document.write(str);

循环中断关键字

典型的,当条件表达式不成立了,那么循环体就会终止执行。也可以称之为循环它寿终正寝了。

  • 循环提前终止:本来条件表达式是成立了,循环体还可以继续的往下执行,但是我们可以使用一些关键字让其提前终止。

  • break和continue 循环终止的关键字 都需要配合 if语句来实现 主要的作用是为了提升循环的效率!

  • 注意: break和continue这个两个关键字,只能在"循环语句"中使用

break

  • break打断的意思,当在循环体中遇到了break关键字以后,"立即跳出整个循环"
// 举例: 小明吃5个包子,吃到第3个发现里面有半个虫子,感觉到恶心, 没胃口, 所以剩下其余包子都不吃了
for (var i = 1; i <= 5; i++) {
    if (i == 3) {
        console.log("吃到第3个发现里面有半个虫子,后面的包子不想吃了");
        break;
    }
    console.log("小明在吃第" + i + "个包子");
}

continue

  • continue继续的意思,"立即跳出当前本次循环,继续下一次循环"
// 举例:  小明吃5个包子,第3个有虫子,就扔掉第3个,继续吃第4个第5个包子
for (var i = 1; i <= 5; i++) {
    if (i == 3) {
        console.log("第3个有虫子,就扔掉第3个, 后面第4个和第5个包子没问题");
        continue;
    }

    console.log("小明在吃第" + i + "个包子");
}

数组

  • 什么是数组?

    数组是一组数据"有序"的集合。数组它是属于"复合数据类型"。至少可以存储一个值。

  • 为什么要使用数组?

    因为在我们工作中 有很多数据是有关联的 我们要表示的时候想把这些数据用一个”东西”来存储,这个时候就可以用到数组!

  • 所谓数组,就是将多个元素(通常是同一类型)按一定顺序排列放到一个集合中,那么这个集合我们就称之为数组。

  • 数组的相关概念

    • 什么是数组元素, 数组元素就是数组中每个数据
    • 数组元素的类型, 数组元素的类型可以是JS中的任意类型
    • 数组下标(索引), 数组下标默认就是从0开始每次递增1的数字
    • 如何访问数组里面的某一个元素, 数组变量名[数组下标]
    • 数组的长度, 数组长度就是数组元素的总个数; 通过 数组变量名.length 可以获取数组长度
    • ❤注意: 数组长度是一个动态值, 随着数组元素的总个数变化而变化
    • ❤❤❤最大数组下标 = 数组长度 - 1;

定义数组

  • 方式一: 使用[]来定义数组
// 使用[]定义空数组
var arr1 = [];
console.log("arr1=>", arr1);
console.log("arr1.length=>", arr1.length);
console.log("");

// 使用[]定义非空数组
// var 数组变量 = [值1, 值2, 值3...]
var arr2 = ["zhangsan"];
console.log("arr2=>", arr2);
console.log("arr2.length=>", arr2.length);
console.log("");

var arr3 = ["abc", 123, true, null, undefined, [10, 20, 30]];
console.log("arr3=>", arr3);
// 获取数组长度
console.log("arr3.length=>", arr3.length);
// 获取某个数组元素
console.log("arr3[0]=>", arr3[0]);
console.log("arr3[2]=>", arr3[2]);
// 注意: 通过下标访问不存在的数组元素将返回undefined
console.log("arr3[6]=>", arr3[6]);
console.log("arr3[7]=>", arr3[7]);
console.log("arr3[8]=>", arr3[8]);
  • 方式二: 使用new Array()构造函数方式来定义数组
// 定义空数组
var arr4 = new Array();
console.log("arr4=>", arr4);
// 定义指定长度的空数组
// var 数组变量名 = new Array( size ); // size是数值
var arr5 = new Array(5);
console.log("arr5=>", arr5);
var arr6 = new Array("5");
console.log("arr6=>", arr6);
var arr7 = new Array(5, 6);
console.log("arr7=>", arr7);

// 定义非空数组
// var 数组变量名 = new Array( 值1, 值2, 值3, 值4... )
var arr8 = new Array(10, "abc", 30, true, 50, 60);
console.log("arr8=>", arr8);
console.log("");

  • 修改数组元素值

    语法

    数组变量名[数组下标] = 新值

var arr9 = [10, 20, 30, 40, 50];
console.log("arr9=>", arr9);
console.log("arr9[1]=>", arr9[1]);
arr9[1] = 234;
console.log("arr9=>", arr9);
console.log("arr9[1]=>", arr9[1]);

多维数组

  • JavaScript它本身是没有多维数组的概念,因为在JavaScript中 数组元素的数据类型可以是"任意数据类型"。

    假设在一个数组中有一些数组元素的的类型还是"数组" 这个时候我们就将它称之为多维数组! 也就是数组嵌套数组

  • 超过一维都是可以叫多维,多维数组指的是一个数组中的数组元素又是一个数组。

// 二维数组, 就是数组嵌套数组 只嵌套一层
var arr1 = ["zhangsan", 23, "男", "Java", "183cm"];
console.log("arr1=>", arr1);
// 如何访问二维数组中的某个数组元素
// 语法
// 数组变量名[下标1][下标2]
console.log(arr[1][1]);
console.log(arr[2][4]);

数组的遍历

  • 数组的遍历就是把数组中所有的数组元素都访问一遍, 类似,早上班长点名

  • 由于数组的下标是从0开始的,最大下标是数组长度-1, 所以是可以确定循环次数, 那么我们就推荐使用for循环遍历

一维数组的遍历

  • 遍历一维数组的语法

    for(var i = 0; i < 一维数组变量名.length; i++ ){

    ​ console.log( 一维数组变量名[i] )

    }

var arr = [10, 20, 30, 40, 50];
for (var i = 0; i <= arr.length - 1; i++) {
    console.log(arr[i]);
}

二维数组的遍历

  • 二维数组遍历语法

for(var i = 0; i < 二维数组变量名.length; i++ ){

​ for(var j = 0; j < 二维数组变量名[i].length; j++ ){

​ console.log(二维数组变量名[i][j]);

​ }

}

for (var i = 0; i < students.length; i++) {
    for (var j = 0; j < students[i].length; j++) {
        console.log(students[i][j]);
    }
    console.log("");
}

新增数组元素

  • 新增数组元素,就是在已经创建好的数组基础上,往数组中添加新的数组元素

  • 格式:

    javascript里的数组可以通过 (数组变量名[数组下标] = 值) 实现新增数组元素

  • 注意:

    1. 如果数组下标已经存在,新值会覆盖旧值
    2. 如果数组下标不存在,就会添加新元素,但是可能会跳过一些没有值的元素
  • 备注: 后期我们学习数组对象的方法的以后,我们可以使用

    数组对象.push() 方法给数组尾部添加 或者 数组对象.unshift() 方法给数组头部添加元素

var heros = ["亚瑟", "李白", "小乔"];
console.log(heros);
console.log("heros.length=>", heros.length);
console.log("");

heros[heros.length] = "王昭君";
console.log(heros);
console.log("heros.length=>", heros.length);

函数

  • 为什么需要函数?

    xxxxxxxxxx 

      // 与v-for同级的ref 返回值都是列表渲染出来当前元素数组集合    

          //v-for 内部的ref 返回值也是列表渲染出来当前元素数组集合        {{num}}-span        没用的a       {{num}}    

       <button @click="showRefList">click
    ​html

  • js里面,也有非常多的相同代码,可能需要大量重复使用。 此时我们可以利用函数。

    1. 为了解决代码的重用!(代码的复用 )
    2. 减少代码量。
  • 函数的封装

    函数的封装是把一个或者多个功能通过函数的方式封装起来,对外只提供一个简单的函数接口( 把代码写在函数中, 通过函数名()就可以指定函数里面的代码 )

    简单理解:封装类似于将电脑配件整合组装到机箱中 (类似快递打包)

  • 什么是函数?

    函数 就是封装了 一段 可被重复调用执行的 代码块。可以实现大量代码的重复使用。

    函数是可以被命名的,它是为了解决某些功能的代码段!

    可以被命名:表示函数是可以有名字的,也是可以没有名字的。

    代码段:函数体

  • 函数的分类

    分为系统内置函数和自定义函数

    系统内置函数就是js给我们提供的函数,我们可以直接使用

    自定义函数就是程序员根据需求自定义封装的函数

定义函数(声明函数)

  • 语法

    function 函数名( 形参1,形参2 ,形参3... ){
        函数体;
        return 返回值;
    }
    
  • 结构说明:

    1. function是定义函数的关键字, 区分大小写
    2. 函数名可以自定义,但是有规则和规范,跟变量名的规则和规范一样的 由a~z A~Z 0~9 _ $组成 不能以数字开头 不能使用js中的关键字和保留字
    3. ()里面的是形参列表,形参就是形式参数, 形参可以省略 "形参的作用是为了接收调用函数的时才能确定的值" 形参其实就是一个变量,接收值使用的 形参是在函数中使用的; 在()里面写形参的时候,不需要加上var关键字; 形参如果没有接收到实参值的时候,默认形参的值undefined
    4. 函数体就是封装在函数中的代码块, 函数体被封装到函数里面以后, 函数体代码不会执行 函数体代码需要函数被调用以后才会执行 函数被调用几次, 函数体就会执行几次
    5. return 返回值, 是向函数的调用者返回指定的值 return语句可以省略
  • 例子1: 定义无形参的函数

function fn() {
    console.log("hello");
    console.log("你好鸭");
    console.log("今天也要加油鸭");
    console.log("");
}
fn();
  • 例子2: 定义只有一个形参的函数
function fn(username) {
    console.log("username形参的值为:", username);
    console.log(username + ",早上好");
    console.log("");
}
fn();
fn("张三疯");
  • 例子3: 定义多个形参的函数
// 函数的封装就是把代码写到函数里面
function getSum(a, b) {
    console.log("a=>", a);
    console.log("b=>", b);
    console.log("a+b=>", a + b);
    console.log("");
}
getSum();
getSum(12);
getSum(20, 30);
getSum(50, 60, 70);

定义函数的方式

  1. 方式1 "函数声明方式" function 关键字 (命名函数) 🧡
  • 语法

    function 函数名( 形参1, 形参2, 形参3...){

    ​ 函数体;

    ​ return 返回值;

    }

function fn(a, b, c) {
    return a + b + c;
}
console.log(fn(11, 22, 33));
  1. 方式2 "函数表达式" 其实就是把匿名函数赋值给一个变量 🧡
var getSum = function (num1, num2) {
    return num1 * num2;
}
console.log(getSum(5, 6));
  1. 方式3 构造函数 new Function() 用的不多 了解即可
  • 语法

    var 函数名 = new Function("形参1", "形参2", ..., "函数体");

    注意: new Function()定义函数的时候, new Function()里面的参数都是字符串

var sayHello = new Function("username", "age", 'console.log("我叫" + username + ",我今年" + age + "岁了")');
sayHello("zhangsan", 23);
sayHello("lisi", 24);
sayHello("wangwu", 25);

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

  1. 函数调用的时候实参不是必需的,javascript允许在调用函数的时候省略实参
function getSum(a, b) {
    console.log("a=>", a);
    console.log("b=>", b);
    console.log("");
}
// ❤❤❤实参个数等于形参个数
getSum(10, 20);
// 实参个数比形参个数少
getSum(30);
getSum();
// 实参个数比形参个数多
getSum(40, 50, 60, 70, 100);
  1. 通过 函数名.length 可以得到形参的个数
console.log("getSum形参的个数为:", getSum.length);
  1. 不能省略前面的参数,只能省略后面的参数;如果一定要省略前面的参数,可以传入undefined
// getSum(100);
// getSum(, 100);// 报错
getSum("", 100);
getSum(null, 100);
getSum(undefined, 100);

return关键字

  • 一般在函数体里面不会有输出语句,只会有一个return关键字,将我们要输出的内容返回给"函数的调用者"。

    return在英文中表示“返回”的意思

    return关键字是在函数体里面使用

  • 如果函数体中没有return返回值, 或者只写return关键字,没有return具体值, 那么函数的调用者将得到undefined

  • return它在函数体使用有两层含义:

    第一层含义: 当函数体里面遇到了return关键字以后,当前的这个return后面的函数体就不会再往下进行执行了。

    第二层含义:它会向"函数的调用者"返回数据 (重点) 返回值

同名函数覆盖问题

  • JS运行的时候,会分为预解析阶段和代码执行阶段

    预解析阶段主要负责检查语法,变量提升,函数提升等

    代码执行阶段,就是按一定的顺序,执行我们的JS代码

    先预解析阶段, 后代码执行阶段

    使用function关键字声明函数的时候, 是发生在预解析阶段, 会提前声明

  • 情况1: 在同一个<script>标签中, 定义多个相同函数名的函数的时候,后面定义的函数会覆盖前面定义的函数

  • 情况2: 在不同的外部js文件中, 定义多个相同函数名的函数,在同一个页面中引入这些js文件,后面引入的js文件定义的函数会覆盖前面引入js文件定义的函数

  • 情况3: 不同<script>块中的定义的同名函数互不影响

函数中的arguments对象

  • arguments是一个关键字,只能在函数中使用, 它的取值是一个对象,所以也称之为arguments对象

    arguments的值也是一个伪数组 "会把所有实参放在一个伪数组中" 伪数组就是类似数组一样 有length长度属性 有下标

    但是它不是真正的数组 不能使用数组对象的内置方法

  • arguments对象用在形参个数不确定的时候

// console.log(arguments);// 报错
function fn(a, b) {
    console.log("a=>", a);
    console.log("b=>", b);
    console.log("arguments=>", arguments);
    console.log("arguments.length=>", arguments.length);
    console.log("arguments[0]=>", arguments[0]);
    console.log("arguments[1]=>", arguments[1]);
    console.log("arguments[2]=>", arguments[2]);
    console.log("arguments[3]=>", arguments[3]);
    console.log("arguments[4]=>", arguments[4]);
    console.log("");
}
fn();
fn(50, 60, 70, 80, 90);

匿名函数

  • 什么是匿名函数? 没有名字的函数 称之为匿名函数!

  • 匿名函数的使用
    匿名函数需要"赋值给变量"或者"赋值给事件" 或者 "立即调用这个匿名函数"

// 把匿名函数赋值一个变量
var a = function (x, y) {
    console.log("x=>", x);
    console.log("y=>", y);
    console.log("我是匿名函数1111");
}
console.log(a);
// 调用匿名函数,匿名函数赋值一个变量
// 语法
// 变量名();
a(12, 14);

// 把匿名函数赋值给一个事件(DOM知识)
// 事件被触发以后,就会执行相应的函数
var btn = document.querySelector("button");
btn.onclick = function () {
    console.log("点击了按钮");
}

立即执行函数

  • 立即调用的函数表达式( IIFE )

    也可以叫自调用匿名函数 , 简称 IIFE ( Immediately-invoked function expression )

  • 自调用你们函数使用场景: 只需要执行一次,不需要重复调用

  • 在定义函数之后,就会立即调用该函数,语法如下:

    1. ;(function(){ 函数体 }());

      ;( function(){
          console.log("我是匿名函数5555");
      }() );
      
    2. 😭 function(){ 函数体 } )();🧡

      ;( function(){
          console.log("我是匿名函数6666");
      } )();
      

    注意:两种写法最后的分号都是必须的。如果省略分号,遇到连着两个 IIFE,可能就会报错。

经典算法-冒泡排序

  • 冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法

  • 冒泡排序思想

    让数组当中相邻的两个数进行比较,数组当中比较小的数值向下沉,数值比较大的向上浮!

    外层for循环控制循环次数,内层for循环控制相邻的两个元素进行比较。

// 封装成冒泡排序函数
function bubbleSort(arr) {
    for (var i = 0; i < arr.length - 1; i++) {
        for (var j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                // 交换两个变量的值
                var temp = arr[j + 1];
                arr[j + 1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

var myArr = [5, 63, 4, 12, 6, 72, 33];
console.log("myArr=>", myArr);
var myNewArr = bubbleSort(myArr);
console.log("myNewArr=>", myNewArr);

对象

  • 什么是对象?

    JavaScript 对象是拥有"属性"和"方法"的数据。

    属性就是特性,特点

    方法就是行为,功能

    说白, 属性就是把变量存在对象中, 方法就是把函数存在对象中 通过对象访问属性 通过对象调用方法

  • 为什么要有对象?

    1. 表达结构更清晰
    2. 可以大量复用
  • 对象的分类

    内置对象 以及 自定义对象

    内置对象就是JS给我们提供的对象,我们可以直接使用, String, Date, Number, Array, RegExp, Window, Document等等

    自定义对象就是程序员根据需要自己定义的对象

创建对象

  1. 创建对象,使用字面量 {} 🧡

// 1.1 创建一个空对象
var obj1 = {};
console.log("obj1=>", obj1);
// 1.2 创建非空对象
/* var 对象名 = {
            属性名1: 属性值1 ,
            属性名2: 属性值2 ,
            属性名3: 属性值3 ,

            方法名1: 匿名函数1, 
            方法名1: 匿名函数2,
            ... 
        } */
var obj4 = {
    uname: "lisi",
    age: 24,
    sayHello: function (a) {
        console.log("你猴啊");
        console.log("a=>", a);
        return "好你妹啊";
    },
    sayHi: function () {
        console.log("嗨");
    },
}
console.log("obj4=>", obj4);
  1. 创建对象 使用函数 new Object()

var obj5 = new Object();
console.log("obj5=>", obj5);

// 往空对象中添加属性和方法
obj5.x = 10;
obj5.y = 20;

var x = 100;

obj5.start = function () {
    console.log("开始");
    // ❤❤❤在方法中,如何访问对象的属性或者方法
    console.log("x=>", x);
    console.log("obj5.x=>", obj5.x);
    // ❤❤❤在方法中, 可以使用this关键字,代表当前对象
    console.log("this=>", this);
    console.log("this === obj5=>", this === obj5);
    console.log("this.x=>", this.x);
    this.stop();
}

注意: new Object()只能创建空对象

var obj6 = new Object( uname:"wangwu" );// 报错
console.log("obj6=>", obj6);

以下代码,不会报错,但是实现的效果不对

var obj8 = new Object(uname = "wangwu", age = 23);
console.log("obj8=>", obj8);

var obj9 = new Object("wangwu", 23);
console.log("obj9=>", obj9);

var obj10 = new Object(23, "孙七");
console.log("obj10=>", obj10);
  1. 构造函数

访问对象的属性

  1. 语法1 对象名.属性名 🧡
console.log(obj4.uname);
  1. 语法2 对象名["属性名"]
console.log(obj4["age"]);

注意: 如果访问不存在的对象属性, 会返回undefined

调用对象的方法

  1. 语法1: 对象名.方法名()
obj4.sayHi();
console.log(obj4.sayHello(10));
  1. 语法2: 对象名["方法名"]()
obj4["sayHi"]();

注意: 如果调用对象中不存在的方法,会报错

obj4.sleep();// 报错
  • 修改对象中某个属性的值
console.log("obj4=>", obj4);
obj4.uname = "李冰冰";
console.log("obj4=>", obj4);
obj4["age"] = 28;
console.log("obj4=>", obj4);
  • 添加新属性或者新方法到已存在对象中
obj4.sex = "男";
obj4.sex = "女";
console.log("obj4=>", obj4);
obj4.eating = function () {
    console.log("吃个饭饭");
}
console.log("obj4=>", obj4);

删除属性

  • 访问对象中不存在,会得到undefined

    ar obj = {
        uname: "zhangsan",
        age: 23
    }
    console.log("obj=>", obj);
    console.log("obj.uname=>", obj.uname);
    console.log("obj.age=>", obj.age);
    console.log("obj.sex=>", obj.sex);
    
  • 删除属性语法

    1. 语法1: delete 对象名.属性名;

    2. 语法2: delete 对象名["属性名"]

    delete obj.age;
    delete obj["age"];
    
    console.log("obj=>", obj);
    console.log("");
    console.log("obj.uname=>", obj.uname);
    console.log("obj.age=>", obj.age);
    

使用for...in遍历数组或者自定义对象

因为对象没有数字下标,而且对象的属性名是没有规律的,所以不能使用简单for循环实现对象的遍历

  • 同名属性会覆盖

    var obj = {
        uname: "zhangsan",
        uname: "lisi",
        age: 23
    }
    console.log(obj);
    
  • 属性名默认是"字符串类型",所以属性名是可以加上引号的

    var obj = {
        "1": 456,
        1: 123
    }
    console.log(obj);
    obj[1] = 789;
    console.log(obj);
    obj["1"] = 666;
    console.log(obj);
    
  • 如果属性名有一些特殊符号, 比如短横杠-, 那么就一定要写引号

    var obj1 = {
        "user-name": "sunqi",
        "age": 53,
        "user_sex": "男",
    }
    console.log(obj1);
    
    var sunqi = "孙琪琪";
    var obj2 = {
        "user-name": "sunqi",
        "user-name2": sunqi,
        age: 53,
        user_sex: "男",
    }
    console.log(obj2);
    
  • 语法

    for(var 变量名 in 数组对象或者自定义对象 ){

    ​ 1. 如果for...in遍历的是自定义对象, 那么变量名代表对象的属性名

    ​ 2. 如果for...in遍历的是数组对象, 那么变量名代表数组的下标

    }

var obj = {
    uname: "李狗蛋",
    age: 28,
    sex: "男",
    a: "我是aaaaa"
};
for (var a in obj) {
    console.log("a=>", a);
    console.log("obj.a=>", obj.a);
    console.log('obj["a"]=>', obj["a"]);
    console.log('obj[a]=>', obj[a]);
    console.log("");
}
console.log("");
console.log("");
console.log("");

var arr = [10, 20, 30, 40, 50];
for (var k in arr) {
    console.log("k=>", k);
    console.log("arr[k]=>", arr[k]);
    console.log("");
}

JS的内置对象

  • JS的内置对象有

    1. String对象:提供了处理字符串的属性与方法。
    2. Math对象:提供了一些操作数学方面属性与方法
    3. Array对象:提供了一些操作数组的属性与方法
    4. Date对象:提供了一些对时间日期操作的方法
    5. Number对象:它主要是提供了一个操作数值的方法
    6. Events对象:提供对JavaScript事件的处理信息。
  • 通过 MDN/W3C/手册 查询需要的内容

    Mozilla 开发者网络(MDN)提供有关开放网络技术(Open Web)的信息,包括 HTML、CSS 和万维网及 HTML5 应用的 API。

    MDN : https://developer.mozilla.org/zh-CN/ 推荐
    W3C: http://www.w3school.com.cn/jsref/index.asp
    菜鸟教程: https://www.runoob.com/js/js-tutorial.html

  • 如何学习对象中的方法?

    1. 先了解这个方法是哪个对象的, 这个方法的功能是什么
    2. 参数的意义和类型
    3. 返回值意义和类型
    4. 编码demo案例代码进行测试效果

Math对象

Math.PI

  • Math.PI 求圆周率

    console.log(Math.PI);
    

Math.ceil()

  • Math.ceil(x) 对x进行向上取整 得到一个比当前数要大的最小整数

    console.log(Math.ceil(5.9123)); // 6
    console.log(Math.ceil(5.2555)); // 6
    console.log(Math.ceil(5.0)); // 5
    console.log(Math.ceil(5.0000001)); // 6
    console.log(Math.ceil(-5.0)); // -5
    console.log(Math.ceil(-5.15566)); // -5
    console.log(Math.ceil(-5.677)); // -5
    

Math.floor()

  • Math.floor(x) 对x进行向下取整 得到一个比当前数要小的最大整数

    console.log(Math.floor(5.9123)); // 5
    console.log(Math.floor(5.2555)); // 5
    console.log(Math.floor(5.0)); // 5
    console.log(Math.floor(5.0000001)); // 5 
    console.log(Math.floor(-5.0)); // -5
    console.log(Math.floor(-5.15566)); // -6
    console.log(Math.floor(-5.677)); // -6
    

Math.round()

  • Math.round(x) 对x进行四舍五入取整

    console.log(Math.round(4.4)); // 4
    console.log(Math.round(4.45)); // 4
    console.log(Math.round(4.2)); // 4
    console.log(Math.round(4.5)); // 5
    

Math.abs()

  • Math.abs(x) 返回x的绝对值

    console.log(Math.abs(5));
    console.log(Math.abs(-5));
    console.log(Math.abs(5.2123456));
    console.log(Math.abs(-5.2789456));
    console.log(Math.abs(0));
    

Math.max()

  • Math.max(x1,x2,x3...) 求x1,x2,x3...这些数中的最大值

    console.log(Math.max(52, 75, 9, 3, 42));
    

Math.min()

  • Math.min(x1,x2,x3...) 求x1,x2,x3...这些数中的最小值

    console.log(Math.min(52, 75, 9, 3, 42));
    

Math.pow(x,y)

  • Math.pow(x,y) 求x的y次方 也就是y个x相乘

    console.log(Math.pow(2, 5));
    console.log(Math.pow(5, 0));
    console.log(Math.pow(2, -2));
    console.log(Math.pow(2, 0.3));
    

Math.sqrt(x)

  • Math.sqrt(x) 求平方根 比如4的平方根是2

    console.log(Math.sqrt(4));
    console.log(Math.sqrt(0));
    console.log(Math.sqrt(1));
    

Math.random()

  • Math.random() 生成一个"随机小数", 取值范围 是 范围[0,1) 左闭右开 0 <= 随机小数 < 1

    console.log(Math.random());
    
    • 使用Math.random()实现指定范围的随机整数
    • 公式: Math.floor( Math.random() * (大值 - 小值 + 1) + 小值 )
    // 求出 30 ~ 35
    while (true) {
        var num = Math.floor(Math.random() * (35 - 30 + 1) + 30);
        console.log(num);
        if (num == 30 || num == 35) {
            break;
        }
    }
    
随机生成十六进制颜色
var a = Math.random();
console.log(a);
// JS中,  可以使用 数值.toString(进制) 可以把其他类型的值转成指定进制的字符串
console.log(a.toString(16));
console.log(a.toString(16).substr(2, 6));
console.log("#" + a.toString(16).substr(2, 6));
console.log("#" + a.toString(16).substr(2, 3));

String对象

String对象的创建方式

  1. 通过字面量 "" ''
var str1 = "abc";
var str2 = 'abc';
console.log(str1);
console.log(str2);
  1. 通过构造函数 new String("字符串内容")
var str3 = new String("abc");
console.log(str3);

String对象的属性

  • String对象的属性 字符串的长度(字符总个数)
  • 通过 String对象.length 获取字符串的长度
var str4 = "abc123@_@哈哈哈";
console.log(str4.length);

String对象的方法

注意:字符串所有的方法,"都不会修改字符串本身",操作完成会返回一个"新的字符串"

注意:字符串方法中的"索引"都是从"0"开始!

根据位置获取字符
charAt()
  • ❤charAt(index)
  • 返回指定位置的字符(index 字符串的索引号) str.charAt(0)
str[]
  • ❤❤str[index]
  • 获取指定位置处字符 HTML5,IE8+支持 和charAt()等效
charCodeAt()
  • charCodeAt(index)
  • 获取指定位置处字符的ASCII码 (index索引号) str.charCodeAt(0)
String.fromCharCode()
  • String.fromCharCode(ASCII码)
  • 返回指定ASCII码对应的字符串 String.fromCharCode( 65 )

ASCII百度百科介绍: https://baike.baidu.com/link?url=jcW6UQw1vRpolAwT3xlq8qRnxuZF_aG0T3zhw4yc_WZUiqDUltiUeHptJr4RDzJafctcoJORKDKFIhSsZlh77K

字符串操作方法
concat()
  • concat(str1,str2,str3...) concat() 方法用于连接两个或多个字符串。拼接字符串,等效于+,+更常用
substr()
  • substr(start[, length])
  • 从start位置开始(索引号) ,length 取的个数
  • 如果length (长度)没有书写 表示一直截取到字符串的末尾 如果有写则表示截取的长度
  • 另外,start还可以写负值,表示从倒数第几个开始截取,比如 "abc".substr(-2,1) 表示从倒数第二个字符串开始截取一个长度的字符
var str = "abcdefghij";
console.log(str);
// 如果忽略 length,则 substr 提取字符,直到字符串末尾
console.log(str.substr(2));
// 从下标2开始,截取长度为3的字符串
console.log(str.substr(2, 3));
// start 为负值, 从字符串的倒数第几个开始截取
console.log(str.substr(-5));
console.log(str.substr(-5, 2));
// 如果 length 为 0 或负值,则 substr 返回一个空字符串
console.log(str.substr(-5, -2));
console.log(str.substr(-5, 0));
substring()
  • substring(start[, end])
  • 截取字符串 从start(开始下标)处开始截取 ;
  • 如果end(结束下标) 没有书写 表示一直截取到字符串的末尾
  • 如果有写则表示截取到end下标的前一个位置
// substring(开始下标[, 结束下标])
var anyString = "Mozilla";
console.log(anyString);
// 省略结束下标,就会从开始下标开始截取到字符串的末尾
console.log(anyString.substring(2));
// 有开始下标也有结束下标  含头不含尾
console.log(anyString.substring(2, 4));
获取字符串位置方法
indexOf()
  • indexOf("要查找的字符或字符串")
  • 返回指定内容在原字符串中"第一次"出现的位置
  • 如果找不到就返回 -1;
  • 如果查找的是多个字符,找的到就返回第一个字符的下标
lastIndexOf()
  • lastIndexOf("要查找的字符或字符串")
  • 返回指定内容在原字符串中"最后一次"出现的位置,
  • 如果找不到就返回 -1;
  • 如果查找的是多个字符 ,找的到就返回第一个字符的下标
var str = "this is a island";
console.log(str.indexOf("g"));
console.log(str.indexOf("i"));
console.log(str.indexOf("is"));
console.log("");
console.log("");
console.log(str.lastIndexOf("g"));
console.log(str.lastIndexOf("i"));
console.log(str.lastIndexOf("is"));
替换 replace()
  • replace("被替换字符串", "替换成什么字符串") 方法用于在字符串中用一些字符替换另一些字符, 默认只替换一次,不会全部替换
  • 如果需要全部替换,可以使用正则表达式(这个正则后续课程会讲)或者字符串对象的replaceAll()方法
  • 注意: 一些旧版浏览器对一些js方法比如这个replaceAll方法不是很兼容,会出现js报错问题。
  • 如果要全部替换的话,浏览器又不支持replaceAll这样的方法,那可以换成了使用正则表达式实现

参考文档: https://www.jianshu.com/p/457943f03397

var str = "hello,world!hello,javascript!hello,html!";
console.log(str);
console.log("");

// replace默认只替换一次,不会全部替换
console.log(str.replace("hello", "你好"));
console.log(str.replace("hi", "你好"));
console.log("");

// 如果需要全部替换,可以使用"正则表达式"或者字符串对象的replaceAll("被替换字符串", "替换成什么字符串")方法
console.log(str.replace(/hello/g, "你好"));
console.log(str.replaceAll("hello", "你好"));
转换大小写
toUpperCase()
  • 英文字母全部转换成大写
toLowerCase()
  • 英文字母全部转换成小写
var str = "HeLLo";
console.log(str);
console.log(str.toUpperCase());
console.log(str.toLowerCase());
切割字符串 split()
  • split(sep) 使用sep参数将字符串分隔为一个"数组"
  • sep就是代码根据什么字符来分割字符串
  • sep如果是""代表,"分割每一个字符"
var str = "张三-李思思-王武-赵柳柳";
console.log(str);
var arr = str.split("-");
console.log(arr);
var arr2 = str.split("");
console.log(arr2);
  • 如果split中没有任何参数 或者 分割符分割不了当前字符串,那么将得到一个数组元素的数组, 而且这个数组元素就是这个字符串
var arr3 = str.split();
console.log(arr3);
var arr4 = str.split("@");
console.log(arr4);
去除字符串的左右两边的所有空格 trim()
var str = "   zhang san    ";
console.log(str, str.length);
console.log(str.trim(), str.trim().length);

求字符串字符串中出现最多字符

// 字符串
var str = "comeaweiabc";
// 定义一个空对象
var obj = {};

// 遍历字符串
for (var i = 0; i < str.length; i++) {
    // 取出单个字符
    var char = str.charAt(i);
    if (obj[char] == undefined) {
        // 给对象添加属性 
        obj[char] = 1;
    } else {
        obj[char]++;
    }
}

// 出现最多次的次数
var maxCount = 1;
// 出现最多次的字符
var maxChar = str[0];
// 遍历对象obj
for (var attr in obj) {
    // 判断大小
    if (maxCount < obj[attr]) {
        // 出现最多次的次数
        maxCount = obj[attr];
        // 出现最多次的字符
        maxChar = attr;
    }
}
console.log(maxChar, maxCount);

Array对象

Array对象创建方式

数组-定义数组

检测是否为数组
  1. 方式1. Array.isArray( 值或者变量 ) 判断指定值或者变量是否为数组 返回布尔值 如果是数组返回true, 不是数组返回false

  2. 方式2: instanceof 运算符

    instanceof 可以判断一个对象是否是某个构造函数的实例,返回布尔值

var str = "abc";
var num = 10;
var bool = true;
var weidingyi = undefined;
var kong = null;
var arr = [10, 20, 30];
var obj = {
    uname: "zhangsan"
};
console.log(str, typeof str, Array.isArray(str), str instanceof Array);
console.log(num, typeof num, Array.isArray(num), num instanceof Array);
console.log(bool, typeof bool, Array.isArray(bool), bool instanceof Array);
console.log(weidingyi, typeof weidingyi, Array.isArray(weidingyi), weidingyi instanceof Array);
console.log(kong, typeof kong, Array.isArray(kong), kong instanceof Array);
console.log(arr, typeof arr, Array.isArray(arr), arr instanceof Array);
console.log(obj, typeof obj, Array.isArray(obj), obj instanceof Array);

Array对象的属性

数组的长度就是数组元素的总个数

数组的长度 数组对象.length

Array对象的方法

数组添加和删除元素方法
方法名 功能
push(值1,值2...) 向数组尾部添加一个或者多个值, 返回新的数组长度
pop() 删除尾部最后一个数组元素 返回被删除的数组元素
unshift(值1,值2...) 向数组头部添加一个或者多个值, 返回新的数组长度
shift() 删除头部最后一个数组元素 返回被删除的数组元素

注:以上4个方法,都会修改原数组、

var arr = [10, 20, 30];
console.log("arr=>", arr);
console.log("");

var res1 = arr.push(40);
console.log("res1=>", res1);
console.log("arr=>", arr);
console.log("");

var res2 = arr.push(50, 60, 70);
console.log("res2=>", res2);
console.log("arr=>", arr);
console.log("");

var res3 = arr.pop();
console.log("res3=>", res3);
console.log("arr=>", arr);
console.log("");

var res4 = arr.pop();
console.log("res4=>", res4);
console.log("arr=>", arr);
console.log("");

var res5 = arr.unshift(100, 200);
console.log("res5=>", res5);
console.log("arr=>", arr);
console.log("");

var res6 = arr.shift();
console.log("res6=>", res6);
console.log("arr=>", arr);
合并数组方法

concat(数组1,数组2...) 合并多个数组, 合成一个大数组, 将返回一个"新数组" 注意 不是合并成多维数组

var arr1 = [10, 20, 30];
var arr2 = [200, 500];
var arr3 = ["lisi", "lisi", "wangwu"];
// concat()方法中不写参数,将返回原数组一模一样的新数组
console.log(arr1.concat());
// concat()里面放参数0
console.log(arr1.concat(arr1));
console.log(arr1.concat(arr2, arr3));
console.log(arr1.concat(arr3, arr2));
console.log(arr1.concat(arr3, arr2, ["189cm", "90kg"]));
// 合并数组,我们不建议使用+号, 因为+号会把每个数组中数组元素已逗号作为连接符,每个组成拼接成一个字符串, 最后再拼接成一个大字符串  +号其实会调用数组的toString()方法
console.log(arr1 + arr2 + arr3);
console.log(arr1.toString() + arr2.toString() + arr3.toString());
console.log(arr1);
console.log(arr1.toString());
document.write(arr1);
数组索引方法
  • indexOf("内容") 查找数组中数组元素第一次出现的位置; 找不到返回-1
  • lastIndexOf("内容") 查找数组中数组元素最后一次出现的位置; 找不到返回-1
var arr = [10, 20, 30, 10, 20, 50, 60, 20];
console.log(arr.indexOf(100));
console.log(arr.lastIndexOf(100));
console.log(arr.indexOf(20));
console.log(arr.lastIndexOf(20));
数组转换为字符串
  1. join("连接符") 把数组元素按指定的连接符拼接成一个字符串 "连接符"参数可以省略,将使用逗号作为连接符
var arr = ["zhangsan", 23, "男", 10, 20, 30];
console.log(arr);
console.log(arr.join());
console.log(arr.join("-"));
console.log(arr.join("@"));
  1. toString()把数组转成字符串的时候,数组元素之间只能使用逗号连接
var arr = [10, 20, 30];
console.log(arr.toString());
console.log(arr.toString("-"));
console.log(arr.toString("|"));

小结: 字符串对象的.split()方法是把字符串分割成数组,而数组对象的.join()方法是把数组拼接成字符串

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

    方法名 功能
    reverse() 颠倒数组中元素的顺序,无参数 "该方法会改变原来的数组" 返回新数组
    sort() 对数组的元素进行排序 该方法会改变原来的数组 返回新数组
  • 注意:

    1. sort方法需要传入一个可以返回正值或者负值或者零的函数作为参数
    2. 如果传入" function(a,b){ return a-b;}",则为升序
    3. 如果传入" function(a,b){ return b-a;}",则为降序
    4. 如果sort()里面未传递参数,那么数组元素将按照ASCII字符顺序进行升序排列

参考文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

var arr = ["zhangsan", 52, "女", 75, 60];
arr.reverse();
console.log(arr);
  • 直接使用sort()方法 sort方法不带参数, 那么将按"ASCII字符顺序进行升序排列
var str = "aoAefo5xbAyoG1zzcD6op9p";
var arr = str.split("");
console.log("原数组arr=>", arr.join(""));
// 直接使用sort()方法 sort方法不带参数, 那么将按"ASCII字符顺序进行升序排列"
arr.sort();
console.log("sort()以后的数组arr=>", arr.join(""));
  • sort()方法里面可以放一个排序函数
var arr = [3, 5, 1, 60, 4, 72, 12, 139, 63, 92];
console.log(arr);
arr.sort(function (a, b) {
    // 升序
    return a - b;
});
console.log(arr);

var arr = [3, 5, 1, 60, 4, 72, 12, 139, 63, 92];
console.log(arr);
arr.sort(function (a, b) {
    // 降序
    return b - a;
});
console.log(arr);
数组元素的随机排序(抽奖)

实现数组元素的随机排序, 使用Math.random()方法结合sort()方法

var arr = [1, 2, 3, 4, 5, 6, 7];
console.log("arr=>", arr);
arr.sort(function () {
    return Math.random() - 0.5;
});
console.log("arr=>", arr);
清空数组所有元素的两种方式
  1. 赋值空数组
var arr1 = [10, 20, 30, 40, 50];
console.log("arr1=>", arr1);
// 第一种方式  赋值空数组
arr1 = [];
console.log("arr1=>", arr1);
console.log("");
console.log("");
  1. 设置数组的长度为0 因为数组的长度就是数组元素的总个数
var arr2 = ["zhangsan", "lisi", "wangwu"];
console.log("arr2=>", arr2);
// 第二种方式  设置数组的长度为0 因为数组的长度就是数组元素的总个数
arr2.length = 0;
console.log("arr2=>", arr2);
数组截取slice()
  • 数组截取slice( begin, end ) 返回一个"新的数组对象",

    这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。

    "原始数组不会被改变"。 数组截取就是"复制"数组中某部分数组元素到新数组中

  • 注意: begin和end参数都是可选的

// slice() 不写任何参数, 从下标0开始截取到数组的末尾
var newArr1 = arr.slice();
console.log("newArr1=>", newArr1);
console.log("arr=>", arr);
console.log("");

// slice(start) 写第一个start参数, 代表从下标start开始截取末尾
var newArr2 = arr.slice(2);
console.log("newArr2=>", newArr2);
console.log("arr=>", arr);
console.log("");

// slice(start, end) 写两个参数, 代表从下标start开始截取到end下标  含头不含尾
var newArr3 = arr.slice(2, 5);
console.log("newArr3=>", newArr3);
console.log("arr=>", arr);
console.log("");
数组删除splice()
splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法"会改变原数组"
格式1: splice(start)
格式2: splice(start, deleteCount)
格式3: splice(start, deleteCount, item1)
格式4: splice(start, deleteCount, item1, item2, itemN)
格式4: splice(开始下标, 删除个数, 要添加进数组的元素1, 要添加进数组的元素2, 要添加进数组的元素N)
var arr = [1, 2, 3, 4, 5, 6, 7, 8];
console.log("arr=>", arr);
console.log("");

// 从下标2开始, 删除2个数组元素,  添加100, 200, 300,400到开始下标2的前面
var newArr = arr.splice(2, 2, 100, 200, 300, 400);
console.log("newArr=>", newArr);
console.log("arr=>", arr);

数组遍历 forEach()

  • forEach对数组进行循环。(一般我们也会用for对数组进行循环,但是用for会麻烦一些,因为它要用循环变量)

  • forEach() 方法对数组的每个元素执行一次给定的函数。

参考文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

var arr = [10, 20, 30, 40, 50, 60];
arr.forEach(); // 报错,forEach()方法中需要传递至少一个function参数

var arr = [10, 20, 30, 40, 50, 60];
arr.forEach(function () {}); // 不报错

var arr = [10, 20, 30, 40];
arr.forEach(function () {
    console.log("hello");
});
var arr = [10, 20, 30, 40];
arr.forEach(function () {
    // arguments对象,可以得到实参列表
    console.log("arguments=>", arguments);
}); 

var arr = [10, 20, 30, 40];
arr.forEach(function (item, index, array) {
    console.log("item=>", item);
    console.log("index=>", index);
    console.log("array=>", array);
    console.log("");
});

数组过滤 filter()

  • filter() 方法创建给定数组一部分的浅拷贝,其包含通过所提供函数实现的测试的所有元素。

  • 有几个数组元素,就会执行几次filter传递的function函数

  • 如果filter的function函数中return了一个可以转成true的值,那么表示这个数组元素通过了测试,需要留下, 也就是放进新数组中

参考文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

var newArr = arr.filter(function () {});
console.log("newArr=>", newArr);
console.log("");

var newArr = arr.forEach(function () {});
console.log("newArr=>", newArr);
console.log("");

var newArr = arr.filter(function () {
    return "abc";
});
console.log("newArr=>", newArr);
  • 注意:数组对象的forEach方法是没有返回值的,也就是返回值为undefined

    ​ 数组对象的filter方法的返回值是一个新数组, 该新数组元素的值由function返回的值是否可以转为true决定

var newArr = arr.forEach(function () {
    return true;
});
console.log("newArr=>", newArr);
console.log("");

var newArr = arr.forEach(function () {
    return "abc";
});
console.log("newArr=>", newArr);

逐一处理 map()

  • map() 方法创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的"返回值"组成。

参考文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map

var arr = [1, 4, 9, 16];
var newArr = arr.forEach(function () {})
console.log("newArr=>", newArr);
console.log("");

var newArr = arr.filter(function () {})
console.log("newArr=>", newArr);
console.log("");

var newArr = arr.map(function () {})
console.log("newArr=>", newArr);
console.log("");

var newArr = arr.map(function () {
    return 123;
})
console.log("newArr=>", newArr);
console.log("");

var newArr = arr.map(function () {
    return 1;
})
console.log("newArr=>", newArr);
console.log("");

var newArr = arr.map(function () {
    return 2 < 3;
})
console.log("newArr=>", newArr);
console.log("");

var newArr = arr.map(function (item) {
    console.log("item=>", item, "item*2=>", item * 2);
    return item * 2;
})
console.log("newArr=>", newArr);
  • 小结:
    1. forEach用于遍历数组
    2. filter用于筛选所有符合条件的数组元素
    3. map用于数组元素进行处理

Date对象

Date对象创建方式

  1. new Date(); 创建"当前"日期时间对象

    var date1 = new Date();
            console.log(date1);
    
  2. new Date("日期时间字符串") 创建"指定"日期时间对象

    • 时间可以省略
    var date2 = new Date("2008-08-12");
    console.log(date2);
    var date3 = new Date("2008-8-12");
    console.log(date3);
    var date4 = new Date("2008/08/12");
    console.log(date4);
    var date5 = new Date("2008/8/12");
    console.log(date5);
    
    • 不能只写时间, 日期必须写
    var date6 = new Date("12:17:8");
    console.log(date6);
    var date7 = new Date("12:17:08");
    console.log(date7);
    var date8 = new Date("12:17");
    console.log(date8);
    var date9 = new Date("12");
    console.log(date9);
    
    • 写日期也写时间
    var date10 = new Date("2008-8-12 12:17:8");
    console.log(date10);
    var date11 = new Date("2008-08-12 12:17:08");
    console.log(date11);
    var date12 = new Date("2008/08/12 12:17:08");
    console.log(date12);
    var date13 = new Date("2008/8/12 12:17:08");
    console.log(date13);
    
  3. new Date(year, monthIndex [, day [, hours [, minutes [, seconds [, milliseconds]]]]]);

    注意: 表示月份的整数值,从 0(1 月)到 11(12 月)。

    • 建议至少需要写年份和月份两个参数,其他参数可以省略,有默认值
    console.log(new Date(2012, 2));
    console.log(new Date(2012, 2, 5));
    console.log(new Date(2012, 2, 5, 10));
    console.log(new Date(2012, 2, 5, 10, 8));
    console.log(new Date(2012, 2, 5, 10, 8, 6));
    console.log(new Date(2012, 2, 5, 10, 8, 6, 755));
    
    • 最后一个参数是毫秒数milliseconds,直接输出日期对象,是看不到毫秒数

      需要使用 日期对象.getMilliseconds() 获取日期对象的毫秒数

    console.log(new Date(2012, 2, 5, 10, 8, 6).getMilliseconds());
    console.log(new Date(2012, 2, 5, 10, 8, 6, 755).getMilliseconds());
    

Date对象的方法

方法名 功能
getFullYear() 获取4位数的年份
getMonth() 获取月份 返回值 0~11 0表示1月 11表示12月
getDate() 返回一个月中的某一天 返回值:1~31
getHours() 小时 返回值0~23
getMinutes() 获取分钟 返回值:0~59
getSeconds() 获取秒数 返回值:0~59
getMilliseconds() 获取毫秒 返回值:0~999
getDay() 获取一周中的某一天 就是星期几 返回值:0~6 0代表星期天 1代表星期一
getTime() 获取时间戳 返回从1970年1月1日00时00分00秒到现在的毫秒数!
toLocaleString() 根据本地时间把 Date 对象转换为字符串,并返回结果。

通过Date对象获取总毫秒数(时间戳)

方式1: 日期对象.getTime() 或者 日期对象.valueOf()

var d = new Date();
console.log(d.getTime());
console.log(d.valueOf());
console.log("");

方式2: Date.now();

console.log(Date.now());
console.log("");

方式3: +日期对象 比如 +new Date();

console.log(+new Date());

number对象

Number对象创建方式

方式1: 字面量

var num1 = 10;
console.log(num1);
var num2 = 123.456;
console.log(num2);

方式2: 构造函数Number

var num3 = new Number(10);
console.log(num3);
var num4 = new Number(123.456);
console.log(num4);

Number对象的方法 toFixed()

  • Number.prototype.toFixed()

    • -方法使用定点表示法来格式化一个数值。
  • 语法

    number数值对象.toFixed(小数点后数字的个数)
    
  • 返回值 使用定点表示法表示给定数字的"字符串"。

// 注意: toFixed()会进行四舍五入
var num2 = 123.426;
console.log(num2);
console.log(num2.toFixed(0));
console.log(num2.toFixed(1));
console.log(num2.toFixed(2));
console.log(num2.toFixed(10));

变量数据类型的转换

变量数据类型的转换分为两种

第一种: 强制转换(显式转换) 通过几个JS的内置函数完成转换

第二种: 自动转换(隐式转换) JS根据我们的代码上下文进行自动转换

注意: 强制转换和自动转换的结果是相同的

强制转换(显式转换)

其他类型转换成字符串类型

其他类型转换成数值类型

  1. Number( 变量名或者值 )
  • NaN是一个JS关键字,代表 not a number
数据格式 转换后的结果
"数字" 数字
"数字字符" NaN
"字符数字" NaN
"字符" NaN
"" 和 " "空字符串和有空格的字符串 0
true 1
false 0
undefined NaN
null 0
[] 空数组 0
{} 空对象 以及 非空数组以及非空对象 NaN
  • 小结:只要是在字符串中含有非数字的都会转换为NaN;
  1. parseInt()方法 这个方法我们主要用于提取整数, 不会进行四舍五入操作 返回值是数值类型

    parseInt(string [, radix] ) 解析一个字符串并返回指定基数的十进制整数, radix 是2-36之间的整数,表示被解析字符串的基数。

    参考文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/parseInt

    注意: 如果parseInt()如果第一个参数不是一个字符串,则将其转换为字符串 (使用 ToString抽象操作)。字符串开头的空白符将会被忽略。

console.log(123);
console.log(parseInt(123));
// 5进制的123 => 1*5^2 + 2*5^1 + 3*5^0 = 25 + 10 + 3 = 38 
console.log(parseInt(123, 5));// 5进制的123  但是输出的时候, 我们看到的是十进制的数   

// 8进制21 =>  2*8^1 + 1*8^0 = 16 + 1 = 17
console.log( parseInt(021, 8) );// 8进制的21
console.log( 021.toString() ); // 8进制的21转成字符串以后得到的"17"

// 8进制的17 => 1*8^1 + 7*8^0 = 8 + 7 = 15
console.log( parseInt( "17", 8) );// 8进制的17
  1. parseFloat(string) 函数解析一个参数(必要时先转换为字符串)并返回一个浮点数(小数)

    parseFloat() 从字符串中提取小数 将字符串转换为number类型

    parseFloat() 从一个字符串提取小数 如果第一个字符不是数字会得到NaN 如果是会进行提取

    提取规则 如果遇到了除了第一个.以外的非数字 就会停止提取

console.log(parseFloat("123.456abc"));
console.log(parseInt("123.456abc"));
console.log(parseFloat("123.456.789abc"));
console.log(parseFloat("abc123.456.789"));
NaN问题
  • 判断值是否为NaN, 不可以使用 == 或者 ===

  • 判断值是否为NaN, 需要使用一个内置函数 isNaN()

  • isNaN()判断是否为NaN isNaN => is not a number

    isNaN(变量名) 如果是NaN就返回true 如果不是NaN的就是false

注意: isNaN 会先将一个变量的数据类型自动的转换为Number类型 如果是NaN就会得到true 如果是一个数字的话就是得到false

console.log( isNaN( NaN ) , Number(NaN) );
console.log( isNaN( true ), Number(true) );
console.log( isNaN("abc") , Number("abc") );
console.log( isNaN( 123 ) , Number(123) );
console.log( isNaN( "456") , Number("456") );

参考文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/isNaN#description

其他类型转转换成布尔类型

  • 方式: Boolean( 变量名或者值 )

  • 只需记住5个假值:undefined、 null、0、"" 和NaN 其他的值都是真值

  • 重点记住 Boolean(1) => true, Boolean(0) => false

console.log( Boolean(null) );// false
console.log( Boolean(undefined) );// false
console.log( Boolean(0) );// false
console.log( Boolean("") );// false
console.log( Boolean(NaN) );// false
console.log( Boolean("0") );// true
console.log( Boolean(1) );// true
console.log( Boolean(5) );// true
console.log( Boolean(" ") );// true

自动转换(隐式转换)

在js中,当运算符在运算时,如果两边数据类型不统一,CPU就无法计算,这时我们编译器会自动将运算符两边的数据做一个数据类型转换,转成一样的数据类型再计算。这种无需程序员手动转换,而由编译器自动转换的方式就称为隐式转换

例如: 1 > "0" 这行代码在js中并不会报错,编译器在运算符时会先把右边的"0"转成数字0 然后再比较大小

console.log(1 > "0");

隐式转换规则

  1. 字符串连接符

    介绍:用于数字时,运算符+表示加法运算,而用于字符串时,表示拼接。如果你试图将数字和字符串相加,JavaScript将把数字转换为字符串,再进行拼接。

console.log(10 + 20);
console.log(10 + "20");
  1. 算术运算符

    介绍:如 ++/--(自增自减运算符) + - * / %(算术运算符) > < >= <=(比较运算符)

JavaScript将认为你要执行的是算术运算,而不是字符串运算。JavaScript会将数据转成number类型。

var str = "123";
console.log(str, typeof str);
++str;
console.log(str, typeof str);
console.log("");

var str = "123abc";
console.log(str, typeof str);
++str;
console.log(str, typeof str);
  1. 关系运算符:== !=

    介绍:进行比较时,关系运算符== 或者 != 会考虑其操作数(要比较的两个值)的"类型"。如果两个值的"类型相同",就"直接进行比较",如果两个值的"类型不同",JS则尝试将它们转换为"相同的类型",再进行比较

    1. 细节1: 字符串和数字比较时,字符串转"数字",再比较
    console.log( Number("2") ); 
    console.log( "2" == 2 );
    
    1. 细节2: 数字和布尔比较时,布尔转"数字",再比较
    console.log( Number( true ) );
    console.log( Number( false ) );
    console.log( 5 == true );
    console.log( 1 == true );
    console.log( 0 == true );
    
    1. 细节3: 字符串和布尔比较时,两者转"数字",再比较
    console.log( "1" == true );
    console.log( "0" == false );
    console.log( "5" == false );
    
    1. 细节4: 对象和布尔比较时,两者转"数字",再比较
    console.log( Number( {} ) );
    console.log( {id: 1} == true );
    console.log( {} == false );
    
    1. 细节5: 对象和数值比较时,对象转"数字",再比较
    console.log( Number({ id : 1}) );
    console.log( { id : 1} == 1 );
    
    1. 细节6: undefined与null ==比较时, undefined和null都转"布尔",再比较

      注意:使用严格关系运算符 =(严格相等)和 !(严格不相等)遵循当且仅当两个值的类型和值都相同时,它们才是严格相等的规则。就无需操心类型转换的问题

    console.log( Boolean(undefined) );
    console.log( Boolean(null) );
    console.log( undefined == null );
    console.log( undefined === null );
    
  2. 条件表达式:!(逻辑非运算符)

    介绍:在JavaScript中用于条件表达式中时被视为true或false。

    要记住哪些值是真值,哪些值是假值,只需记住5个假值:undefined、 null、0、"" 和NaN 其他的值都是真值。

console.log(2, !2);
console.log(undefined, !undefined);
console.log(null, !null);
console.log(0, !0);
console.log("", !"");
console.log(NaN, !NaN);

递归

  • 什么是递归

    概念:递归就是直接或间接的调用自身的一种"函数"。递归是一种强大的编程技术。他把一个问题分解为一组"相似"的问题,每一组都使用"通用方法"去解决。简单来说一个递归函数就是调用函数自身去解决它的子问题。

  • 递归, 先递进,再回归,递归通俗地讲就是函数自己调用自己,它具有以下三要素

    1、一个大问题可以分解为更小的问题, 而且用同样的方法解决

    2、分解后的子问题求解方式一样,不同的是规模在变小

    3、存在递归终止条件

  • 注意:在使用递归函数时一定要设置退出函数的条件,否则会发生死循环造成严重问题

  • 注意:递归函数的作用和循环效果一样,由于递归很容易发生“超过最大调用堆栈大小”错误(Maximum call stack size exceeded),所以必须要加退出条件

  • 递归的目的

    递归的目的是用于解决特殊的问题,这类问题需要具备以下的特点

    1. 大问题可以拆分成小问题
    2. 小问题可以继续拆分为小小问题 无论问题规模的大小 解决方式都一样
  • 递归的用法

    1. 第一步:封装一个能解决单一问题的函数(类似循环体)

    2. 第二步:在这个函数中调用当前函数 调整问题的规模(类似循环中的变量更新)

    3. 第三步:在函数中设置递归出口 如果没有递归出口 递归调用无法结束(类似循环中的判断条件)

  • 举例: 使用递归输出10~1

    使用循环输出10~1,包括10和1

    function fn(i) {
        // 控制台输出内容
        console.log(i);
        // 调整规模
        i--;
    
        if (i == 0) {
            // return可以终止函数体的执行
            return;
        }
    
        // 再次调用自身函数
        fn(i);
    }
    fn(10);
    

使用递归实现扁平化数组功能

function flatten(arr) {
    // 创建一个新的空数组
    var newArr = [];
    // 遍历数组arr
    arr.forEach(function (item) {
        // console.log("item=>", item);
        // 如何判断一个值是否为数组
        // console.log( Array.isArray( item ) );
        // console.log("");

        if (Array.isArray(item)) { // 判断item是否为数组
            // console.log("item=>", item );
            // 再次调用flatten()
            var res = flatten(item);
            // 拼接数组
            newArr = newArr.concat(res);
        } else {
            newArr.push(item);
        }
    });

    // 返回newArr
    return newArr;
}

var arr1 = [1, 2, 3, [4, 5, [6, 7], 8, 9]];
console.log(flatten(arr1)); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

var arr2 = [
    [100, 200, [666, [888, 999]]], 10, 20
];
console.log(flatten(arr2)); // [100, 200, 666, 888, 999, 10, 20]

作用域(Scope)

  • 作用域概述

    作用域是当前的执行上下文,值 (en-US)和表达式在其中“可见”或可被访问。如果一个变量 (en-US)或表达式不在当前的作用域中,那么它是不可用的。作用域也可以堆叠成层次结构,子作用域可以访问父作用域,反过来则不行。简单说,作用域就是什么地方能用,什么地方不能用。

  • JavaScript 的作用域分以下三种:
    1. 全局作用域

    全局作用域:脚本模式运行所有代码的默认作用域

    任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问。例如:

    // 全局变量
    var greeting = 'Hello World!';
    function greet() {
      console.log(greeting);
    }
    // 打印 'Hello World!'
    greet();
    
    1. 函数作用域

    函数作用域(局部作用域):由函数创建的作用域

    函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。

    这些变量只能在函数内部访问,不能在函数以外去访问。例如:

    function greet() {
      var greeting = 'Hello World!';
      console.log(greeting);
    }
    // 打印 'Hello World!'
    greet();
    // 报错: Uncaught ReferenceError: greeting is not defined
    console.log(greeting);
    
    1. 块级作用域

    ES6引入了letconst关键字,和var关键字不同,在大括号中使用letconst声明的变量存在于块级作用域中。在大括号之外不能访问这些变量。看例子:

    {
      // 块级作用域中的变量
      let greeting = 'Hello World!';
      var lang = 'English';
      console.log(greeting); // Prints 'Hello World!'
    }
    // 变量 'English'
    console.log(lang);
    // 报错:Uncaught ReferenceError: greeting is not defined
    console.log(greeting);
    

面向过程与面向对象介绍

  • 例:面向过程的数组排序 冒泡排序

​ 面向对象的数组排序 看看有没有什么对象可以帮助排序的 数组对象.sort()方法

  1. 面向过程: 注重于流程顺序
    1. 分析解决问题都需要哪些步骤
    2. 将这些步骤一步一步封装成方法
    3. 根据业务逻辑,将方法依次调用
  2. 面向对象:oop (Object对象 Oriented面向 Programming编程 )
    • 核心思想:关注的是 以事务为中心 提取顺序流程中的参与者(对象) 将各个流程顺序进行划分。

      编写到各个参与者的方法里面,各自按照约定好的顺序,执行各自的流程顺序,来共同完成一个业务逻辑

    • 对象的范畴:万物万事皆对象

    • 对象的特征:属性

    • 对象的行为:方法

    • 面向对象有三大特点:
      1. 封装性 就是封装功能代码到某个对象中
      2. 继承性 子承父业 子构造函数可以继承父构造函数的方法和属性
      3. 多态性 执行同一个方法,传入的对象不一样,输出的结果不一样

参考文档: https://www.jianshu.com/p/fafa021d2ebe
参考文档: https://www.jianshu.com/p/3bac73fe5e65

构造函数

  • 介绍:为了解决从原型对象生成实例(生成对象)的问题,Javascript提供了一个构造函数(Constructor)模式。

    对象构造函数(简称为构造函数)让你能够更好地创建对象。构造函数犹如一个小型工厂,能够创建无数类似的对象。定义后,每当需要创建新对象时都可调用它。

  • 先定义一个构造函数,再使用它来创建对象。所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。再对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。

  • 构造函数也是一种函数, 只是构造函数是专门用于创建对象的函数 为了跟普通函数更好的区分,建议构造函数名首字母大写

  • 在 JS 中,使用构造函数时要注意以下两点:

    1.构造函数用于创建某一类对象,其首字母要大写

    2.构造函数要和 new 一起使用才有意义

  • 构造函数语法

    function 构造函数名( [形参列表] ){
      // 添加属性
    
      this.属性名1 = 形参1;
    
      this.属性名2 = 形参2;
    
      this.属性名3 = 形参3;
    
      ...
    
      // 添加方式
    
      this.方法1 = function(){};
    
      this.方法2 = function(){};
      ...
    
    }
    
function Cat(name, color, weight) {
    console.log("1. this=>", this);

    // 给对象添加属性
    this.name = name;
    this.color = color;
    this.weight = weight;
    console.log("2. this=>", this);

    // 给对象添加方法
    this.cry = function () {
        console.log(this.name + "喵喵喵");
    }
    this.eating = function () {
        console.log(this.name + "吃猫粮");
    }
    console.log("3. this=>", this);
}

var jm = Cat("橘猫", "橘色", 9);
console.log("jm=>", jm);
console.log("");

var yd = new Cat("英短", "灰色", 7);
console.log("yd=>", yd);

构造函数的工作原理

  • 介绍:你知道了如何声明构造函数以及如何使用它来创建对象,但还需看看幕后的情况,以了解构造函数的工作原理。

    要明白构造函数的工作原理,关键在于了解运算符new都做了些什么。

  1. new 首先创建一个新的空对象。
  2. 接下来,new 设置 this,使其指向这个新对象。(this存储了一个引用,指向代码当前处理的空对象。)
  3. 设置this后,调用函数Cat,并将 "大毛","黄色" 和 9作为实参传递给它。
  4. 接下来,执行这个函数的代码。与大多数构造函数一样,Cat给新创建的this对象的属性赋值。
  5. 最后,Cat函数执行完毕后,运算符new返回this指向新创建的对象的引用。
    注意:它会自动为你返回this,你无需在代码中显式地返回。指向新对象的引用被返回后,我们将其赋给变量cat1。
// 定义一个a变量
var a = null;

function Cat(name, color, weight) {
    console.log("1. this=>", this);

    // 给对象添加属性
    this.name = name;
    this.color = color;
    this.weight = weight;
    console.log("2. this=>", this);

    // 给对象添加方法
    this.cry = function () {
        console.log(this.name + "喵喵喵");
    }
    this.eating = function () {
        console.log(this.name + "吃猫粮");
    }
    console.log("3. this=>", this);

    // 把this赋值给a变量
    a = this;
}

var dm = new Cat("大毛", "黄色", 9);
console.log("dm=>", dm);
console.log("a=>", a);
console.log("dm === a", dm === a);

理解对象实例

  • 介绍:你无法通过观察确定JavaScript对象是哪种类型的对象,如小狗或汽车。在JavaScript中,对象是一个动态的结构,无论对象包含哪些属性和方法,其类型都是object

  • 概念: 说对象是某个构造函数的实例可以通过JavaScript提供的方法判断。

    1. 使用运算符instanceof来确定对象是由哪个构造函数创建的。

      console.log(yd instanceof Cat); // true
      console.log(yd instanceof Dog); // false
      console.log(yd instanceof Array); // false
      console.log(yd instanceof Date); // false
      console.log(yd instanceof String); // false
      console.log(yd instanceof Object); // true
      
    2. 使用构造函数创建对象后,实例对象会自动含有一个constructor属性,指向它们的构造函数。

      console.log(yd.constructor);
      console.log(Cat);
      console.log(yd.constructor === Cat);
      

this关键字

  • this是什么

    1. this是一个JS关键字
    2. this的值是一个对象, 所以一般会叫this对象
      console.log(this);
      console.log(typeof this);
  • this的难点

    1. this的值是不确定的, 在不同环境在, this的取值是不一样的
    2. this的值不能被赋值
  • 确定this的值

    1. 函数外 this代表window对象

      console.log("this=>", this);
      
    2. 函数内(对象的方法内)

    //2.1 普通函数内 this代表window对象
    function fn() {
        console.log("我是fn函数");
        console.log("this=>", this);
        console.log("");
    }
    fn();
    window.fn();
    

    **注意: **全局作用域下,使用var定义的全局变量和全局函数会成为window对象的属性和方法

    1. 小结: this的值指向调用该方法或者该函数的对象决定 谁调用我, 我就指向谁

改变函数内部 this对象 指向的三个方法

  • 指定 this 为 null 或 undefined 时会自动替换为指向全局对象Window

Function.prototype.call()

  • Function.prototype.call(thisArg, arg1, arg2, ...) 方法使用一个指定的 this 值和单独给出的一个或多个参数来"调用一个函数"。

  • Object.prototype.toString
    1. Object是一个构造函数,它有一个原型prototype

      这个原型又是一个对象,在Object.prototype这个对象中有一个方法toString

    2. 由于它是一个function,则它可以使用call

    3. Object.prototype.toString的返回值不是数组(只是看起来前后有[ ] )

      它的返回结果是固定格式的:

      [object 当前对象的构造器的名字]

应用场景:封装一个获取数据类型的函数

function getType( val ){
    return Object.prototype.toString.call(val).slice(7,-1);
}
console.log( getType("abc") );
console.log( getType(123) );
console.log( getType(true) );
console.log( getType(false) );
console.log( getType(undefined) );
console.log( getType(function fn(){}) );
console.log( getType(null) );
console.log( getType([52,47,96]) );
console.log( getType({}) );
console.log( getType({id:12,uname:"二狗子"}) );

参考文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call

Function.prototype.apply()

  • Function.prototype.apply() 方法调用一个具有给定 this 值的函数,以及以一个"数组"(或一个"类数组对象")的形式提供的参数。

  • 注意: apply()和call()最明显的区别就是传递参数的形式不同 apply()传递参数是把所有参数放在一个数组中 call()传递参数是一个一个的传

应用场景:求数组的最大值或者最大小值

var arr = [75,96,37,5,61,12];
console.log( Math.max.apply(null, arr) );
console.log( Math.max.apply(undefined, arr) );
console.log( Math.min.apply(null, arr) );
console.log( Math.min.apply(undefined, arr) );

参考文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

Function.prototype.bind

  • Function.prototype.bind( thisArg[, arg1[, arg2[, ...]]] ) 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
  • 注意: bind()跟call(),apply()最明显的区别是 , bind()是返回一个新的函数, bind()不会调用函数

应用场景:更换延时器指定对象达到禁用按钮目的

var btn = document.querySelector("button");
btn.onclick = function(){
    console.log("获取验证码成功");
    // 禁用控件
    this.disabled = true;

    // 延时器
    window.setTimeout(function(){
        console.log("延时器的代码执行了");
        console.log("this=>", this);
        // 取消禁用
        this.disabled = false;
    }.bind(this), 3000 );
}

参考文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

构造函数的弊端

  • 介绍:构造函数方法很好用,但是存在浪费内存的问题。

    构造函数方法每次创建一个实例,"就会单独创建一个空间来存放同一个方法",这样就比较浪费内存

  • 通过以上代码,我们发现内置对象Array创建的不同实例之间的同一个方法是可以共享空间的,

    但是我们自己创建的构造函数不同实例之间同一个方法是无法共享空间的

  • 原因是, 内置对象的方法都是放在"构造函数身上的一个prototype属性中"

    这个prototype属性的属性值是一个对象,所以我们也会称之为prototype对象, 也可以叫原型对象

    prototype对象上面的方法和属性都可以被不同的实例所共享

构造函数原型对象

prototype(显式原型)

  • 构造函数通过原型分配的方法和属性是所有实例对象所共享的。

  • JavaScript 规定,每一个构造函数都有一个prototype 属性,指向另一个对象。注意这个prototype就是一个对象,这个原型对象的所有方法和属性,都会被构造函数所拥有。

  • 我们可以把那些不变的方法或者属性,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法和在这些属性。

  • 注意:一般情况下,我们的公共属性和公共方法我们放到原型对象身上 , 而不同的属性定义到构造函数里面

  • 注意:实例对象身上也可以添加属性和方法

    1. 原型是什么?

      一个对象,我们也称为 prototype 为原型对象。

    2. 原型的作用是什么?

      主要用于共享方法。

function Cat(name, color, weight) {
    this.name = name;
    this.color = color;
    this.weight = weight;
    // 直接在构造函数内部添加方法
    this.cry = function () {
        console.log(this.name + "喵喵喵");
    }
    this.eating = function () {
        console.log(this.name + "吃猫粮");
    }
}

// 输出Cat构造函数
console.log(Cat);
// 输出Cat构造函数的原型对象prototype
console.log(Cat.prototype);
// 给原型对象添加方法
Cat.prototype.sleep = function () {
    // 原型对象添加的方法中this指向 调用这个方法的实例对象 
    console.log("this=>", this);
    console.log(this.name + "睡觉觉");
}
Cat.prototype.runing = function () {
    console.log(this.name + "跑步步");
}
console.log(Cat.prototype);

__proto__(隐式原型)

  • "实例对象"身上也有一个属性__proto__ 这个属性会指向当前构造函数的原型对象

    这个__proto__属性就称之为对象原型 也可以叫隐式原型

  • 正是因为实例对象身上有对象原型, 所以我们的实例对象才可以使用构造函数原型对象身上添加的方法和属性

// 数组的原型对象
console.log(Array.prototype);

// 创建数组的实例对象
var arr1 = [10, 20, 30, 40, 50];
var arr2 = new Array(100, 200, 300);
console.log(arr1.__proto__);
console.log(arr2.__proto__);

console.log(arr1.__proto__ === Array.prototype); //true
console.log(arr2.__proto__ === Array.prototype); //true

原型对象的应用——扩展内置对象方法

// 求数组元素和的方法
Array.prototype.getSum = function () {
    // 在原型对象添加的方法中,this代表调用这个方法的实例对象
    // console.log("this=>", this);

    // 定义一个变量,保存和
    var sum = 0;

    // 遍历数组
    for (var i = 0; i < this.length; i++) {
        sum += this[i];
    }

    return sum;
}

// 求数组元素最大值的方法
Array.prototype.getMax = function () {
    // 假设数组第一个元素为最大值
    var maxVal = this[0];
    // 遍历数组
    for (var i = 1; i < this.length; i++) {
        if (maxVal < this[i]) { // 比较大小
            maxVal = this[i];
        }
    }
    // 返回找到的最大值
    return maxVal;
}
<!-- 通过你script标签的src属性引入外链式js文件 -->
<script src="./js/my.js"></script>

<script>
    var arr1 = [10, 20, 45, 96, 75];
    console.log(arr1.getSum());
    console.log(arr1.getMax());
    console.log("");

    var arr2 = [52, 62, 37, 12, 95];
    console.log(arr2.getSum());
    console.log(arr2.getMax());
    console.log("");

    var arr3 = new Array(1, 2, 3, 4, 5, 6);
    console.log(arr3.getSum());
    console.log(arr3.getMax());
</script>

constructor构造器属性

  • 构造函数原型对象身上自带一个constructor构造器属性

    constructor构造器属性作用是用于指回"构造函数本身"

    function Cat(name, color, weight) {
        this.name = name;
        this.color = color;
        this.weight = weight;
    }
    console.log(Cat.prototype);
    console.log(Cat.prototype.constructor);
    console.log(Cat.prototype.constructor === Cat); //true
    
    // 实例对象身上的对象原型指向构造函数的原型对象
    // 通过 new 构造函数名() 得到实例对象
    var bsm = new Cat("波斯猫", "黑白", 9);
    console.log(bsm.__proto__);
    console.log(bsm.__proto__.constructor);
    console.log(bsm.__proto__.constructor === Cat); //true
    
  • 当我们给构造函数的原型对象赋值一个对象的时候, constructor属性就会丢失

    Cat.prototype = {
        say: function () {
            console.log(this.name + "喵喵喵");
        },
        eating: function () {
            console.log(this.name + "吃东西");
        },
        sleep: function () {
            console.log(this.name + "睡觉");
        }
    };
    console.log(Cat.prototype);
    

原型链

  • 对于使用过基于类的语言 (如 Java 或 C++) 的开发者们来说,JavaScript 实在是有些令人困惑 —— JavaScript 是动态的,本身不提供一个 class 的实现。即便是在 ES2015/ES6 中引入了 class 关键字,但那也只是语法糖,JavaScript 仍然是基于原型的。

  • 当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 proto)指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(proto),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

  • 几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

  • 介绍:在 JavaScript 中所有的对象(除了少数例外)都有自己的原型。而且,对象的原型本身也是一个对象。正因为原型是一个对象,所以原型对象也有它自己的原型!

  • 概念:所有对象都有一个__proto__(隐式原型)属性,属性指向它构造函数的prototype,当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。

参考文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

// 数组实例对象
var arr = [10, 20, 30];
// 数组对象原型
console.log(arr.__proto__);
// 数组对象原型指向数组的原型对象
console.log(Array.prototype);
console.log(arr.__proto__ === Array.prototype);
// 原型对象也有一个自己的对象原型(__proto__)
console.log(Array.prototype.__proto__);
// Object的原型对象
console.log(Object.prototype);
console.log(Array.prototype.__proto__ === Object.prototype);

console.log(Object.prototype.__proto__); // null

原型链成员的查找机制

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

  • JavaScript 的成员查找机制(规则):

    1. 当访问一个对象的属性(或者方法)时,首先查找这个对象自身有没有该属性(或者方法)。
    2. 如果没有就查找构造函数里面是否有这个属性或者方法
    3. 如果构造函数里面也没有, 就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
    4. 如果它的原型还没有, 就查找构造函数原型对象的原型(Object的原型对象)。
    5. 依此类推一直找到 Object 为止(null)。
    6. __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
    7. 如果多个原型对象同时有同一个属性(或者方法),遵循就近原则
function Cat() {
    this.color = "构造函数里面设置的颜色: 蓝色";
    this.say = function () {
        console.log("构造函数里面设置的say方法");
    }
}

Cat.prototype.color = "Cat构造函数原型对象身上设置的颜色: 绿色";
Object.prototype.color = "Object构造函数原型对象身上设置的颜色: 灰色";

Cat.prototype.say = function () {
    console.log("Cat构造函数原型对象设置say方法");
}

Object.prototype.say = function () {
    console.log("Object构造函数原型对象身上设置say方法");
}

var bsm = new Cat();
bsm.color = "波斯猫实例对象身上的颜色: 红色";
bsm.say = function () {
    console.log("波斯猫实例对象设置的say方法");
}

console.log(bsm.color);
bsm.say();
console.log("");
console.log("");
console.log("");

// JS中所有对象都可以顺着原型链找到Object的原型对象
var arr = [20, 50, 60];
console.log(arr);
console.log(arr.color);
arr.say();
console.log("");
console.log("");
console.log("");
  • 另外,可以使用in运算符可以用来判断,某个实例是否含有某个属性

    如果指定的属性在指定的对象或其原型链中,则in 运算符返回true。

    console.log("属性名" in 某个对象 );

    console.log("length" in arr);
    console.log("abc" in arr);
    console.log("color" in arr);
    

实现构造函数之间的继承

  • 继承包括继承属性和继承方法

    继承简单就是子承父业 子构造函数继承父构造函数的方法和属性, 子构造函数也可以有自己独有的方法和属性

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

Animal.prototype.say = function () {
    console.log(this.name + "say方法");
}
Animal.prototype.eating = function () {
    console.log(this.name + "eating方法");
}

// 猫构造函数
function Cat(name, age, weight, color) {
    /* this.name = name;
            this.age = age;
            this.weight = weight; */

    // 继承属性
    // Animal.call(this, name, age, weight);
    Animal.apply(this, [name, age, weight]);

    // 独有属性color
    this.color = color;
}
  • 继承方法, 把父构造函数的原型对象,赋值给子构造函数的原型对象 会出现引用问题 因为是对象之间的赋值操作
Cat.prototype = Animal.prototype;

// 手动设置constructor属性
Cat.prototype.constructor = Cat;
  • 使用for...in遍历Animal构造函数的原型对象
for (var attr in Animal.prototype) {
    // console.log("attr=>", attr);
    // console.log("Animal.prototype[attr]=>", Animal.prototype[attr]);
    // console.log("");

    Cat.prototype[attr] = Animal.prototype[attr];
}

Cat.prototype.catch = function () {
    console.log(this.name + "catch方法");
}
var bsm = new Cat("波斯猫", 2, 9, "白");
console.log(bsm);
bsm.say();
bsm.eating();
bsm.catch();

静态属性与静态方法

  • 通过"构造函数自身"调用的属性,称之为静态属性
console.log(Math.PI);
  • 通过"构造函数自身"调用的方法,称之为静态方法
console.log(Math.random());
  • 通过实例对象调用的属性,称之为实例属性
var arr = [10, 20, 30];
console.log(arr);
console.log(arr.length);
  • 通过实例对象调用的方法,称之为实例方法
arr.pop();
  • 例:
function Person(name, age, sex) {
    // 实例属性
    this.name = name;
    this.age = age;
    this.sex = sex;
}

// 实例方法
Person.prototype.sayHello = function () {
    console.log(this.name + ",hello");
}

// 通过构造函数自身添加的属性,就是静态属性
Person.COUNTRY = "China";
// 通过构造函数自身添加的方法,就是静态方法
Person.sleep = function () {
    console.log("睡觉");
}

var zs = new Person("zhangsan", 23, "男");
// 实例属性和实例方法是通过"实例对象"调用的
console.log(zs.name);
console.log(zs.age);
console.log(zs.sex);
zs.sayHello();
console.log("");
console.log("");

// 静态属性和静态方法是通过"构造函数自身"调用的
console.log(zs.COUNTRY);
console.log(Person.COUNTRY);
// zs.sleep();// 报错
Person.sleep();

引用(深浅拷贝)

  • 概念: 变量并不实际存储"对象"。"变量存储指向对象的引用"。引用就像指针,是对象的存储地址: 换句话说,变量并不存储对象本身,而是存储类似于指针的东西。在JavaScript中,但我们需要知道,当对象赋值给变量时它肯定指向相应的对象。当我们使用属性与方法时,JavaScript将负责根据引用获取对象并访问其属性。

  • 浅拷贝指的是仅拷贝对象的"指针地址",而不复制对象本身,新旧对象还是"共享同一块内存",这样会使被拷贝的对象会因为拷贝的对象的数据改变而改变

  • 深拷贝指的是拷贝一个对象的数据之前先给拷贝的对象创建一个堆地址,创造一个一模一样的值 , 新值跟原值"不共享内存", 这样当拷贝的对象指向的堆中的数据改变时,被拷贝的对象堆中的数据并不会被改变(意思就是o,obj指向不同的堆)

  • JS中基本数据不存在引用问题

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

    注:Symbol 是 ES6 引入了一种新的原始数据类型,表示独一无二的值。

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

// 传递的意思就是 一个变量的值"赋值"给另一个变量
// 值类型(基本类型)的传递是值传递, 互不相干
var a = 10;
var b = a;
console.log("a=>", a);
console.log("b=>", b);
console.log("");
console.log("");
// 改变a的值
a = 20;
console.log("a=>", a);
console.log("b=>", b);
console.log("");
console.log("");

// 改变b的值
b = 50;
console.log("a=>", a);
console.log("b=>", b);
console.log("");
console.log("");


// 引用数据类型(对象类型)的传递是引用传递, 同生共死
var obj1 = {
    id: 1,
    name: "zhangsan",
    age: 23
};
var obj2 = obj1;
console.log("obj1=>", obj1);
console.log("obj2=>", obj2);
console.log("");
console.log("");

// 修改obj1的id属性
obj1.id = 123;
console.log("obj1=>", obj1);
console.log("obj2=>", obj2);
console.log("");
console.log("");

// 修改obj2的age属性
obj2.age = 24;
console.log("obj1=>", obj1);
console.log("obj2=>", obj2);
console.log("");
console.log("");

// 给obj1添加sex属性
obj1.sex = "男";
console.log("obj1=>", obj1);
console.log("obj2=>", obj2);
console.log("");
console.log("");

// 删除obj2里面的name属性
delete obj2.name;
console.log("obj1=>", obj1);
console.log("obj2=>", obj2);


// 数组也属于引用类型数据
var arr1 = [10, 20, 30];
var arr2 = arr1;
console.log("arr1=>", arr1);
console.log("arr2=>", arr2);
console.log("");
console.log("");

arr1[0] = 111;
console.log("arr1=>", arr1);
console.log("arr2=>", arr2);
console.log("");
console.log("");

arr2[1] = 222;
console.log("arr1=>", arr1);
console.log("arr2=>", arr2);
console.log("");
console.log("");

arr1.push(500, 600);
console.log("arr1=>", arr1);
console.log("arr2=>", arr2);

解除数组引用

浅拷贝(解决第一层引用)

解除数组引用核心: 遍历数组赋值给新数组 或者 使用数组对象内置方法得到一个新数组

注意: 该方式只能解决外层引用问题,如果内层数据再次出现数组或者对象, 内层数据还是会引用

​ 也就是实现的是解决第一层的引用, 深层的引用解决不了

var arr1 = [
    10,
    [21, 22, 23],
    30,
    {
        id: 1,
        uname: "zhangsan",
        age: 23
    }
];
/*var arr2 = [];
// 遍历数组arr1
for (var i = 0; i < arr1.length; i++) {
    arr2[i] = arr1[i];
}*/

var arr2 = arr1.map(function (item) {
    return item;
});
console.log("arr1=>", arr1);
console.log("arr2=>", arr2);

深拷贝

实现深拷贝 自己封装函数

// obj参数表示需要被深拷贝的对象
function deepCopy(obj) {
    // 判断obj参数的数据类型是否为"object"
    if (typeof obj !== "object") {
        return obj;
    }

    // 如果obj是对象,我们就创建新的空对象
    // 如果obj是数组,我们就创建新的空数组
    var newObj = Array.isArray(obj) ? [] : {};

    // 遍历obj
    for (var attr in obj) {
        if (typeof obj[attr] !== "object") {
            // 如果obj[attr]的数据类型是基本数据类型,可以直接赋值给newObj
            newObj[attr] = obj[attr];
        } else {
            // 如果obj[attr]的数据类型是引用数据类型,需要再次进行deepCopy
            newObj[attr] = deepCopy(obj[attr]);
        }
    }

    // 返回newObj对象
    return newObj;
}

解除对象引用

浅拷贝指的是仅拷贝对象的指针地址,而不复制对象本身,新旧对象还是"共享同一块内存",这样会使被拷贝的对象会因为拷贝的对象的数据改变而改变

深拷贝指的是拷贝一个对象的数据之前先给拷贝的对象创建一个堆地址,创造一个一模一样的值 , 新值跟原值不共享内存, 这样当拷贝的对象指向的堆中的数据改变时,被拷贝的对象堆中的数据并不会被改变

浅拷贝(解决第一层引用)

  1. 方式一: for...in遍历 实现的是"浅拷贝"

    注意: 该方式只能解决外层引用问题,如果内层数据再次出现数组或者对象, 内层数据还是会引用

    ​ 也就是实现的是解决第一层的引用, 深层的引用解决不了

var obj1 = {
            id: 1,
            uname: "zhangsan",
            age: 23,
            hobby: ["吃饭", "睡觉", "打豆豆"]
        }
        var obj2 = {};
        // for...in遍历obj1
        for (var attr in obj1) {
            obj2[attr] = obj1[attr];
        }

        console.log("obj1=>", obj1);
        console.log("obj2=>", obj2);
  1. 方式二: 使用ES6新增的一个Object.assign()合并对象的方法 实现的是"浅拷贝"
  • 语法

    Object.assign(目标对象, 源对象1, 源对象2, 源对象3... )

  • 返回值 修改后的目标对象。

  • 注意: 合并的时候, 会把源对象列表中的属性添加到目标对象中, 如果同名属性将覆盖属性值

参考文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

var obj1 = {
    id: 1,
    uname: "zhangsan",
    age: 23,
    hobby: ["吃饭", "睡觉", "打豆豆"]
}
var obj2 = {};
Object.assign(obj2, obj1);
console.log("obj1=>", obj1);
console.log("obj2=>", obj2);
console.log("");

obj1.id = 11;
obj2.uname = "lisi";
obj1.hobby[0] = "烧烤";
console.log("obj1=>", obj1);
console.log("obj2=>", obj2);

深拷贝

  • JSON.stringify() 与 JSON.parse() 配合使用

  • JSON.stringify()把JSON对象转成JSON字符串的方法

  • JSON.parse() 把JSON字符串转成JSON对象

  • 注意: 该种方式不能拷贝对象中的方法以及属性值为undefined的属性

var obj1 = {
    name: "小明",
    age: 19,
    address: "北京",
    hobby: ["抽烟", "喝酒", "烫头"],
    data: {
        friends: ["lee", "Tom", "Jane"],
        mother: "韩梅"
    },
    sayHello: function () {
        console.log("hello");
    },
    sayHi: function () {
        console.log("Hi");
    },
    sex: undefined
};
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1);
console.log(obj2);
console.log("");
obj1.name = "马冬梅";
obj2.age = 18;
obj1.hobby[0] = "唱跳Rap";
obj2.data.mother = "马什么梅";
console.log(obj1);
console.log(obj2);

高阶函数

  1. 高阶函数情况1: 把函数当做参数传递进另一个函数中, 我们使用的比较多的就是回调函数
var arr = [6, 9, 1, 42, 3];
console.log(arr);
console.log("");

arr.forEach(function (item) {
    console.log("item=>", item);
});
console.log("");

arr.sort(function (a, b) {
    return a - b;
})
console.log(arr);
  1. 高阶函数情况2: 把函数当做返回值返回出来
function fn(a) {
    // return返回
    return function (b) {
        console.log("a=>", a);
        console.log("b=>", b);
        console.log(a + b);
    }
}

fn(30)(40);

闭包

  • 什么是闭包

    闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函

    数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

  • 闭包让开发者可以从内部函数访问外部函数的作用域就是表示内层函数可以访问外层函数定义的形参以及变量

  • 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。我们知道Javascript语言的特殊之处,就在于"函

    数内部可以直接读取全局变量"。另一方面,在函数外部自然无法读取函数内的局部变量。而闭包就是能够读取其他函数内部变量的函数。在本质上,闭

    包就是将函数内部和函数外部连接起来的一座桥梁。

  • 创建闭包最简单方式就是函数嵌套函数

  • 闭包的特性🧡

    1.函数内再嵌套函数

    2.内部函数可以引用外层函数的形参和局部变量

    3.被引用的形参和局部变量不会被垃圾回收机制回收

参考文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures

function fn1() {
    var num = 10;

    function fn2() {
        num++;
        console.log(num);
    }

    return fn2;
}
fn1()();

点击事件,获取索引号

// 解决方式一: 自定义属性
var lis = document.querySelectorAll("li");
for (var i = 0; i < lis.length; i++) {
    lis[i].dataset.index = i;
    lis[i].onclick = function () {
        console.log("li被点击以后,才会执行click事件函数里面对应的代码");
        console.log("i=>", i);
        var index = this.dataset.index;
        console.log("index=>", index);
        console.log("");
    };
}
console.log("i=>", i);
console.log(111);
console.log(222);
console.log(333); 

// 解决方式二: 闭包
var lis = document.querySelectorAll("li");
for (var i = 0; i < lis.length; i++) {
    // 立即执行函数
    ;(function( index ){
        lis[i].onclick = function () {
            console.log(index);
        };
    })( i );
} 

// 解决方式二: 闭包
var lis = document.querySelectorAll("li");
for (var i = 0; i < lis.length; i++) {
    lis[i].onclick = function (index) {
        return function () {
            console.log("index=>", index);
        }
    }(i);
}

正则表达式

  • 什么是正则表达式

    介绍:正则表达式(regular expression)是一个描述字符模式的对象,是一种表达文本模式(即字符串结构)的方法,有点像字符串的模板,常常用来按照“给定模式”匹配文本。

  • 正则表达式的特点

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

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

    3. 对于刚接触的人来说,比较晦涩难懂。比如:/^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$/ 验证邮箱的表达式

    4. 实际开发,一般都是直接复制写好的正则表达式. 但是要求会使用正则表达式并且根据实际情况修改正则表达式.

      比如用户名: /^[a-z0-9_-]{3,16}$/

  • 正则表达式的作用

    正则表通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)。此外,正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等 。

  • 常用正则表达式

    vscode可以安装any-rule插件, 该插件提供了一些常用的正则表示式 安装完成以后 按ctrl+shift+p快捷键以后, 找到正则大全 可以根据需求改一些常用正则表达式

  • 正则表达式的组成

    一个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单字符和特殊字符的组合,比如 /ab*c/ 。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+、 * 、 ?等。

  • 特殊字符非常多,可以参考相关文档:

    1. jQuery手册正则表达式部分 https://jquery.cuishifeng.cn/regexp.html

    2. 菜鸟教程 https://www.runoob.com/regexp/regexp-tutorial.html

    3. 菜鸟教程正则表达式在线测试 https://c.runoob.com/front-end/854/

    4. MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions

边界符

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

    ^ 表示匹配行首的文本(以谁开始)

    $ 表示匹配行尾的文本(以谁结束)

  • 注意: 如果 ^和 $ 在一起,表示必须是"精确匹配",表示"只能是这个字符串"

// 含有"a"字符
var reg1 = /a/;
console.log(reg1.test("a"));
console.log(reg1.test("a123"));
console.log(reg1.test("123a"));

// 以"a"字符开头
var reg2 = /^a/;
console.log(reg2.test("a"));
console.log(reg2.test("a123"));
console.log(reg2.test("123a"));

// 以"a"字符结尾
var reg3 = /a$/;
console.log(reg3.test("a"));
console.log(reg3.test("a123"));
console.log(reg3.test("123a"));

// 注意: 如果 ^和 $ 在一起,表示必须是"精确匹配",表示"只能是这个字符串"
var reg4 = /^a$/;
console.log(reg4.test("a"));
console.log(reg4.test("a123"));
console.log(reg4.test("123a"));

// 以"abc"开头
var reg5 = /^abc/;
console.log(reg5.test("abc123"));
console.log(reg5.test("a123bc"));
console.log(reg5.test("a123b456c"));

字符类

  • 字符类表示有一系列字符(多个字符)可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号[]内。

  • 方括号 []

    [] 方括号,表示有一系列字符可供选择,只要匹配其中一个就可以了

  • 范围符 -

    [-] 方括号内部 范围符-

    方括号内部加上 - 表示范围

    可以在中括号里面加一个短横线表示一个范围 ,比如[a-z]表示a到z的26个英文字母任何一个字母,包括a和z

  • 取反符 ^

    [^] 方括号内部 取反符^

  • 字符组合

    方括号内部可以使用字符组合,这里表示包含 a 到 z 的26个英文字母和 1 到 9 的数字都可以

    举例: /[a-z1-9]/.test('andy')

// 方括号
var reg1 = /[qw]/; // 含有一个q或者一个w
console.log(reg1.test("q"));
console.log(reg1.test("w"));
console.log(reg1.test("q123"));

// 范围符-
var reg2 = /[a-z]/; // 含有一个a到z,包括a和z,其中一个字符
console.log(reg2.test("a123"));
console.log(reg2.test("1b23"));
console.log(reg2.test("12c3"));

// 取反符^
//  [^] 方括号内部 取反符^
// var reg4 = /[abc]/; // 含有一个 a, b, c其中一个
var reg4 = /[^abc]/; // 含有a,b,c之外其他的一个字符即可
console.log(reg4.test("a"));
console.log(reg4.test("d"));
console.log(reg4.test("abc"));
console.log(reg4.test("abc1"));

// 字符组合
var reg3 = /[0-3a-c@]/; // 含有0,1,2,3 a,b,c,@其中一个
console.log(reg3.test("0"));
console.log(reg3.test("qw0e"));
console.log(reg3.test("@_@"));

量词符

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

  • 注意: 没有小括号的情况下, 量词符是修饰前一个字符出现的次数

操作符 描述
* 重复0次或更多次
+ 重复1次或更多次
? 重复0次或1次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次
// * 重复0次或更多次
var reg2 = /^ab*$/; // *元字符此时修饰的是b
console.log(reg2.test("123"));
console.log(reg2.test("a"));
console.log(reg2.test("abbbb"));
console.log(reg2.test("abbbbb"));

// + 重复1次或更多次
var reg3 = /^ab+$/;
console.log(reg3.test("123"));
console.log(reg3.test("a"));
console.log(reg3.test("abbbb"));
console.log(reg3.test("abbbbb"));

// ? 重复0次或1次
var reg4 = /^ab?$/;
console.log(reg4.test("123"));
console.log(reg4.test("a"));
console.log(reg4.test("abbbb"));
console.log(reg4.test("abbbbb"));

// {n} 重复n次
var reg5 = /^ab{2}$/;
console.log(reg5.test("123"));
console.log(reg5.test("a"));
console.log(reg5.test("abbbb"));
console.log(reg5.test("abbbbb"));

// {n,} 重复n次或更多次
var reg6 = /^ab{2,}$/;
console.log(reg6.test("123"));
console.log(reg6.test("a"));
console.log(reg6.test("abbbb"));
console.log(reg6.test("abbbbb"));

// {n,m} 重复n到m次
var reg7 = /^ab{2,3}$/;
console.log(reg7.test("123"));
console.log(reg7.test("a"));
console.log(reg7.test("abbbb"));
console.log(reg7.test("abbbbb"));

组匹配,分组,使用()表示

  • 功能: 把多个原子,组在一起,成一个大原子

  • 区别:

    /^abc{2}$/ 代表c这个字符重复2次

    /^(abc){2}$/ 代表abc这个整体进行重复2次

var reg1 = /^abc{2}$/;
console.log(reg1.test("abc")); // false
console.log(reg1.test("abcc")); // true
console.log(reg1.test("abccc")); // false
console.log(reg1.test("abcabc")); // false
console.log(reg1.test("abcabcabc")); // false

var reg2 = /^(abc){2}$/;
console.log(reg2.test("abc")); // false
console.log(reg2.test("abcc")); // false
console.log(reg2.test("abccc")); // false
console.log(reg2.test("abcabc")); // true
console.log(reg2.test("abcabcabc")); // false

var reg3 = /^a(bc){2}$/;
console.log(reg3.test("abc")); // false
console.log(reg3.test("abcc")); // false
console.log(reg3.test("abccc")); // false
console.log(reg3.test("abcabc")); // false
console.log(reg3.test("abcabcabc")); // false
console.log(reg3.test("abcbc")); // true

括号总结

1.中括号 字符集合 匹配方括号中的任意字符

2.大括号 量词符 里面表示重复次数

3.小括号 组匹配 可以让多个小原子组成一个大原子

在线测试正则表达式:regexper(外国网站) https://regexper.com/

预定义类

预定义类指的是某些常见模式的简写方式

字符 描述
\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“n”匹配字符“n”。“\n”匹配一个换行符。串行“\\”匹配“\”而“\(”则匹配“(”。
^ 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。
$ 匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。
* 匹配前面的子表达式零次或多次。例如,zo能匹配“z”以及“zoo”。等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。
? 匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“does”或“does”中的“do”。?等价于{0,1}。
{n} n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。
{n,} n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。
{n,m} mn均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。
? 当该字符紧跟在任何一个其他限制符(,+,?,{n},{n,},{n,m*})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o+”将匹配所有“o”。
. 匹配除“\n”之外的任何单个字符。要匹配包括“\n”在内的任何字符,请使用像“`(.
(pattern) 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“\(”或“\)”。
(?:pattern) 匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“`(
(?=pattern) 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“`Windows(?=95
(?!pattern) 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“`Windows(?!95
(?<=pattern) 反向肯定预查,与正向肯定预查类拟,只是方向相反。例如,“`(?<=95
(?<!pattern) 反向否定预查,与正向否定预查类拟,只是方向相反。例如“`(?<!95
x|y 匹配x或y。例如,“`z
[xyz] 字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。
[^xyz] 负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“p”。
[a-z] 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。
[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。
\B 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
\cx 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。
\d 匹配一个数字字符。等价于[0-9]。
\D 匹配一个非数字字符。等价于[^0-9]。
\f 匹配一个换页符。等价于\x0c和\cL。
\n 匹配一个换行符。等价于\x0a和\cJ。
\r 匹配一个回车符。等价于\x0d和\cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于[^ \f\n\r\t\v]。
\t 匹配一个制表符。等价于\x09和\cI。
\v 匹配一个垂直制表符。等价于\x0b和\cK。
\w 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。
\W 匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。
\xn 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。正则表达式中可以使用ASCII编码。.
*num* 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。
*n* 标识一个八进制转义值或一个向后引用。如果*n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n*为一个八进制转义值。
*nm* 标识一个八进制转义值或一个向后引用。如果*nm之前至少有nm个获得子表达式,则nm为向后引用。如果*nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若nm均为八进制数字(0-7),则*nm将匹配八进制转义值nm*。
*nml* 如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。
\un 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。

参考文档: https://jquery.cuishifeng.cn/regexp.html

常用正则表达式

类型 正则
用户名 /{3,16}$/
密码 /{6,18}$/
十六进制值 /^#?([a-f0-9]{6}|[a-f0-9]{3})$/
电子邮箱 /^([a-z0-9_.-]+)@([\da-z.-]+).([a-z.]{2,6})$/ /+(.[a-z\d]+)*@(\da-z?)+(.{1,2}[a-z]+)+$/
URL /^(https?😕/)?([\da-z.-]+).([a-z.]{2,6})([/\w .-])/?$/
IP 地址 /((2[0-4]\d|25[0-5]|[01]?\d\d?).){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)/ /^(?😦?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
HTML 标签 /<([a-z]+)([<]+)(?:>(.)</\1>|\s+/>)$/
删除代码\注释 (?<!http:|\S)//.*$
Unicode编码中的汉字范围 /+$/

正则表达式的创建

  1. 方式一:通过调用RegExp对象的构造函数创建

    var 变量名 = new RegExp("正则表达式" [,"匹配模式"] );

var reg1 = new RegExp("a");
console.log(reg1);
  1. 方式二:利用字面量创建(推荐) 正则表达式

    var 变量名 = /正则表达式/[匹配模式];

var reg2 = /a/;
console.log(reg2);

注意: 匹配模式可以省略不写

匹配模式

匹配模式也叫修饰符:表示正则匹配的附加规则,放在正则模式的最尾部。

修饰符可以单个使用,也可以多个一起组合使用, 多个修饰符组合使用时,不分顺序。

修饰符 含义 描述
i ignore - 不区分大小写 将匹配设置为不区分大小写,搜索时不区分大小写: A 和 a 没有区别。
g global - 全局匹配 查找所有的匹配项。
m multi line - 多行匹配 使边界字符 ^$ 匹配每一行的开头和结尾,记住是多行,而不是整个字符串的开头和结尾。
s 特殊字符圆点 . 中包含换行符 \n 默认情况下的圆点 . 是匹配除换行符 \n 之外的任何字符,加上 s 修饰符之后, . 中包含换行符 \n。
gi 全局匹配 + 忽略大小写 复合叠加
  • 如何设置给正则表达式添加匹配模式

    方式1. 字面量创建正则表达式 /正则表达式/匹配模式

    方式2. 构造函数RegExp创建正则表达式 new RegExp("正则表达式", "匹配模式")

参考文档: https://www.runoob.com/regexp/regexp-flags.html

正则对象相关方法

正则对象.test(str): 判断字符串中是否具有指定模式的字符串,返回结果是一个布尔类型的值;

参考文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test

正则对象.exec(str): 返回字符串中指定模式的子串,一次只能获取一个与之匹配的结果;

参考文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec

  • 格式:

    返回值 = 正则.exec(目标字符串)

  • 功能: 匹配。在目标字符串中,找出符合正则表达式要求的字符串。

    • 返回值:如果能够匹配某个字符串,则返回值是一个数组。其中:

      第一个是匹配成功的字符串;

      第二个index,表示在哪里匹配到的。

      第三个input,表示目标字符串

    • 如果不能匹配,则返回值是null

  • 注意: 一次exec只能得到一个匹配成功的结果 ,如果要全部匹配出来,则需要调用多次exec

  • 注意: 如果要全部结果匹配出来, 全局匹配模式g不能少! 否则exec每次只能得到字符串中第一次匹配到的那个结果

var str = "生日是:19901-10-10 生日是:1998-11-12 生日是2003-05-03 生日是3003-06-07 生日是2008-08-08";
// 匹配生日字符串
// var reg = /[12]\d{3}-\d{2}-\d{2}/;
var reg = /[12]\d{3}-\d{2}-\d{2}/g;
// 有一个问题,我们事先并不知道,这个匹配会成功多少次?所以我不能用for循环去执行匹配。
// 可以使用while循环匹配

var res;
while (res = reg.exec(str)) {
    console.log(res);
}

String对象跟正则相关方法

方法 功能
字符串对象.search(reg) 返回指定模式的子串在字符串"首次"出现的位置
字符串对象.match(reg) 以数组的形式返回指定模式的字符串,可以返回所有匹配的结果 🧡
字符串对象.replace(reg, 替换后的字符) 把指定模式的子串进行替换操作 🧡
字符串对象.split(reg) 以指定模式分割字符串,返回结果为数组
  1. 字符串对象.search(reg)

    • 与indexOf非常类似,返回指定模式的子串在字符串"首次"出现的位置

    • indexOf参数不建议写正则表达式 不管能否找到,都是返回-1

var str = "生日是:19901-10-10 生日是:1998-11-12 生日是2003-05-03 生日是3003-06-07 生日是2008-08-08";
var reg1 = /[12]\d{3}-\d{2}-\d{2}/;
var reg2 = /[12]\d{3}-\d{2}-\d{2}/g;
var reg3 = /\d{10}/;

console.log(str.search(reg1));
console.log(str.indexOf("1998-11-12"));
// indexOf参数不建议写正则表达式  不管能否找到,都是返回-1
console.log(str.indexOf(reg1));
  1. 字符串对象.match(reg) :以数组的形式返回指定模式的字符串,可以返回所有匹配的结果 🧡

注意: match(reg)方法如果不是全局匹配,匹配出来的效果跟正则对象的exec方法一样

console.log(str.match(reg1));
console.log(reg1.exec(str));
console.log(str.match(reg2));
console.log(str.match(reg3));
  1. 字符串对象.replace(reg,"替换后的字符") :把指定模式的子串进行替换操作 🧡

    字符串对象.replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。

    • replace()基本用法,第二个参数是字符串
      1. 第一个参数: 被替换的字符串 或者 正则表达式
      2. 第二个参数: 替换为的字符串
      3. 返回值是一个替换完毕的新字符串
      console.log(str);
      console.log(str.replace(reg1, "****-**-**"));
      console.log(str.replace(reg2, "****-**-**"));
      console.log(str.replace(reg3, "****-**-**"));
      
    • replace()高级用法,第二个参数是function

      每次找到一次目标,就会调用一次function(){}匿名函数,把目标替换成函数中的return值;

      目标可以通过匿名函数中的arguments关键字查看

      var result5 = str.replace(reg2, function (targetValue, targetIndex, myStr) {
          // console.log("arguments=>", arguments);
          // console.log("targetValue=>", targetValue);
          // console.log("targetIndex=>", targetIndex);
          // console.log("myStr=>", myStr);
          // console.log("执行了匿名函数");
          var year = targetValue.substr(0, 4);
          var month = targetValue.substr(5, 2);
          var day = targetValue.substr(8, 2);
          return month + "月" + day + "日" + year + "年";
      });
      console.log("str=>", str);
      console.log("result5=>", result5);
      
  2. 字符串对象.split(reg) :以指定模式分割字符串,返回结果为数组

var str = "张三-李四-王五-赵六";
console.log(str.split("-"));
console.log(str.split(/-/));
var str = "张三3李四4王五5赵六";
console.log(str.split("3"));
console.log(str.split(/\d/));

反向引用(组匹配)

概念

在做匹配时,如果正则表达式中有小括号,则它会把小括号中所匹配到的文本都存储在一个特殊的地方, 以备以后使用。

这些存储在分组中的特殊值被称为反向引用。

如何使用反向引用的结果

  • RegExp构造函数的静态属性

    RegExp.$n 获取第n个()中匹配的内容,n是从1开始的自然数

    注意: "只有执行过正则相关方法",才可以使用RegExp.$n的方式得到反向引用的结果

    var str = "我的生日是1999-08-07, 今年我24岁";
    var reg = /(\d{4})-(\d{2})-(\d{2})/;
    if (reg.test(str)) {
        console.log(RegExp.$1);
        console.log(RegExp.$2);
        console.log(RegExp.$3);
    }
    
  • 直接在定义分组的正则表达式中包含反向引用。通过特殊的转义序列 \1,\2... 来调用。 🧡
    var str = "q7q7-a1a1-b2b2-abcd-qwer-4567-7890-f4f4-a7b8";
    // 目标,找到 a1a1, b2b2, f4f4 这些字符串   规律第一个字符跟第三个字符相同 第二个跟第四个字符相同
    var reg = /(\w)(\w)\1\2/g;
    console.log(str.match(reg));
    
  • 在匹配成功的数组中,通过下标去访问。
    var str = "我的生日是1999-08-07, 今年我24岁; 你的生日是2002-02-14, 今年就21岁";
    var reg = /(\d{4})-(\d{2})-(\d{2})/g;
    var res;
    while (res = reg.exec(str)) {
        console.log(res);
        console.log("年份=>", res[1]);
        console.log("月份=>", res[2]);
        console.log("日期=>", res[3]);
        console.log("");
    }
    

反向引用与replace结合使用

需求

目标字符串: "生日是:1991-10-11。生日是:1998-11-12。生日是:1990-1-10。生日是:1990/09/11。生日是:1980-01-13。";

把生日中的格式调整一下, 1990-01-31(年-月-日) ===> 31-01-1990(日-月-年)

var str = "生日是:1991-10-11。生日是:1998/11/12。生日是:1990-1-10。生日是:1990/09/11。生日是:1980-01-13。生日是: 2022-7/14";
var reg = /(\d{4})([-/])(\d{1,2})\2(\d{2})/g;
var newStr = str.replace(reg, function (result, year, $2, month, day) {
    console.log(arguments);
    return day + "-" + month + "-" + year;
});
console.log("newStr=>", newStr);

或者的用法 用|符号表示或者的意思

  • 语法:

    x|y 匹配x或y

  • 例如:

    z|food 能匹配"z"或"food"

    (z|f)ood 能匹配"zood"或"food"

var reg1 = /^(z|food)$/;
console.log(reg1.test("z")); // true
console.log(reg1.test("food")); // true
console.log(reg1.test("zood")); // false
console.log("");

var reg2 = /^(z|f)ood$/;
console.log(reg2.test("z")); // false
console.log(reg2.test("food")); // true
console.log(reg2.test("zood")); // true
console.log("");
console.log("");

var str = "ipad,iphone,imac,ipod,iamsorry,iwatch";
// 找到所有苹果产品
// var reg = /ipad|iphone|imac|ipod|iwatch/g;
var reg = /i(pad|phone|mac|pod|watch)/g;
console.log(str.match(reg));

转义字符

因为在正则表达式中 . + * ? 等是属于正则表达式的一部分,但是我们在匹配时,字符串中也需要匹配这些特殊字符,所以,我们必须使用 反斜杠\ 对某些特殊字符进行转义;

  • 需要转义的字符:

    点号.

    小括号()

    中括号[]

    左斜杠 /

    右斜杠\

    选择匹配符 |

    ...

var str = "英短原产于[英国],它是一个动物(猫)";
// 目标:  从str字符串中找到 "[英国]" 和 "(猫)"
var reg = /\[英国\]|\(猫\)/g;
console.log(str.match(reg));

取消反向引用(非捕获组)

介绍:(?:x)称为非捕获组(Non-capturing group),表示不返回该组匹配的内容,即匹配的结果中不计入这个括号。

概念:非捕获组的作用请考虑这样一个场景,假定需要匹配foo或者foofoo,正则表达式就应该写成/(foo){1, 2}/,但是这样会占用一个组匹配。这时,就可以使用非捕获组,将正则表达式改为/(?:foo){1, 2}/,它的作用与前一个正则是一样的,但是不会单独输出括号内部的内容。

引用分组会需要额外的空间存储。当我们只是需要分组(把小原子组成大原子)而不需要引用时,就可以手动取消这个引用。

原子就是单个字符 比如 /abc{2}/ => /(abc){2}/

做法是: 在()里面的前面加一个 ?: 即可

var str = "abcabcabcc123456abcd";
// var reg = /abc{2}/g;
var reg = /(?:abc){2}/g;
var res;
while (res = reg.exec(str)) {
    console.log(res);
}

贪婪匹配与惰性匹配

我们会发现以上代码运行结果中,默认优先配到 13 位,在对后面的进行匹配;为什么不是优先匹配 5 位后,在对后面的进行匹配呢?

因为在正则表达式中,默认情况下,能匹配多的就不匹配少的,我们把这种匹配模式就称之为 贪婪匹配,也叫做 贪婪模式所有的正则表达式,默认情况下采用的都是贪婪匹配原则。

如果在量词符的后面添加一个问号? 那我们的贪婪匹配原则就会转化为非贪婪匹配原则,优先匹配少的,也叫惰性匹配;

var str = "我的QQ20869921366666666666,nsd你的是6726832618吗?";
// var reg = /[1-9]\d{4,12}/g;
var reg = /[1-9]\d{4,12}?/g;
console.log(str.match(reg));

函数的防抖,节流

  • 介绍

    函数防抖中的抖动就是执行的意思,而一般的抖动都是持续的、多次的、频繁的执行某一段代码。函数防抖就是某函数持续多次执行,我们希望让它冷静下来再执行。也就是当持续触发事件的时候,函数是完全不执行的,等最后一次触发结束的一段时间之后,再去执行。在前端开发中经常会遇到这种频繁的事件触发,比如:
    window 的 onresize、onscroll
    onmousemove
    onkeyup、onkeydown 、oninput等等......

    window.onresize = function () {
        console.log("浏览器可视区域发生改变");
    }
    window.onscroll = function () {
        console.log("页面滚动中..");
    }
    
  • 为什么需要防抖,节流

    这种频繁的事件触发不做限制的话,有可能一秒之内执行几十次、几百次,如果在这些函数内部执行了其他函数,尤其是执行了操作 DOM 的函数(浏

    览器操作 DOM 是很耗费性能的),那不仅会浪费计算机资源,还会降低程序运行速度,甚至造成浏览器卡死、崩溃。这种问题显然是致命的。

    除此之外,短时间内重复的 ajax 调用不仅会造成数据关系的混乱,还会造成网络拥塞,增加服务器压力,显然这个问题也是需要解决的。

    函数防抖和节流,都是控制事件触发频率的方法。

  • 区别

    函数防抖, 事件尽管触发, 需要停止触发事件以后 ,再等上一个指定时间以后, 才会执行指定代码

    函数节流, 指定时间内,只能触发一次事件

  • 防抖,节流体验

    Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。

    lodash中文文档 https://www.lodashjs.com/

  • bootcdn网址(可以通过这个网站在线引入第三方库) https://www.bootcdn.cn/

函数的防抖

  • 概念

    其中防抖的原理就是:事件尽管触发,但是在事件触发 n 秒后才执行,如果你在一个事件触发的 n

    秒内又触发相同事件,那我就以新的事件 n 秒后才执行,舍弃掉上一次事件触发执行操作。简单地说就是频繁的触发事件完毕后的 n 秒内不再触发同

    一事件,函数才会执行。

  • 生活中例子

    生活中例子1: 一群人排队上公交车, 公交车司机, 直到最后一个人上了车以后,再关车门

    生活中例子2: 坐电梯的时候,如果电梯检测到有人进来(触发事件),就会多等待 10 秒,此时如果又有人进来(10秒之内重复触发事件),那么电梯就会再多等待 10 秒。在上述例子中,电梯在检测到有人进入 10 秒钟之后,才会关闭电梯门开始运行,因此,“函数防抖”的关键在于,在 一个事件 发生 一定时间 之后,才执行 特定动作。

    生活中例子3: 王者荣耀或者英雄游戏中, 多次按回城按钮,只有按了以后,停下来一段时间才会回城

  • 函数防抖的使用场景

    函数防抖一般用在什么情况之下呢?一般用在,连续的事件只需触发一次回调的场合。具体有:

    1. 搜索框搜索输入。只需用户最后一次输入完,再发送请求;
    2. 用户名、手机号、邮箱输入验证;
    3. 浏览器窗口大小改变后,只需窗口调整完后,再执行 resize 事件中的代码,防止重复渲染。
    4. 懒加载、滚动加载、加载更多或监听滚动条位置;
    5. 百度搜索框,搜索联想功能;
    6. 防止高频点击提交,防止表单重复提交;
  • 函数节流的实现

function mydebounce(func, wait) {
    // 延时器标识符
    var timeoutId = null;
    // 使用一个变量,保存this上下文的对象
    var context = null;
    // 使用一个变量,保存实参列表
    var args = null;

    // 返回一个新函数
    return function () {
        // 把正确的this对象赋值给context变量
        context = this;

        // 把正确的arguments对象赋值给args变量
        args = arguments;
        console.log(arguments);

        // 清除上一个延时器
        window.clearTimeout(timeoutId);

        // 开启一个延时器
        timeoutId = window.setTimeout(function () {
            // 改变func函数内部this指向,并且传递参数
            func.apply(context, args);
        }, wait);
    }
}

// 获取dom对象
var container = document.getElementById("container");
// 定义一个变量,保存次数
var count = 0;
// 封装一个函数
function fn(e) {
    // console.log("fn函数被调用了");
    console.log("this对象=>", this);
    console.log("e事件对象=>", e);
    console.log("");
    // // 自加1
    count++;
    // // 设置container标签内容
    this.innerText = "触发了" + count + "次事件,当前鼠标在盒子内坐标为" + e.offsetX + "," + e.offsetY;
}
// 给container对象绑定鼠标移动事件
// 使用自己封装的防抖动函数
container.onmousemove = mydebounce(fn, 400);


var objInput = document.querySelector("input");
objInput.onkeyup = mydebounce(function (e) {
    console.log("this对象=>", this);
    console.log("e事件对象=>", e);
    console.log("e.key=>", e.key);
    console.log(this.value);
}, 400);

函数的节流

  • 概念

    规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

  • 生活中例子

    举个栗子,坐火车或地铁或高铁,过安检的时候,在一定时间(例如10秒)内,只允许一个乘客通过安检入口,以配合安检人员完成安检工作。上例中,每10秒内,仅允许一位乘客通过,分析可知,“函数节流”的要点在于,在 一定时间 之内,限制 一个动作 只 执行一次 。

    再举个栗子, 王者荣耀某个英雄释放技能以后,该技能进入冷却,等该技能冷却结束以后才能再次释放技能

  • 函数节流的使用场景

    1. DOM元素的拖拽功能实现
    2. 射击游戏
    3. 计算鼠标移动的距离
    4. 监听scroll滚动事件
  • 函数节流的实现

    在函数的节流中首次触发是否执行以及结束后是否执行,不同需求,实现的方式也有所不同。目前实现函数节流有两种主流方式:

    1. 使用时间戳(从1970年1月1日0时0分0秒到现在的毫秒数)
    2. 设置延时器(setTimeout)。

    我们用 leading 代表首次是否执行,trailing 代表结束后是否再执行一次。

  • 使用时间戳完成节流函数的第一版本

    原理:当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期(时间间隔),就执行函数,并更新时间戳为当前的时间戳,如果小于,就不执行。
    目标:第一次触发,最后不会被调用触发函数

function mythrottle1(func, wait) {
    // 之前的时间戳
    var old = 0;
    // 定义一个变量保存this对象
    var context = null;
    // 定义一个变量保存实参列表
    var args = null;

    // 返回一个新函数
    return function () {
        // 保存this对象
        context = this;
        // 保存arguments对象
        args = arguments;

        // 获取当前最近的时间戳
        var now = new Date().getTime();
        // 如果大于设置的时间周期(时间间隔),就执行函数,如果小于,就不执行。
        if (now - old > wait) {
            // 修改函数内部this指向,并传递参数
            func.apply(context, args);
            // 并更新时间戳为当前的时间戳
            old = now;
        }
    }
}
  • 使用延时器完成节流函数的第二版本

    原理:当触发事件的时候,设置一个延时器,再触发事件的时候,如果延时器存在,就不执行,直到延时器执行完毕,然后执行函数,清空延时器,再设置下个延时器。
    目标:实现第二个版本 第一次不触发,最后一次触发

function mythrottle2(func, wait) {
    // 定义一个变量保存this对象
    var context = null;
    // 定义一个变量保存实参列表
    var args = null;
    // 延时器标识符
    var timeoutId = null;

    // 返回一个新函数
    return function () {
        // 保存this对象
        context = this;
        // 保存arguments对象
        args = arguments;

        // 判断延时器是否存在
        if (timeoutId === null) { // 如果不延时器存在
            // 开启延时器
            timeoutId = window.setTimeout(function () {
                // 改变函数内部this指向以及传递参数
                func.apply(context, args);
                // 执行完毕函数以后, 重新给timeoutId标识符赋值为null
                timeoutId = null;
            }, wait);
        }
    }
}
  • 使用时间戳和延时器双剑合璧完成节流函数的第三版本

    目标:使用时间戳和延时器双剑合璧完成节流函数的第三个版本 第一次触发,最后一次也触发

function mythrottle3(func, wait) {
    // 之前的时间戳
    var old = 0;
    // 定义一个变量保存this对象
    var context = null;
    // 定义一个变量保存实参列表
    var args = null;
    // 延时器标识符
    var timeoutId = null;

    // 返回一个新函数
    return function () {
        // 保存this对象
        context = this;
        // 保存arguments对象
        args = arguments;

        // 获取当前最近的时间戳
        var now = new Date().getTime();
        // 如果大于设置的时间周期(时间间隔),就执行函数,如果小于,就不执行。
        if (now - old > wait) {
            // 修改函数内部this指向,并传递参数
            func.apply(context, args)
            // 并更新时间戳为当前的时间戳
            old = now;

            // 如果此时有延时器存在,则清除延时器,并且设置延时器变量timeout值恢复为null
            if (timeoutId) {
                clearTimeout(timeoutId);
                timeoutId = null;
            }
        }


        // 判断延时器是否存在
        if (timeoutId === null) { // 如果不延时器存在
            // 开启延时器
            timeoutId = window.setTimeout(function () {
                // 改变函数内部this指向以及传递参数
                func.apply(context, args);
                // 执行完毕函数以后, 重新给timeoutId标识符赋值为null
                timeoutId = null;

                // 更新时间戳
                // 重新获取现在最新的时间戳
                now = new Date().getTime();
                // 把now的值赋给old, 更新old的值
                old = now;
            }, wait);
        }

    }
}

function mythrottle(func, wait, options) {
    // 判断options是否为undefined
    if (options === undefined || (options.leading && options.trailing)) {
        return mythrottle3(func, wait);
    }

    if (options.leading === true && options.trailing === false) {
        return mythrottle1(func, wait);
    }

    if (options.leading === false && options.trailing === true) {
        return mythrottle2(func, wait);
    }
}

Ajax

Ajax概述

Ajax:标准读音 [ˈeɪˌdʒæks] ,中文音译:阿贾克斯

它是浏览器提供的一套方法,可以实现页面无刷新更新数据,提高用户浏览网站应用的体验。

Ajax我们需要学习的是如何拿到服务器端地址的数据,并前端进行渲染

参考文档: JS Ajax请求(简明教程) (biancheng.net)

ajax对象.readyState ajax的状态码

状态 描述
0 UNSENT 代理被创建,但尚未调用 open() 方法。
1 OPENED open() 方法已经被调用。
2 HEADERS_RECEIVED send() 方法已经被调用,并且头部和状态已经可获得。
3 LOADING 下载中;responseText 属性已经包含部分数据。
4 DONE 下载操作已完成。

参考文档: https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/readyState

ajax对象.status 请求的响应状态

常用状态码 200请求成功 404请求地址有问题,找不到资源 403拒绝访问 500服务器错误

  1. 网络畅通,服务器端能接收到请求,服务器端返回的结果不是预期结果。
    可以判断服务器端返回的状态码,分别进行处理。

  2. 网络畅通,服务器端没有接收到请求,返回 404 状态码。
    检查请求地址是否错误。

  3. 网络畅通,服务器端能接收到请求,服务器端返回 500 状态码。
    服务器端错误,找后端程序员进行沟通。

  4. 服务器端成功处理了请求,服务器端返回 200 状态码。
    在 onreadystatechange 事件中,我们规定当服务器响应已做好被处理的准备时所执行的任务。
    当 readyState 等于 4 且状态为 200 时,表示响应已就绪

  5. Bad Request 客户端请求的语法错误,服务器无法理解 返回400状态码

  6. 网络中断,请求无法发送到服务器端。
    会触发xhr对象下面的onerror事件,在onerror事件处理函数中对错误进行处理。

参考文档: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status

Ajax的实现步骤

XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest 在 AJAX 编程中被大量使用。

console.log("XMLHttpRequest=>", XMLHttpRequest);

第一步: 创建ajax对象, 通过XMLHttpRequest()构造函数

var xhr = new XMLHttpRequest();

第二步: 使用ajax对象初始化一个请求 通过XMLHttpRequest对象.open( method要使用的 HTTP 方法, url请求地址, async表示是否异步执行操作默认true表示异步操作 )
method参数取值主要是GET和POST 这两个请求方式

xhr.open("GET", "https://v2.alapi.cn/api/dog?token=lJLxHK2NrnPVri457AaK");

第三步: 发送请求 通过 XMLHttpRequest对象.send()
如果是GET请求方式, send()方法中不需要写参数;
如果是POST请求方式, send()方法中可能需要写请求参数;

xhr.send();

第四步: 处理响应结果
当 ajax对象的readyState 属性发生变化时,就会触发readystatechange事件

xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
        // xhr.responseText表示ajax对象获取服务器返回的文本值
        console.log("xhr.responseText=>", xhr.responseText);
        // 把json字符串转成json对象  JSON.parse(json字符串)
        var res = JSON.parse(xhr.responseText)
        console.log("res=>", res);
        console.log("res.data.content=>", res.data.content);

        // 设置p段落标签内容
        objP.innerText = res.data.content;
    }
}

注意: 默认ajax请求的时候,如果直接使用file协议访问会报错 需要使用http协议或者https协议访问 vscode可以安装live server插件

但是, 访问远程某个网址(api接口),如果api接口开启了CORS(跨域资源共享), 我们就可以直接使用file:///协议浏览页面,测试效果

alapi网站:https://www.alapi.cn/

参考文档: https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest

get请求和post请求的区别

  • 我们大概要记住以下几点:

    1. 浏览器在回退时,get 不会重新请求,但是post会重新请求。【重要】
    2. get请求会被浏览器主动缓存,而post不会。【重要】
    3. get请求的参数,会报保留在浏览器的历史记录里,而post不会。做业务时要注意。为了防止CSRF攻击,很多公司把get统一改成了post。
    4. get请求在url中传递的参数有大小限制,基本是2kb`,不同的浏览器略有不同。而 post 没有注意。
    5. get的参数是直接暴露在url上的,相对不安全。而post是放在请求体中的。
  • GET与POST的区别?

    1. get是从服务器上获取数据,post是向服务器推送数据。
    2. get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。post是通过HTTP post机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。
    3. 对于get方式,服务器端用Request.QueryString获取变量的值,对于post方式,服务器端用Request.Form获取提交的数据。
    4. get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。
    5. get执行效率却比post方法好。

面试题: https://interview.html5.wiki/compound.html#_5-get-和-post-的区别

URL介绍

统一资源定位符(Uniform Resource Locaotr,URL) 是互联网上标准资源的地址. 互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它.

url可以简单理解为是某个网址

URL的一般语法格式: protocol://host[:port]/path/[?query]#fragment
比如: https://www.mi.com/shop/buy/detail?product_id=10000203&selected=10000203#bottom

一个URL由以下几部分组成:

组成 说明
protocol 通信协议,常见的有http://、https://、ftp://等
host 主机(域名),比如: www.mi.com
port 端口号,可选,省略时会使用默认端口,比如http协议默认端口为80;https协议默认端口为443
path 路径,由零个或者多个"/"符号隔开的字符串,一般用来表示主机上的一个目录或者文件地址,比如"/shop/buy/detail"
query 参数,以键值对的形式通过&符号分隔开,GET请求提交数据就是这种方式
fragment 片段,#后面的内容,常见于链接的锚点

get 请求方式传参

get方式传递参数是通过url拼接字符串的形式

语法如下

url?键名1=键值1&键值2=键值2...

// 获取文本框输入的关键字
var keyword = objInput.value;
// 发送ajax请求
// 创建ajax对象
var xhr = new XMLHttpRequest();
// 初始化请求
xhr.open("GET", "https://v2.alapi.cn/api/idiom?token=lJLxHK2NrnPVri457AaK&word=" + keyword);
// 发送请求
xhr.send();
// 监听ajax状态码的变化
xhr.onreadystatechange = function () {
    // ajax状态为4 并且 http响应状态码为200
    if (xhr.readyState === 4 && xhr.status === 200) {
        // 获取服务器响应的文本内容数据
        // console.log(xhr.responseText);

        // 把json字符串转成json对象
        var res = JSON.parse(xhr.responseText);
        console.log(res);
        // 获取目标数据
        var data = res.data;
        // 渲染数据
        if (data !== null) {
            objUl.innerHTML = data.map(function (item) {
                return "<li><h2>" + item.word + "</h2><p>" + item.explanation + "</p></li>";
            }).join("");
        }
    }
}

POST 请求方式传参

POST 请求方式传参方式一

传统表单传值方式 常用 数据通过send方法发送,并且在发送请求之前, 需要设置请求头

post传统表单传值方式传参需要先设置请求头 告诉服务器端当前请求参数的格式是为传统表单传值方式

xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

传统表单传值方式 "name=zhangsan&age=20&sex=男"

传统表单参数字符串 格式 键名1=键值1&键名2=键值2...

方式一: 直接字符串拼接

var paramStr = "token=lJLxHK2NrnPVri457AaK&keyword=" + keywordVal + "&page=" + pageSizeVal + "&type=" + biaoqingTypeVal;

方式二: 创建一个js对象,保存要传递的参数

var paramData = {
    token: token,
    keyword: keywordVal,
    page: pageSizeVal,
    type: biaoqingTypeVal
}
// console.log("paramData=>", paramData);

// 定义一个空字符串
var paramStr2 = "";
// for...in遍历paramData对象
for (var attr in paramData) {
    paramStr2 = paramStr2 + attr + "=" + paramData[attr] + "&";
}
paramStr2 = paramStr2.slice(0, -1);

POST传递参数,还需要在请求发送之前,设置请求头

xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

POST传递参数是通过send()方法 在send()方法里面放传统表单参数字符串

//xhr.send(paramStr);
xhr.send(paramStr2);

POST 请求方式传参方式二

传递json格式字符串 不常用 数据通过send方法发送,并且需要设置请求头

post传json格式字符串参数需要先设置请求头 在请求头中指定 Content-Type 属性的值是 application/json,告诉服务器端当前请求参数的格式是 json。

xhr.setRequestHeader("Content-Type", "application/json");

var paramData = {}; // 空对象
// 添加属性
paramData.token = token;
paramData.page = pageSizeVal;
paramData.keyword = keywordVal;
paramData.type = biaoqingTypeVal;

// 通过 JSON.stringify( json对象 )  就可以把json对象转换成JSON字符串

// POST传递参数,需要发送请求之前设置对应的请求头
xhr.setRequestHeader("Content-Type", "application/json");

// 发送请求
xhr.send(JSON.stringify(paramData));

请求报文

在 HTTP 请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,这些数据和信息要遵守规定好的格式。

报文分为报文头和报文体

报文头中存储的是一些键值对信息,可以理解为客户端向服务器说的一些话,比如

设置请求头

xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

报文体主要存储一些内容,比如POST请求的参数,就是存储在报文体中

报文头和报文体在请求的过程中, 整体被发送到服务器当中

参考文章: https://www.cnblogs.com/myseries/p/11239662.html

跨域问题

不是所有的接口地址都是可以直接请求的 有些接口只允许特定的ip或者域名访问 也就是有所限制

window.open( url ) 该方法可以在新窗口打开指定url的网页

注意: 以上接口,用浏览器直接访问没有问题, 但是使用ajax请求的时候, 即使我们浏览页面的时候,使用了live server浏览器,还是会出现如下错误:

要解释以及解决以上问题,我们需要先学习同源政策

同源政策

  • Ajax请求限制

    Ajax 默认只能向自己的服务器发送请求。

    比如现在有一个A网站、有一个B网站,A网站中的 HTML 文件只能向A网站服务器中发送 Ajax 请求,B网站中的 HTML 文件只能向 B 网站中发送 Ajax 请求,但是 A 网站是不能向 B 网站发送 Ajax请求的,同理,B 网站也不能向 A 网站发送 Ajax请求。

  • 什么是同源

    如果两个页面拥有相同的协议、相同的域名和相同的端口,那么这两个页面就属于同一个源,同一个源就不会有跨域问题, 但是协议、域名和端口其中只要有一个不相同,就是不同源,那就是跨域。

    http://协议的默认端口号是80

    https://协议的默认端口号是443

例:

  • 同源政策的目的

    同源政策是为了保证用户信息的安全,防止恶意的网站窃取数据。最初的同源政策是指 A 网站在客户端设置的 Cookie,B网站是不能访问的。

    随着互联网的发展,同源政策也越来越严格,在不同源的情况下,其中有一项规定就是无法向非同源地址发送Ajax 请求,如果请求,浏览器就会报错。

最常见的六种跨域解决方案: https://blog.csdn.net/m0_37873510/article/details/126558023

JSONP方式解决跨域

  • jsonp 是 json with padding 的缩写,它不属于 Ajax 请求,但它可以模拟 Ajax 请求。 JSONP只能模拟"get请求"

  • JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。"解决跨域问题的一种技术,一种方案"

  • JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的"数据交换格式"。

  • jsonp的原理就是利用了script标签不受浏览器同源策略的限制,然后和后端一起配合来解决跨域问题的。

  • 注意: jsonp可以解决部分接口跨域问题, 不是所有的接口都可以使用jsonp技术解决, 取决后端

  • JSONP的实现步骤

    1. 将不同源的服务器端请求地址写在 script 标签的 src 属性中, 并传递callback参数与函数名

    注意:callback参数名称不是固定为callback,只是一般情况下为callback这个名字

    1. 服务器端响应jsonp需要的数据

    注意:此步骤需要服务器端完成

    1. 在客户端全局作用域下定义函数 fn, 并在 fn 函数内部对服务器端返回的数据进行处理

    注意:fn函数的定义需要放在全局作用域下

<script>
    function fn(data) {
        console.log("我是fn函数");
        console.log("arguments=>", arguments);
        // data就是服务器响应的jsonp数据
        console.log("data=>", data);
    }
</script>
<script src="https://api.asilu.com/phone?phone=13123456789&callback=fn"></script>

封装jsonp函数

function jsonp(options) {
    // 判断options中的callbackName属性值是否为undefined
    if (options.callbackName === undefined) {
        options.callbackName = "callback";
    }

    // 创建script标签
    var newScript = document.createElement("script");

    // 参数字符串
    var paramStr = "";
    // for..in遍历options.data
    for (var attr in options.data) {
        // 拼接字符串
        paramStr = paramStr + "&" + attr + "=" + options.data[attr];
    }

    // 随机函数名
    var randomFunName = "myjsonp" + Math.random().toString(16).substr(2);

    // 所有全局作用域下定义的变量会成为window对象的属性
    // 所有全局作用域下定义的函数会成为window对象的方法
    window[randomFunName] = options.success;

    // 设置src属性
    newScript.src = options.url + "?" + options.callbackName + "=" + randomFunName + paramStr;

    // 添加到body中
    document.body.appendChild(newScript);

    // 当script标签加载完毕以后,删除这个script标签
    newScript.onload = function () {
        this.remove();
    }
}


  1. a-z0-9_- ↩︎

  2. a-z0-9_- ↩︎

  3. a-z\d ↩︎

  4. \u2E80-\u9FFF ↩︎

本文作者:make逸努

本文链接:https://www.cnblogs.com/makeinu/p/18691426

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   make逸努  阅读(25)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
🔑