前端技术

JavaScript 基本语法手册

概述

JavaScript 简称 JS,是运行在客户端的脚本语言,浏览器通过 JavaScript 引擎来执行 JS 代码,JS 引擎执行代码时逐行解释每一句源码然后由计算机去执行,即读取一行就执行一行。

JavaScript 由三部分组成,ECMAScript ( JavaScript 语法 ), DOM (页面文档对象模型) , BOM ( 浏览器对象模型 )。

  • ECMAScript ( JavaScript ): 规定了 JS 的编程语法和基础核心知识,是所有浏览器厂商共同遵守的一套 JS 语法工业标准;
  • DOM – 文档对象模型 ( Document Object Model ): 是 W3C 组织推荐的处理可扩展标记语言的标准编程接口。通过 DOM 提供的接口可以对页面上的各种元素进行操作 (大小、位置、颜色等);
  • BOM – 浏览器对象模型 ( Browser Object Model ): 它提供了独立于内容的、可以与浏览器窗口进行互动的对象结构。通过 BOM 可以操作浏览器窗口,比如弹出框、控制浏览器跳转、获取分辨率等。

如何引入 JavaScript

JS 代码可以写在页面的任何位置,有三种不同的写法,为符合标准,建议将所有 JS 都写在外部文件。

// 行内插入
<input type="button" onclick="alert('a')">

// 页面内嵌
<script> ... </script>

// 引入外部文件
<script src="filename.js"></script>
  • 所有 JS 代码建议都使用单引号书写;
  • 引用外部 JS 文件的 <script></script>, 标签内不可写代码。

注释

// 单行注释 , 快捷键 "ctrl + /" 或 "cmd + /"

/* 多行注释
 * 快捷键 shift + alt + a 
 * 快捷键是 vscode 中的 */

js的书写规范

标识符命名规范与语法规范

  • 变量、函数的名称必须要有意义
  • 变量名称一般使用名次
  • 函数名称一般使用动词

变量的命名规则

  • 必须以英文字母(包含大小写)、“_” 或 “$” 开头;
  • 变量名可包含英文字母(包含大小写)、“_” 、 “$” 与数字, 如 usrAge, num01, _name, $age… ;
  • 严格区分大小写,如 var = app; 和 var = App; 是两个变量;
  • 不可用系统中有特殊语法含义的关键字或保留字作为变量名 (见下表);
  • 命名需有一定的含义,以方便查阅代码可更好的理解;
  • 遵守驼峰命名法,首字母小写,后面单词的首字母大写,如 myFirstName。

关键字:

break case catch continue default delete do else finally for function if in instanceof new return switch this throw try typeof var void while with

保留字:

abstract boolean byte char class const debugger double enum export extends final float goto implements import int interface long name native package private protected public short static super synchronized throws transient volatile

操作符规范

  • 操作符的左右两侧各保留一个空格

常用的输入输出语句

alert(msg) // 浏览器弹出框
console.log(msg) // 浏览器控制台打印输出信息
prompt(info) //浏览器弹出输入框,用户可以输入

变量

变量即是用于存放数据的容器,我们可以通过变量名获取数据或修改数据,变量的数据存储于内存空间。

变量的使用包含了两个步骤,1、声明变量。2、给变量赋值,声明一个变量并给他赋值,我们称之为变量的初始化。

变量声明

// 变量声明
var a;
// 变量赋值
a = 100;
// 简化写法
var a = 100;

更新变量

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

// 变量重新赋值,替换掉之前的值
var a = 100;
a = 200;

同时声明多个变量

只需写一个 var,多个变量之间使用半角逗号 “,” 隔开,结尾使用 “;”结束,为格式标准化,每个变量最好换一行进行声明。

// 声明多个变量
var a = 100;
var b = 200;
var c = 300;
// 简化写法 ( 单一var模式 )
var a = 100,
    b = 200,
    c = 300;

声明变量的特殊情况

// 只声明不赋值
var a;
console.log(a); // undefined
// 不声明也不赋值
console.log(a); // 报错
// 不声明直接赋值 (不建议)
a = 100;
console.log(a); // 100

let 声明变量 (ES6)

let声明变量的规则与书写格式同 var 大致一致,但有以下几点是和 var 声明变量不同的地方

let 声明变量不可重复声明
let a = 1;
let a = b;
// 报错,使用 var 声明变量不存在此问题
块级作用域

let 声明的变量是块级作用域,作用域块是 ES6 新的概念,只能在代码块中有效,块级作用域简单的说就是花括号里面的作用域,如 {} 或 if / else / while / for 等;

{
  let a = 1;
}
console.log(a);
// 报错,let声明的变量只在声明的块中起作用。
不存在变量提升

不存在变量提升,在声明之前执行代码会报错

console.log(a);
let a = 1;
// 报错,let声明的变量不会提升
不影响作用域链

这一点和 var 一致

{
  let a = a;
  function fn() {
    console.log(a);
  }
}
fn();

变量的解构赋值 (ES6) (重学)

Bilibili 视频教程:https://www.bilibili.com/video/BV1bS4y1b7NV?p=3

ES6 允许按照一定模式从数据或对象中提取值,对变量进行赋值,这被称为解构赋值。

// 从数组中提取值
const PJ = [3,6,9];
let [a,b,c] = PJ; // 声明了三个变量并将数组中的值分别赋给了三个变量
// 从变量中提取值
const PJ = {
  name: 'PUJI',
  age: 36,
  work: function() {
    console.log(name + age + 'years old');
  }
}
let {name, age, work} = PJ; // 声明了三个变量并将对象中的值分别赋给了三个变量
// 利用数组结构交换变量的数值
let a = 1;
let b = 2;
[a, b] = [b, a]; // a = 2, b = 1

常量 (ES6)

值不可修改的量称为常量。

常量声明

使用 const 声明常量,常量名使用全大写字母,用于区分变量

const PI = 3.1415926;

常量的使用规则

  • 常量声明是必须赋值,并且声明后不可修改;
  • 一般使用大写作为常量名;
  • 常量与let声明的变量,也是块级作用域;
  • 对于数组和对象的元素修改,不算做对常量的修改;
  • 数组或对象使用常量来声明更为合适。

数据类型

JavaScript 属于弱类型或者说动态语言,这意味着不用提前声明变量的数据类型,在程序运行过程中,类型会被自动确定。

JavaScript 拥有动态类型,因此变量的数据类型是可以变化的。

var a = 6; // a 为数字型
var a = 'Puji'; // a 为字符串型

简单数据类型与复杂数据类型

目标

  • 能够说出简单数据类型的内存分配
  • 能够说出复杂数据类型的内存分配
  • 能够说出简单类型如何传参
  • 能够说出复杂类型如何传参

简单数据类型又叫基本数据类型或者值类型,复杂数据类型又叫引用类型。

  • 值类型:简单数据类型/基本数据类型,在存储时变量中存储的是值本身,因此叫做值类型 String / Number / Boolean / undefined / null (null 返回的是一个空的对象)
  • 引用类型:复杂数据类型,在存储时变量中存储的仅仅是地址(引用),因此叫做引用数据类型,通过 new 关键词创建的对象(自定义对象、内置对象)如 Object / Array / Date 等
简单数据类型
  • 数字型 Number // 默认值 0
  • 字符串型 String // 默认值 “”
  • 布尔型 Boolean // 默认值 false
  • 未定义 Undefined // 默认值 undefined
  • 空值 Null // 默认值 null
  • Symbol
复杂数据类型
  • 数组 array
  • 对象 object
  • 函数 function

堆和栈

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

堆:存储复杂类型,一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。

简单数据类型都放在栈中,复杂数据类型首先在栈里存放地址(十六进制)然后再指向堆里的数据

简单数据类型

数字型 Number

数字型包含整数或浮点数,0与负数。

var num = 10;
var PI = 3.14;

数字型进制通常包括 二进制,八进制,十进制与十六进制,在js中八进制前价0,十六进制前价0x

// 1、八进制数字序列范围 (0~7)
var num1 = 07;  //对应十进制 7
var num2 = 019;  //对应十进制 19
var num3 = 08;  //对应十进制 0
// 2、十六进制数字序列范围 (0~9以及 a~f)
var num = 0xA
非数字 NaN

运算结果不是数字时,会返回 NaN,非数字采用 isNaN() 函数进行判断,如果结果是数字,返回的是 false,如果不是数字返回 true

var a = 'Hello' - 100; // NaN
isNaN(12) // false
isNaN('Puji') // true

字符串型 String

字符串型可以是引号中的任意文本,可以使用单引号或双引号。可以使用单引号与双引号进行嵌套

var strMsg1 = 'Puji Design'; // 使用单引号表示字符串
var strMsg2 = "Puji Design"; // 使用双引号表示字符串
var strMsg3 = Puji Design; // 报错,会被识别为 js代码,但js没有这样的语法
var strMsg1 = '"Puji" Design';
var strMsg2 = "'Puji' Design";

字符串不可变

变量里的字符串值是不可变的,虽然看上去可以改变内容,但其实是地址变了,内存中会心开辟一个内存空间存储新的字符串的值,原来的值还是存在,因此在开发过程中,尽量减少字符串的修改与拼接。

// 一直在生成新的内存空间
var str = '';
for (var i = 1; i<= 100; i++) {
  str += i;
}

字符串方法

trim()

从一个字符串的两端删除空白字符,不影响原先的字符串本身,将返回一个新的字符串。

var str = '   PUJI Design ';
var str1 = str.trim();
console.log(str1); // PUJI Design
// 示例
var input = document.querySelector('input');
var btn = document.querySelector('button');
btn.addEventListener('click', function() {
  var inputValue = input.value.trim(); // 如果输入框内输入的空格或文字首尾两端有空格都进行清除
  if( inputValue === '') {
    alert('请输入内容');
  } else {
    alert('您的内容已成功提交');
  }
})

转义符

类似 html 里的特殊字符,字符串也有特殊字符,也叫做转义符,转义符必须写到引号内,转义符都是用 \ 开头的,常用的转义符如下:

\n   换行符,n是newline的意思
\\   斜杠 \
\'   单引号
\"   双引号
\t   tab 缩进
\b   空格,b是blank的意思

字符串类型的拼接

字符串类型的与其他类型进行相拼接,结果都以字符串类型输出。

// 获取字符串的长度
str.length;
// 字符串的拼接
'PUJI' + ' Design'; // PUJI Design
'PUJI' + 9; //PUJI9
'PUJI' + true; //PUJItrue

模版字符串 (ES6)

由反引号 “ 包裹的字符串视为模版字符串

let str = `PUJI Design`;
// 示例1,允许换行
let str = `<ul>             <li>Digital</li>
             <li>Branding</li>
             <li>Coding</li>
           </ul>`;
// 示例2,拼接变量
let name = 'PUJI';
let out = `${name} Design`;
  • 在内容中可以直接出现换行符
  • 允许使用变量直接在字符串里进行拼接

布尔型 Boolean

布尔型包含两个值 true 与 false,布尔型同样可以与数字相加

var flag1 = true; // true 1
var flag2 = false; // false 0
flag1 + 1; // 2

未定义 Undefined

声明变量但没有赋值的,会有一个默认值 undefined,如果与数字相加,最后的结果是NaN,如果与字符串相连,结果如下:

// 以下两种形式都属于 undefined
var a;
var a = undefined;
//相加或相连
var a;
12 + a; // NaN
'PUJI' + a; // PUJIundefined

空值 Null

与 undefined 不同,空值如果与数字相加,空值会被视为0,如果与字符串相连,结果如下:

var a = null;
var a = null;
1 + a; // 1
'PUJI' + a; // PUJInull

Symbol (ES6) (需重学)

ES6 引入了一种新的原始值数据类型 Symbol,表示独一无二的值。是一种类似于字符串的数据类型。它的使用场景是给对象添加属性和方法,表示独一无二。

Symbol特点
  • Symbol 的值是唯一的,用来解决命名冲突的问题
  • Symbol 的值不能与其他数据类型进行换算
  • Symbol 的值不能进行字符串的拼接
  • Symbol 定义的对象属性不能使用 for…in 循环遍历,但是可以使用 Reflect.ownKeys 来获取对象的所有键名
let s1 = Symbol();
let s1 = Symbol('PUJI Design');
let s2 = Symbol('PUJI Design');
console.log(s1 === s2); // false
let s1 = Symbol.for('PUJI Design');
let s2 = Symbol.for('PUJI Design');
console.log(s1 === s2); // true
let a = {
  [Symbol('name'): 'PUJI';
}
Symbol 内置值
Symbol.hasInstance

数据类型的操作

typeof 获取变量的数据类型

var a = 10;
typeof a; // number
var b = 'PUJI';
typeof b; // string
var c = true;
typeof c; // boolean
var d;
typeof d; // undefined
var e = null;
typeof e; // object

数据类型的转换

即把一种数据类型转换为另一种数据类型,常用的3种转换方式

转换为字符串型
var num = 1;
// 方法1
num.toString();
// 方法2
String(num);
// 方法3(隐式转换)
num + '';
转换为数字型
// 将 string 类型转换成整数数值型 (如果有小数点,将在小数点前取整)
parseInt('9'); // 9
// 将 string 类型转换成浮点数值型
parseFloat('6.9'); // 6.9
// 将 string 类型转换成数值型
Number('9'); // 9
// 利用算术运算隐式转换为数值型(隐式转换)
// 使用的符号包括 "-" "*" "/"
'12' - 0; // 12
'9' * '3'; // 27

parseInt() 与 parseFloat() 的使用方法基本一致,只是一个取整一个取小数的区别

parseInt('9');  // 9
parseInt('6.9');  // 6
parseInt('9px');  // 9
parseInt('rem9');  // NaN
parseFloat('6.9');  //6.9
转换为布尔型

Boolean()函数进行转换,代表空、否定的值都会被转换为 false,如0,NaN,null,undefined。其余都会被转换为 true

Boolean(''); // false
Boolean(0); // false
Boolean(NaN); // false
Boolean(null); // false
Boolean(undefined); // false
Boolean('PUJI'); // true
Boolean(9); // true

运算符

运算符 ( Operator ) 也被称为操作符,是用于实现赋值、比较和执行算数运算等功能的符号。

JavaScript 中常用的运算符有:

  • 算数运算符
  • 递增递减运算符
  • 比较运算符
  • 逻辑运算符
  • 赋值运算符

算数运算符

用于执行多个变量或值的算数运算,算数运算符的符号包括加”+”、减”-“、乘”*”、除”/”、取模”%”(取余数)。

10 + 20; //30
10 - 20; //-10
10 * 20; // 200
10 / 20; //0.5
9 % 2; //1
  • 由数字、运算符、变量等以能求得数值的有意义排列方式所得的组合称为表达式。
  • 表达式最终都会有一个结果返回给我们,这个结果称为返回值。
  • 在程序中,是将我们右边表达式计算完毕后把返回值给到左边
  • 浮点数的算数运算里会容易出现问题,尽量避免使用浮点数

前后置递增/递减运算符

如果需要反复给数字变量添加或减去1,可以使用递增 (++) 或递减 (–) 运算符来完成。在 JavaScript 中,递增 (++) 或递减 (–) 即可以放在变量前面,也可以放在变量后面。放在变量前面时,我们可以称为前置递增(递减)运算符,放在变量后面时,我们称为后置递增(递减)运算符。

// 原理
var num = 1;
num = num + 1;
// 简化写法
var num = 1;
++num

前置递增/减和后置递增/减如果单独使用,效果是一样的。前置递增先自加1,再与表达式计算返回值,后置递增先返回原值与表达式计算,然后再自加1:

// 前置递增和后置递增如果单独使用,效果是一样的
var a = 10;
++a; // 11
var b = 10;
b++; // 11
// 示例 2
var a = 10;
++a + 10; // 21
var b = 10;
b++ + 10; //20 (先使用原值与表达式计算,然后在给变量增加一个+1的值)
// 前置递增与后置递增的练习
var a = 10;
++a;
var b = ++a + 2; // 14
var c = 10;
c++;
var d = c++ + 2; // 13
var e = 10;
var f = e++ + ++e; // 22
  • 递增/减运算符必须和变量配合使用;
  • 变量与++/–之间没有空格;
  • 前置递增/减和后置递增/减运算符单独使用时,运行结果相同,与其他代码联用时结果会不同;
  • 开发时,大多情况会使用后置递增/减。

比较运算符

比较运算符也可称为关系运算符,是两个数据进行比较时所使用的运算符,比较运算后,会返回一个布尔值 ( true / false )作为比较运算的结果。

比较运算符的符号:

符号说明案例结果
<小于3 < 6true
>大于3 > 6false
<=小于等于3 <= 3true
>=大于等于3 >= 6false
==判断是否相同 (会转型)36 == 36true
!=判断是否不相同36 != 36false
=== !==值和数据类型一起进行对比36 === ’36’false
  • 除 “===” 与 “!==” 外,其他的比较运算符都会将字符串型的数据转换为数字型数据

逻辑运算符

用来进行布尔值运算的运算符,其返回值也是布尔值。在开发中常用于多个条件的判断。

逻辑运算符说明案例
&&“逻辑与”,简称”与” andtrue && false
||“逻辑或”,简称”或” ortrue || false
!“逻辑非”,简称”非” not! true
// 逻辑与
3 > 6 && 3 > 1; //false,所有结果中,只要有一个是假时则返回 false
6 > 3 && 6 < 9; //true,所有结果都为真时返回 true
// 逻辑或
3 > 6 || 3 > 1; //true,所有结果中,只要有一个是真时则返回 true
6 < 3 || 6 > 9; //false,所有结果都为假时返回 false
//逻辑非
!(6 < 9); //false
//交叉使用
!(6 < 9 || 6 == 3); //false

短路运算(逻辑中断)

当有多个表达式(值)时,左边的表达式值可以确定结果时,就不再继续运算右边的表达式的值。

1、逻辑与

  • 表达式1 && 表达式2
  • 如果表达式1的值为真,则返回表达式2
  • 如果表达式1的值为假,则返回表达式1
123 && 456; // 456
0 && 456; // 0 (所有数字中,除0是假外,其他都是真)
0 && 1 + 2 && 123 * 456; // 0
'' && 1 + 2 && 123 * 456; //
null && 0 && 456; // null

2、逻辑或

  • 表达式1 || 表达式2
  • 如果表达式1结果为真,则返回的是表达式1
  • 如果表达式1结果为假,则返回表达式2
123 || 456; // 123
0 || 456; // 456
0 || 1 + 2 || 123 * 456; // 3
'' || 1 + 2 || 123 * 456; // 3
null || 0 || 456; // 456
// 逻辑中断的用法示例
var num = 0;
123 || num++;
console.log(num); // 0 由于使用了逻辑中断,num++并没有进行运算

赋值运算符

赋值运算符说明案例
=直接赋值var myName = ‘PUJI’;
+= , -=加 / 减 一个数后再赋值var age = 10; age += 5; // 15
*= , /= , %=乘 / 除 / 取模 后再赋值var age = 2; age *= 5; // 10

展开运算符

bilibili 视频教程: https://www.bilibili.com/video/BV1bS4y1b7NV?p=4

展开运算符使用 “…” ,能将数组转换为逗号分隔的参数序列,只要可以遍历的元素都可以使用展开运算符。

const A = [1,2,3];
function pj() {
  console.log(arguments);
}
pj(...A);
// 数组的合并
const A = [1,2];
const B = [3,4];
const C = [...A, ...B]
// 数组的克隆
const A = [1,2];
const B = [...A];
// 将伪数组转为数组
const DIV = document.querySelectorAll('div');
const DIVARR = [...DIV];
console.log(DIVARR);
  • 数组的合并可以使用扩展运算符
  • 数组的克隆

运算符优先级

优先级运算符顺序
1小括号()
2一元运算符++ — !
3算数运算符先* / % 后 + –
4关系运算符> >= < <=
5相等运算符== != === !==
6逻辑运算符先 && 后 ||
7赋值运算符=
8逗号运算符,

流程控制

在一个程序执行的过程中,各条代码的执行顺序对程序的结果是由直接影响的。很多时候我们要通过控制代码的执行顺序来实现我们想要完成的功能。

流程控制主要有三种结构,分别是顺序结构分支结构循环结构,这三种结构代表三种代码执行的顺序。

分支流程控制

分支结构

由上到下执行代码的过程中,根据不同的条件执行不同的路径代码(执行代码多选一的过程),从而得到不同的结果。

if 语句

// 如果if里面的表达式结果为真 (true) 则执行大括号里的代码
if (条件表达式) {
  ...
}
//示例
if ( 6 > 3 ) {
  alert('Yes');
}

if / else 双分支语句

// 如果if里面的表达式结果为真 (true) 则执行代码1,否则执行代码2
if (条件表达式) {
  代码 1
} else {
  代码 2
}
//示例
if ( 6 > 3 ) {
  alert('Yes');
} else {
  alert('No');
}

if / else if 多分支语句

// 如果if里面的表达式结果为真 (true) 则执行代码1,否则执行代码2
if (条件表达式1) {
  代码 1
} else if (条件表达式2) {
  代码 2
} ... {
  ...
}
else {
  代码n
}
//示例
if ( 6 < 3 ) {
  alert('Yes');
} else if ( 6 > 9 ) {
  alert('Yes');
} else {
  alert('No');
}

三元表达式

三元表达式也能做一些简单的条件选择,有三元运算符组成的式子称为三元表达式。可以看作是简化版的 if else 语句。三元表达式由符号 “?” 和 “:”组成

条件表达式 ? 表达式1 : 表达式2
如果条件表达式结果为真,则返回表达式1的值,如果条件表达式结果为假,则返回表达式2的值。

var num = 10;
var result = num > 5 ? 'Yes' : 'No'; // Yes

switch 语句

switch 语句也是多分支语句,它用于基于不同的条件来执行不同的代码。当要针对变量设置一系列的特定值的选项时,就可以使用 switch 语句。

执行思路:利用我们的表达式的值和 case 后面的选项值进行对比,如果匹配,就执行该 case 里的语句,如果都不匹配,则执行 default 里的语句。( 如无 default 则什么都不执行 )

switch(表达式) {
  case value1:
    执行语句1;
    break;
  case value2:
    执行语句2;
    break;
  ...
  default:
    最后的执行语句;
}
switch (2) {
  case 1:
    document.write('1');
    break;
  case 2:
    document.write('2');
    break;
  default:
    document.write('No Result');
}
  • 在开发时,表达式一般会使用变量;
  • 表达式的值和case里的值必须全等的时候才属于匹配,如 a === 1;
  • 如果不写条件下的 break 那么代码会一直往下执行;

if 与 switch 的区别

  • 一般情况下,他们两个语句可以相互替换;
  • switch / case 语句通常在有确定的值的情况下使用;
  • if / else 语句通常用于范围判断 ( 大于、等于某个范围等 );
  • switch / case 语句进行条件判断时,会直接执行匹配条件的程序语句,效率更高,而 if / else 语句会逐条进行判断;
  • 当分支比较少时,if / else 执行效率相对会更高,较多时则 switch / case 语句会效率更高。

循环流程控制

目标:

  • 能够说出循环的目的是什么
  • 能够说出 for 循环的执行过程
  • 能够使用断点调试来观察代码的执行过程
  • 能够使用 for 循环完成累加求和等案例
  • 能够使用双 for 循环完成乘法表案例
  • 能够说出 while 循环和 do while 循环的区别
  • 能够说出 break 和 continue 的区别

循环的内容

  • 循环
  • for 循环
  • 双重 for 循环
  • while 循环
  • do while 循环
  • continue 与 break

什么是循环

循环的目的是可以重复执行某些代码,JS 中主要有三种类型的循环语句,分别是 for 循环、while 循环、do … while 循环。

在程序中,一组被重复执行的语句被称为循环体,能否继续重复执行,取决于循环的终止条件。由循环体及循环的终止条件组成的语句,被称为 循环语句。

for 循环(重点)

重复执行某些代码,通常与计数有关系

// 以下三个选项一个不能少
for(初始化变量; 条件表达式; 操作表达式) {
  ...
}
// 示例
for (var i = 1; i <= 100; i++) {
    document.write('Hello');
}
  • 初始化变量,就是用 var 声明的一个普通变量,通常用于作为计数器使用;
  • 条件表达式,就是用来决定每一次循环是否继续执行,也就是终止条件;
  • 操作表达式,是每次循环最后执行的代码,经常用于我们计数器变量进行更新(递增或递减)。

for 循环的执行过程

  1. 首先执行初始化的变量,但这句话在 for 循环里只执行一次;
  2. 接着去条件表达式里去判断是否满足条件,如果满足条件则进入第三步,如果不满足则退出循环;
  3. 执行循环体;
  4. 接着执行操作表达式,然后再进入步骤2进行判断,如果条件满足则继续循环,如果不满足则退出循环体。

断点调试

断点调试是指自己在程序的某一行设置一个断点,调试时,程序运行到这一行就会停止,然后你可以一步步往下调试,调试过程中可以看到各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下。

断点调试可以帮组我们观察程序的运行过程,浏览器中检查代码 – sources – 找到需要调试的文件 – 在程序的某一行设置断点-刷新浏览器

代码调试的能力非常重要,只有学会了代码调试,才能学会自己解决bug的能力。

双重 for 循环

很多情况下,单层 for 循环并不能满足我们的需求,比如我们要打印一个5行5列的五角星,打印一个倒三角形等,此时就可以通过循环嵌套来实现。

for(外层初始化变量; 外层条件表达式; 外层操作表达式) {
    for(里层初始化变量; 里层条件表达式; 里层操作表达式) {
        ...
    }
}
  • 我们可以把里面的循环当作外层循环的执行语句;
  • 外层循环循环一次,里面的循环执行全部。
for (var i = 1; i <= 3; i++) {
    for (var j = 1; j <= 3; j++) {
        console.log('Outter' + i);
        for (var j = 1; j <= 3; j++) {
            console.log('Inner' + j);
        }
    }
}
var str = '';
for (var i = 1; i <= 10; i++) {
    for (var j = 1; j <= 11 - i; j++) {
        str = str + "A"
    }
    str = str + '\n'
}
console.log(str);
// 结果
AAAAAAAAAA
AAAAAAAAA
AAAAAAAA
AAAAAAA
AAAAAA
AAAAA
AAAA
AAA
AA
A

while 循环

while 语句可以在条件表达式为真的前提下,循环执行指定的一段代码,直到表达式不为真的时候结束循环。

while (条件表达式) {
  ...
}
// 示例
var num = 1;
while (num <= 100) {
    console.log('PUJI');
    num++;
}

执行思路

  1. 先执行条件表达式,如果结果为 true ,则执行循环体代码,如果为 false,则退出循环
  2. 执行循环体代码
  3. 循环体代码执行完毕后,程序会返回第一步继续进行判断,直到循环条件为 false时,循环结束。
  • 里面应该有计数器(初始化变量)
  • 循环体中需要有操作表达式,完成计数器的更新,防止死循环

for 与 while 循环之间的区别

  • while 循环可做比 for 循环更复杂的选项

do … while 循环

do … while 语句其实是 while 语句的一个变体,该循环会先执行一次代码块,然后对条件表达式进行判断,如果条件为真,就会重复执行循环体,否则退出循环。

do {
    ...
} while(条件表达式)
var i = 1;
do {
    console.log('PUJI');
    i++;
} while ( i <= 100 )

循环小结

  • js中循环分为三种,分别是 for、while、do while;
  • 三个循环很多情况下都可以相互替代使用;
  • 如果是用来计算次数,跟数字相关的,三者使用基本相同,只是 for 更常用;
  • while 和 do while 可以做更复杂的判断条件,比for循环更灵活;
  • while 和 do while 执行顺序不一样,while先判断后执行,do while先执行一次在判断是否继续执行

continue 与 break

continue 关键字用于立即跳出本次循环,继续下一次循环(本次循环体中continue之后的代码就会少执行一次)。

break 关键字用于立即跳出整个循环(循环结束)

// continue 的使用
for (var i = 1; i <= 5; i++) {
    if (i == 3) {
        continue;
    }
    console.log(i);
} // 1,2,4,5
// break 的使用
for (var i = 1; i <= 5; i++) {
    if (i == 3) {
        break;
    }
    console.log(i);
} // 1,2

迭代器 (Iterator) ES6

视频教程:https://www.bilibili.com/video/BV1uK411H7on?p=18

迭代器是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构之遥部署 Iterator 接口,就可以完成遍历操作。Iterator 接口也就是对象中的一个名为 Symbol.iterator 的属性。ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 使用。迭代器的用途主要用于自定义去遍历数组。

原生具备 Iterator 接口的数据(可用 for…of 遍历)

  • 数组 Array
  • Arguments 参数
  • Set
  • Map
  • String 字符串
  • TypedArray
  • NodeList
工作原理
  • 创建一个指针对象,指向当前数据结构的起始位置;
  • 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员;
  • 接下来不断调用 next 方法,指针一直往后移动,知道指向最后一个成员
  • 每次调用 next 方法返回一个包含 value 和 done 属性的对象

for … of

与 for … in 不同,of 后面返回的是值,而 in 后面返回的是属性名

for(let v of value) {
  ...
}

生成器

视频教程:https://www.bilibili.com/video/BV1uK411H7on?p=20

生成器是一个函数,是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。是一个纯回调函数

// 声明
function * gen() {
  yield 111;
  yield 222;
  yield 333;
}
// 调用
let iterator = gen();
iterator.next();
function getUsers() {
    setTimeout(() => {
        let data = '用户数据';
        iterator.next(data);
    }, 1000)
}
function getOrders() {
    setTimeout(() => {
        let data = '订单数据';
        iterator.next(data);
    }, 1000)
}
function getGoods() {
    setTimeout(() => {
        let data = '商品数据';
        iterator.next(data);
    }, 1000)
}
function * gen() {
    let users = yield getUsers();
    console.log(users);
    let orders = yield getOrders();
    console.log(orders);
    let goods = yield getGoods();
    console.log(goods);
}
let iterator = gen();
iterator.next();

Promise (重要) ES6

视频教程:https://www.bilibili.com/video/BV1uK411H7on?p=24

Promise 是 ES6 引入的异步编程的新解决方案。语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。

  • Promise 构造函数:Promise (excutor) {}
  • Promise.prototype.then 方法
  • Promise.prototype.catch 方法

集合 (Set) ES6

视频教程:https://www.bilibili.com/video/BV1uK411H7on?p=30

ES6 提供了新的数据结构 Set 。它类似于数组,但成员的值都是唯一的,集合实现了 Iterator 接口,所以可以使用扩展运算符和 for… of 进行遍历,集合的属性和方法:

  • size 返回集合的元素个数
  • add 增加一个新元素,返回当前集合
  • delete 删除元素,返回布尔值
  • has 检测集合中是否包含某个元素,返回布尔值

创建集合

// 声明一个集合
let s = new Set(['Digital','Branding','Coding']);

添加元素 add

s.add('UI&UX');

删除元素 delete

s.delete('UI&UX');

检测 has

集合长度size

清空集合

s.clear();

Map (ES6)

ES6 提供了 Map 数据结构,它类似于对象,也是键值对的形式,但是键的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了 iterator 接口,所以可以使用扩展运算符和 for…of 进行遍历。Map 的属性和方法

  • size
  • set
  • get
  • has
  • clear

数组 (Array)

目的:

  • 知道为什么要有数组
  • 能够创建数组
  • 能够获取数组中的元素
  • 能够对数组进行遍历
  • 能够给数组新增一个元素

数组 ( Array ) 是指将一组相关的数据存储在单个变量名下的集合,其中每个数据被称为元素,在数组中可以存放任意类型的元素。

创建数组

数组的创建方式有两种,一种是通过new关键字创建数组,一种是利用数组字面量 ‘[ ]’ 创建数组,两种创建方式的结果相同。

// 利用 new 关键字创建数组
var arr = new Array(); // 创建了一个空的数组
var arr = new Array(2); // 表示数组长度为2的空数组
var arr = new Array(2,3); // 表示有2个数组元素,分别是2, 3
// 利用数组字面量创建数组(常用)
var 变量名 = []; // 创建了一个空的数组
// 示例
var arr = [1, 2, 'PUJI Design', true];
  • 声明数组并赋值,我们称为数组的初始化
  • 数组里的数据一定使用逗号来分隔
  • 数组里的数据被称为数组元素

检测是否为数组

var arr = [1,2,3];
// 方法1
arr instanceof Array; // true
// 方法2
Array.isArray(arr); // true

数组的索引

索引 ( 下标 ):用来访问数组元素的序号(数组下标从0开始)。数组可以通过索引来访问、设置和修改对应的数组元素。如果数组中没有对应的元素,则输出 undefined。

数组名[索引]

获取数组元素

var arr = [1, 2, 'PUJI Design', true];
console.log(arr[2]); // PUJI Design

获取数组长度 .length

使用 “数组名.length” 可以获取数组元素的长度 (数量)。

var arr = [1, 2, 'PUJI Design', true];
console.log(arr.length);

新增/修改数组元素

通过修改length长度增加数组元素,也可以通过数组的索引号的方式追加或修改数组元素。还可使用 push() / pop() 方法对数组元素进行修改

// 新增元素 方法1
var arr = [1, 2, 3]; // 声明一个数组变量
arr.length = 4; // 增加数组长度
arr[3] = 4; // 给新增的数组元素赋值
// 新增元素 方法2(追加数组元素)
var arr = [1, 2, 3];
arr[3] = 4;
// 修改元素
var arr = [1, 2, 3];
arr[0] = 4;

遍历数组(重要)

遍历数组,也就是把数组中的每一个元素从头到尾都访问一次。同样可以通过 forEach() 方法进行遍历。

var arr = [1, 2, 'PUJI Design', true];
for (var i = 0; i < 4; i++) {
    console.log(arr[i]);
}
  • 因为数组索引号是从 0 开始,所以i初始值为0;
  • 输出的时候 arr[i]
案例:

在一个数组中筛选大于等于10的数字放入一个新的数组

// 方法1
var arr = [2, 0, 6, 1, 77, 0, 52, 0, 25, 7];
var newArr = [];
var j = 0;
for (i = 0; i < arr.length; i++) {
    if (arr[i] >= 10) {
        newArr[j] = arr[i];
        j++;
    }
}
console.log(newArr);
// 方法2
var arr = [2, 0, 6, 1, 77, 0, 52, 0, 25, 7];
var newArr = [];
for (i = 0; i < arr.length; i++) {
    if (arr[i] >= 10) {
        newArr[newArr.length] = arr[i];
    }
}
console.log(newArr);

数组排序(冒泡排序)

冒泡排序是一种简单的排序算法,把一系列的数据按照一定的顺序进行排列显示(从小到大或从大到小)。可使用 sort() 方法更方便进行排序。

var arr = [5, 4, 3, 2, 1];
for (var i = 0; i < arr.length - 1; i++) {
  for (var j = 0; j < arr.length - i - 1; j++) {
    if (arr[j] > arr[j + 1]) {
      var temp = arr[j];
      arr[j] = arr[j + 1];
      arr[j + 1] = temp;
    }
  }
}
  • 外层循环控制交换的轮数
  • 内层循环控制每轮交换的次数

push() / pop()

// push() 在数组的末尾添加一个或多个数组元素
var arr = [1,2,3];
arr.push(4, 'PUJI');
console.log(arr); // [1, 2, 3, 4, 'PUJI']
// unshift 在数组的开头添加一个或多个数组元素
var arr = [1,2,3];
arr.unshift(4, 'PUJI');
console.log(arr); // [4, 'PUJI', 1, 2, 3]
  • push / unshift是可以给数组追加新的元素
  • push() / unshift() 参数直接写数组元素
  • push / unshift 完毕后,返回的结果是新数组的长度
// pop() 可以删除数组的最后一个元素
var arr = [1,2,3];
arr.pop();
console.log(arr); // [1, 2]
// shift() 可以删除数组的第一个元素
var arr = [1,2,3];
arr.shift();
console.log(arr); // [2, 3]
  • pop() / shift() 不带参数,一次只能删除一个元素
  • pop() / shift() 的返回值是删除的元素值

sort()

使用sort() 可以更方便的实现排序功能

// 基于个字符串的排序
var arr = [5,7,8,6,3,9];
arr.sort();
// 使用函数进行修改
var arr = [12, 5, 7, 36, 9, 6];
arr.sort(function(a, b) {
    return a - b; // 升序排列, b - a 则降序排列
});
console.log(arr);

indexOf() / lastIndexOf()

indexOf()可以根据数组元素的值查询数组中所在的序列,返回一个序列号值

indexOf() 和 lastIndexOf() 都是在数组中查询元素对应的序列号,一个是从前面开始查询,一个是从后面开始查询。

// 将数组中重复的元素进行筛除
var arr = ['c', 'a', 'z', 'a', 'x', 'a', 'x', 'c', 'b'],
    newArr = [];
for (var i = 0; i < arr.length; i++) {
    if (newArr.indexOf(arr[i]) == -1) {
        newArr.push(arr[i]);
    }
}
console.log(newArr);

数组转化为字符串有两种方法

// 方法一
var arr = [1,2,3];
arr.toString(); // 1,2,3
// 方法二
var arr = [1,2,3];
arr.join('-'); // 1-2-3 里面的分隔符可以自定义也可以使用默认

forEach()

用于遍历数组

array.forEach(function(currentValue[, indexvar arr = [3, 5, 6, 7, 8, 9, 11, 12, 16, 18];
var newArr = arr.some(function(value) {
  return value % 3 == 0;
})
console.log(newArr);, arr]));
  • currentValue 数组当前项的值
  • index 数组当前项的索引
  • arr 数组对象本身
var arr = ['Digital', 'Branding', 'Conding'];
arr.forEach(function(value, index, array) {
  console.log(value + ' : ' + index + ' @ ' + array);
});
// 返回结果
Digital : 0 @ Digital,Branding,Conding
main.js:3 Branding : 1 @ Digital,Branding,Conding
main.js:3 Conding : 2 @ Digital,Branding,Conding

map()

根据原有数组返回一个新的数组,需要一个回调函数作为参数,回调函数的返回值称为新数组的元素。

回调函数中有三个参数

  • 第一个参数为当前参数
  • 第二个参数为元素的索引
  • 第三个参数为当前数组
const arr = [1, 2, 3, 4, 5];
let result = arr.map(item => item + 1); // [2, 3, 4, 5, 6]

filter()

filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中复合条件的所有元素,主要用于筛选数组。

array.filter(function(currentValue[, index, arr]));
  • currentValue 数组当前项的值
  • index 数组当前项的索引
  • arr 数组对象本身
  • 会返回一个新的数组
var arr = [3, 5, 6, 7, 8, 9, 11, 12, 16, 18];
var newArr = arr.filter(function(value) {
  return value % 3 === 0;
})
console.log(newArr); // 3, 6, 9, 12, 18

find()

从数组中找到一个符合条件的第一个值。

reduce()

用来合并数组中的元素。

some()

用于检测数组中的元素是否满足指定条件,通俗点说,就是查找数组中是否有满足条件的元素,返回结果是一个布尔值。

array.filter(function(currentValue, index[, arr]));
  • currentValue 数组当前项的值
  • index 数组当前项的索引
  • arr 数组对象本身
  • 返回一个布尔值,如果查找到返回 true,否则返回 false
  • 如果找到第一个满足条件的元素,则终止循环
var arr = [3, 5, 6, 7, 8, 9, 11, 12, 16, 18];
var flag = arr.some(function(value) {
  return value % 3 == 0;
})
console.log(flag); // true

every()

函数 (Function)

目标

  • 能说出为什么需要函数
  • 根据语法书写函数
  • 能够根据需求封装函数
  • 能够说出形参和实参的传递过程
  • 能够使用函数的返回值
  • 能够使用arguments获取函数的参数
  • 能够说出函数的多种定义和调用方式
  • 能够说出和改变函数内部 this 的指向
  • 能够说出严格模式的特点
  • 能够把函数作为参数和返回值传递
  • 能够说出闭包的作用
  • 能够说出递归的两个条件
  • 能够说出深拷贝和浅拷贝的区别

概述

在js里,可能会定义非常多的相同代码或者功能相似的代码,这些代码可能需要大量重复使用,这种情况下使用函数就可以解决复用的问题。函数就是封装了一段可重复调用执行的代码块,通过此代码块可实现代码的多次重复使用。

  • 每个函数都有一个原型对象

函数的定义

视频教程:https://www.bilibili.com/video/BV17y4y1J7L1?p=51

函数的声明方式有三种方式,使用 function 关键字进行申明(命名函数),使用函数表达式进行申明(匿名函数),利用构造函数创建函数对象实例。

函数属于对象类型

// 自定义函数(命名函数)
function 函数名() {
  ...
}
// 函数表达式(匿名函数)
var fn = function () {
  ...
}
// 利用构造函数创建函数对象实例 (效率较低,不常用)
var fn = new Function('参数1', '参数2', '函数体')

函数的调用

视频教程:https://www.bilibili.com/video/BV17y4y1J7L1?p=52

// 调用普通函数(以上三种形式创建的函数都是普通函数)
fn();
// 调用对象函数
var obj = {
  fn: function() {
    ...
  }
}
obj.fn();
// 调用构造函数
function Fn() {
  ..
}
new Fn();
// 除以上三种形式外,还包括事件绑定函数、定时器函数、立即执行函数。
  • 函数是执行某件事情,函数名一般使用动词
  • 声明函数本身不会执行,只有调用了函数才会执行

进阶参考: JS 中四种函数的调用模式

函数的参数

函数的参数分为两种类型,形参与实参,在声明函数的圆括号里的参数称为形参,在函数调用的圆括号里的参数称为实参。

// 声明函数
function 函数名(形参1, 形参2, ...) {
  //函数体
}
// 调用函数
函数名(实参1,实参2, ...);
// 示例
function test(a,b,c) {
  return a + b + c;
}
test(1,2,3);
  • 形参是接收实参的,可以看作是一个不用声明的变量
  • 函数可以带参数也可以不带,并且带参数时数量不限
  • 多个参数之间使用逗号分隔
  • 如果形参与实参的数量一致,则正常输出结果
  • 如果实参的数量多余形参的数量,会取到形参的数量进行执行
  • 如果实参的数量少余形参的数量,多余的形参会被定义为 undefined
  • 建议尽量让形参与实参的数量一致

参数的默认值

// 示例
function test(a,b,c=10) {
  return a + b + c;
}
test(1,2);
// 示例 2
function connect({host, username, password, port='80'}) {
  console.log('ok');
}
conncet({
  host: 'puji.design',
  username: 'root',
  password: 'root',
  port: '8888'
})
  • 参数默认值,一般需要从右往左添加
  • 参数的默认值可以与解构赋值共同使用

return 返回值

将函数的值返回给调用者,此时可以通过使用return语句来实现。

function 函数名() {
  return 需要返回的结果;
}
函数名();
// 示例
function getResult() {
  return 'PUJI';
}
getResult(); // getResult() = 'PUJI'
// 示例2
var sum = 0;
function getSum(num1, num2) {
    for (var i = num1; i <= num2; i++) {
        sum += i;
    }
    return sum;
}
console.log(getSum(1, 100)); // 5050
// 示例3
function getMax(num1,num2) {
  return num1 > num2 ? num1 : num2;
}
console.log(getMax(1, 100));  // 100
// 示例4
function getMax(arr) {
    var maxNum = arr[0];
    for (var i = 1; i < arr.length; i++) {
        if (maxNum < arr[i]) {
            maxNum = arr[i];
        }
    }
    return maxNum;
}
console.log(getMax([5, 2, 99, 101, 67, 77]));
  • 函数只是实现某种功能,最终的结果需要返回给函数的调用者
  • 只要函数遇到 return ,就把后面的结果返回给函数的调用者
  • return 语句有终止函数的作用,遇到 return 之后的代码不被执行
  • return 只能返回一个值,如果需要输出多个值,可以利用数组或对象完成
  • 函数都是有返回值的,如果没有 return 将返回 undefined,有 return 则返回 return 语句中的值

break, continue, return 的区别

  • break:结束当前的循环体(如for,while)
  • continue:跳出本次循环,继续执行下一次循环(如for,while)
  • return:可在循环或函数中使用,不仅可以退出循环或函数,还能返回return语句中的值

arguments 参数

当我们不确定有多少个参数传递的时候,可以用 arguments 来获取,在 javascript中,arguments 实际上它是当前函数的一个内置对象。所有函数都内置了一个arguments 对象,arguments 对象中存储了传递的所有实参。

function 函数名(){
  arguments; // 里面存储了所有传递过来的实参
  arguments.length; // 5
  arguments[2]; // 2
}
函数名(1,2,3,4,5);

arguments 是以一个伪数组的形式来展示的,伪数组的特点以下:

  • 具有 length 属性
  • 按索引方式储存数据
  • 不具有数组的 push(), pop() 等方法

rest 参数 (ES6)

用于获取函数的实参,代替 arguments

function date(...args){
  console.log(args);
}
date(1,2,3); // 返回[1,2,3]
function date(a,b,...args){
  console.log(args);
}
date(1,2,3,4,5,6); // 返回[3,4,5,6]
  • rest 参数前面为三个 … 后面为自定义的标识符;
  • rest 参数返回的是一个数组,而 argument 返回的是一个对象;
  • rest 参数必须放到参数的最后。

函数可以调用其他函数

因为每个函数都是独立的代码块,用于完成特殊任务,因此经常会用到函数互相调用的情况。

匿名函数

前面一种函数的声明方式,也叫命名函数,以下的函数声明,fn只是变量名,因此此函数没有名称,也叫匿名函数。

// 函数表达式(匿名函数)
var 变量名 = function(){};
// 示例
var fn = function(){};
// 调用函数
fn();
  • 函数没有名称,声明的名称属于变量名
  • 函数表达式声明方式与声明变量差不多,只不过变量里存的是值,而函数表达式里存的是函数
  • 同样可以进行参数的传递

立即执行函数

一般的函数声明后需调用才可执行,而立即执行函数无需调用立马可以执行。主要创建一个独立的作用域

// 写法 1
(function() {})();
// 写法 2
(function() {}());
(function(a, b) {
  console.log(a + b);
})(1, 2);
  • 立即执行函数也可以进行参数传递
  • 立即执行函数可以是匿名函数,也可以命名;
  • 他的最大作用就是独立创建了一个作用域

高阶函数

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

// 接收函数作为参数
function fn(callback) {
  callback && callback();
}
fn(function() {alert('PUJI')});
// 将函数作为返回值输出
function fn(callback) {
  return function() {}
}
fn();

箭头函数 (ES6)

bilibili 视频教程:https://www.bilibili.com/video/BV1bS4y1b7NV?p=5

ES6 允许使用箭头 => 定义函数

// 以前声明函数的方法
var fn = function() {
  ..
}
// 箭头简化写法
let fn = () => {
  ..
}
// 省略小括号
let fn = n => {
  return n;
}
console.log(fn(3));
// 省略花括号
let fn = (n) => n * n;
console.log(fn(3));
// this 指向声明函数的作用域
let ad = document.querySelector('div');
ad.addEventListener('click', function(){
  setTimeout(() => {
    this.style.background = 'blue';
  },1000);
});
// 代码简化
const arr = [1,2,3,4,5,6,9];
const result = arr.filter( item => item % 2 === 0; )
  • 箭头函数的 this 是静态的,this 始终指向函数声明时所在作用域下的 this 的值;
  • 不能作为构造函数实例化对象;
  • 不能使用 arguments 变量;
  • 当形参有且只有一个的时候可以省略小括号;
  • 当执行体只有一条语句的时候可以省略花括号,并且 return 也必须省略;
  • 箭头函数适合与 this 无关的回调、定时器、数组的方法回调。

函数的 this 指向

视频教程:https://www.bilibili.com/video/BV17y4y1J7L1?p=53

this 的指向,是当我们调用函数的时候确定的,调用方式的不同决定了 this 的指向不同,一般指向我们的调用者

调用方式this 指向
普通函数调用普通模式:window 严格模式:undefine
构造函数调用实例对象,原型对象里的方法也指向实例对象。
对象方法调用该方法所属对象
事件绑定调用绑定事件对象
定时器函数window
立即执行函数window

JavaScript 为我们提供了一些函数方法来帮我们处理函数内部 this 的指向问题,常用的有 bind()、call()、apply() 三种方法。

call()

视频教程:https://www.bilibili.com/video/BV17y4y1J7L1?p=34
视频教程:https://www.bilibili.com/video/BV17y4y1J7L1?p=54

可以调用这个函数,并且修改函数运行时的 this 指向

fn.call(thisAsg, arg1, arg2, ...);
  • thisArg: 当前调用函数 this 的指向对象
  • arg1, arg2: 传递的其他参数
// 常规调用方法
function fn() {
  console.log('PUJI');
}
fn();
// 使用 call() 方法进行调用
function fn() {
  console.log(this);
}
fn.call(); // this 指向 window
// 修改 this 指向
function fn() {
  console.log(this.name);
}
var o = {
  name: 'PUJI'
}
fn.call(o); // 此时这个函数的 this 就指向了 o 这个对象
// 传递参数
function fn(a, b) {
  console.log(this.name);
  console.log(a + b);
}
var o = {
  name: 'PUJI'
}
fn.call(o, 1, 2); // call 只是修改 this 的指向,指向的对象不参与参数的传递
  • call() 方法可以调用函数
  • 改变函数运行时的 this 指向
  • 他的主要作用是可以实现继承

apply()

视频教程:https://www.bilibili.com/video/BV17y4y1J7L1?p=55

利用 apply() 方法调用一个函数,简单理解为调用函数的方式,但是他可以改变函数的 this 指向

fn.apply(thisArg, [argsArray])
  • thisArg: 在函数运行时指定的 this 值
  • argsArray: 传递的值,必须包含在数组里
  • 返回值就是函数的返回值,因为他就是调用函数
var o = {
  name: 'PUJI'
}
function fn(arr) {
  console.log(arr);
}
fn.apply(o, ['Design']); // 返回字符串
// 借助于数学内置对象求最大值
var arr = [1,2,3,5,6,9];
var result = Math.max.apply(Math, arr);
console.log(result);
  • 也是调用函数,同样可以改变函数内部的 this 指向
  • 但是他的参数必须是数组(伪数组)
  • 返回的值是一个字符串
  • 主要作用,比如可以利用 apply 借助于数学内置对象求最大值

bind()

视频教程:https://www.bilibili.com/video/BV17y4y1J7L1?p=57

bind() 不会调用函数,但是能改变函数内部 this 指向

fn.bind(thisArg, arg1, arg2, ...);
var o = {
  name: 'PUJI'
}
function fn() {
  console.log(this);
}
var f = fn.bind(o);
f();
  • thisArg: 函数运行时 this 的指向对象
  • arg1, arg2: 传递的其他参数
  • 不会调用原来的函数,可以改变原来函数内部的 this 指向
  • 返回由指定的 this 值和初始化参数改造的原函数拷贝
// bind 应用案例示例
var btn = document.querySelector('button');
btn.addEventListener('click', function() {
  this.disabled = true;
  setTimeout(function () {
    this.disabled = false; // 此时定时器里的 this 指向的是 btn 而不是 window 了
  }.bind(this), 3000);
})

递归函数

https://www.bilibili.com/video/BV17y4y1J7L1?p=70

如果一个函数在内部可以调用其自身,那么这个函数就是递归函数。递归函数的作用和循环效果一样。由于递归很容易发生”栈溢出”错误 ( Stack overflow ),所以必须加上退出条件 return。

function fn() {
  if() {
    return;
  }
  fn();
  
}
fn();

利用递归函数求数学题

// 示例 1
function fn(n) {
  if(n === 1) {
    return 1;
  }
  return n * fn(n - 1);
}
fn(6);
// 示例 2
function fn(n) {
  if(n ===1 || n === 2) {
    return 1;
  }
  return fn(n - 1) + fn(n - 2);
}
fn(6)

利用递归函遍历数据

var data = [
  {
    id: 1,
    name: 'Design',
    cats: [
      {
        id: 11,
        name: 'UI&UX'
      }, {
        id: 12,
        name: 'Branding'
      }, {
        id: 13,
        name: 'Website'
      }
    ]
  },
  {
    id: 2,
    name: 'Coding'
  }
]
var o = {};
function getID(json, id) {
  json.forEach( element => {
    if(element.id === id) {
      o = element
      return element;
    } else if ( element.cats && element.cats.length >0 ) {
      o = getID( element.cats, id);
      
    }
  });
  return o;
}
console.log(getID(data, 11));

检测属性

let arr = ['PUJI', 'Design']
console.log(arr.hasOwnProperty('length')) // 只检测自己是否有此属性
console.log('length' in arr) // 检测自己以及父级是否有此属性

作用域

目标:

  • 能够说出 javascript 的两种作用域
  • 能够区分全局变量和局部变量
  • 能够说出如何在作用域链中查找变量的值

什么是作用域

一段程序代码中所用到的名字并不总是有效可用的,而限定这个名字的可作用性的代码范围就是这个名字的作用域。作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,更重要的是减少了名字的冲突。

js的作用域分为两种 (es6 之前)

  • 全局作用域:在文件的最外层定义的变量就是在全局作用域里
  • 局部作用域:在函数内定义的变量就是局部作用域,这个代码名字只在函数内部起效果,因此也叫函数作用域。
var a = 10;
function fn() {
  var b = 20;
}
// a 为全局作用域的变量,b 为局部作用域的变量
  • 全局作用域与局部作用域的相同的变量不会相互影响

变量的作用域

在javascript中,根据作用域的不同,变量也可以分为全局变量与局部变量

  • 全局变量:在全局作用域下的变量就是全局变量,在全局下都可使用
  • 局部变量:在局部作用域下的变量,或者说是在函数内部的变量就是局部变量
var a = 1;
function fn() {
  var b = 2;
  c = 3;
}
// a全局变量
// b局部变量
// c全局变量
  • 如果在函数内部没有声明直接赋值的变量,也属于全局变量
  • 函数的形参也可以看作局部变量
  • 从执行效率来看,全局变量只有浏览器关闭的时候才会销毁,比较占内存资源,局部变量当程序执行完毕就会销毁,比较节约内存资源

作用域链

根据内部函数可以访问外部函数变量变量的这种机制,用链式查找决定哪些数据能被内部函数访问,就称作作用域链,作用域链采用就近原则,即查找到最近一层的数据后就停止再向外去找

块级作用域 (ES6)

使用花括号 {} 包裹,里面的作用域就属于块级作用域,块级作用域里的变量,只在当前块中有效。块级作用域的好处在于,如果项目比较复杂的情况下,有内层变量覆盖外层变量的风险。

if(true) {
  块级作用域
}

闭包 (Closure)

视频教程:https://www.bilibili.com/video/BV17y4y1J7L1?p=63

闭包是指有权访问另一个函数作用域中变量的函数。简单理解就是,一个作用域可以访问另外一个函数内部的局部变量。闭包的作用是延伸了变量的作用范围。

案例示范

循环注册点击事件

// 手动获取索引号方式
var items = document.querySelectorAll('li');
for ( i = 0; i < items.length; i++ ) {
  items[i].index = i; // 获取索引号
  items[i].addEventListener('click', function () {
    console.log(this.index);
  })
}
// 利用闭包方式获得索引号 (相比第一种效率更低)
var items = document.querySelectorAll('li');
for ( i = 0; i < items.length; i++ ) {
  // 利用 for 循环创建了多个立即执行函数
  (function (i) {
    items[i].addEventListener('click', function () {
      console.log(i);
    })
  })(i);
}

循环中的 setTimout()

var items = document.querySelectorAll('li');
for ( i = 0; i < items.length; i++ ) {
  (function (i) {
    setTimeout(function() {
      console.log(i);
    }, 3000);
  })(i);
}

出租车计价案例

var car = (function() {
  var start = 13;
  var total = 0;
  return {
    price: function(n) {
      if(n <= 3) {
        total = start;
      } else {
        total = start + (n-3)*5
      }
      return total;
    },
  }
})();
console.log(car.price(6));

浅拷贝与深拷贝

浅拷贝只是拷贝一层,更深层次的对象级别的只拷贝引用。深拷贝可拷贝多层,每一层级的数据都会被拷贝。

在 ES6 中新增了浅拷贝的方法 Object.assign( target, …sources )

// 浅拷贝
var obj = {
  name: 'PUJI',
  age: 36,
  cats: {
    work1: 'Digital',
    work2: 'Branding',
    work3: 'Coding'
  }
};
var o = {};
for(var k in obj) {
  // k 是属性名,obj[k] 是属性值
  o[k] = obj[k];
}
console.log(o);
// assign() 方法
var obj = {
  name: 'PUJI',
  age: 36,
  cats: {
    work1: 'Digital',
    work2: 'Branding',
    work3: 'Coding'
  }
};
var o = {};
Object.assign(o, obj);
console.log(o);
// 浅拷贝可以拷贝第一层的数据,更深层的对象数据只会拷贝对象的指向地址,所以修改此对象的数据,另一个对象里的数据也会一起更改
// 深拷贝
var obj = {
  name: 'PUJI',
  age: 36,
  cats: {
    work1: 'Digital',
    work2: 'Branding',
    work3: 'Coding'
  },
  num: [3, 6, 9]
};
var o = {};
function deepCopy(newObj, oldObj) {
  for( var k in oldObj ) {
    // 判断属性值属于哪种数据类型
    var element = oldObj[k];
    if( element instanceof Array ) {
      newObj[k] = [];
      deepCopy(newObj[k], element);
    } else if( element instanceof Object ) {
      newObj[k] = {};
      deepCopy(newObj[k], element);
    } else {
      newObj[k] = element;
    }
  }
}
deepCopy(o, obj)
console.log(o);;
// 深拷贝可以拷贝所有层级的数据,会把深层级的数据另外开辟一个空间进行存储,所以修改此对象的数据,不会对另一个对象里的数据造成影响

对象 (Object) (重要)

目标

  • 为什么需要对象
  • 能够使用字面量创建对象
  • 能够使用构造函数创建对象
  • 能够说出 new 的执行过程
  • 能够遍历对象

在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,如字符串,数值,数组函数等。对象属于复杂数据类型

JavaScript 中的对象分为3种:自定义对象、内置对象、浏览器对象。前面两种对象是js基础内容,属于ECMAScript;第三个浏览器对象属于 JS 独有的。

对象是由属性和方法组成的
  • 属性:事物的特征,在对象中用属性来表示(常用名次);
  • 方法:事物的行为,在对象中用方法来表示(常用动词)。
为什么需要对象

在保存一个值时,可以使用变量,保存多个值(一组值)可以使用数组,但如果需要保存多维的数据,JS 中的对象表达结构更清晰,更强大。

创建对象的三种方式

在 JavaScript 中 我们可以采用三种方式创建对象

  • 利用字面量创建对象(常用):就是花括号 {} 里包含了表达这个具体事物(对象)的属性和方法;
  • 利用 new Object 创建对象;
  • 利用构造函数创建对象。

利用字面量创建对象

var obj = {}; // 创建了一个空的对象
var obj = {
  uname: 'Wewe',
  age: 36,
  sex: 'male',
  sayHi: function() {
    ...
  }
}
  • 里面的属性或方法我们采取键值对的形式,即名称与值,值可以是任何类型;
  • 多个属性或者方法中间用逗号隔开;
  • 方法冒号后面跟的是一个匿名函数。

new 关键字创建对象

// 利用 new Object 创建对象
var obj = new Object(); //创建了一个空的对象
obj.uname = 'Wewe';
obj.age = 36;
obj.sex = 'male'
obj.sayHi = function() {}
  • 利用等号赋值的方法添加对象的属性和方法;
  • 每个属性和方法之间使用分号分隔;
  • 调用与前面一样。

利用构造函数创建对象(重要)

ES6 新增了面向对象的概念,和此方法类似。

由于前面两种创建对象的方式一次只能创建一个对象,如果需要一次创建多个对象,且很多属性和方法是大致相同的,因此我们可以利用函数的方法重复这些相同的代码,又因为这个函数里面封装的不是普通代码,而是对象,我们就把这个函数称为构造函数。

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 运算符一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里。

function 构造函数名() {
  this.属性 = 值;
  this.方法 = function() {
    ...
  }
}
new 构造函数名();
// 示例
function Star(uname, age, sex) {
  this.name = uname;
  this.age = age;
  this.sex = sex;
}
Star.prototype.sing = function(song) {
  console.log(song);
}
var ldh = new Star('刘德华', '36', '男');
ldh.sing('冰雨');
var zxy = new Star('张学友', '39', '男');
zxy.sing('李香兰');
console.log(ldh);
console.log(zxy);
  • 构造函数名字首字母需要大写;
  • 构造函数中的属性与方法指向构造函数的原型对象;
  • 通过 new 构造出的子类,将自动同函数的原型对象进行关联;
  • 每个子类还可拥有自己独立的属性和方法( 尽量将每个子类相同的方法都放在原型对象里 );
  • 原型对象相当于所有构造出的实例对象的公共区域;
  • 设置属性时,只能通过原型对象自己去设置这个属性,如 “Star.prototype.name = 123” ;
  • 如果实例对象设置一个跟原型对象相同属性的值时,会自动添加一个自己的属性,如 “ldh.name = 456” ;
  • 构造函数中不需要 return 就可以返回结果,返回的结果是 this 对象,如果写了 return 将返回 return 的值;
  • 我们只要调用一次构造函数就创建了一个新的实例对象
  • 属性和方法前面必须添加 this
  • 属性在构造函数中添加,尽量不要在原型中添加属性( 如果是常量可以添加在原型中 ),方法尽量在原型中添加。
// 标准用法结构
function Studio() {
  this.name = 'PUJI';
  this.age = 36;
}
Studio.prototype.work = function() {
  console.log(this.name + this.age + 'years old.');
}
var a = new Studio();
a.work();

// 改进1,将属性参数化
function Studio(name, age) {
  this.name = 'PUJI';
  this.age = 36;
}
Studio.prototype.work = function(work) {
  console.log(this.name + this.age + 'years old.' + ' And we lover ' + work);
}
var a = new Studio( 'PUJI', 36 );
a.work('Design');
// 改进2,将参数设为对象
function Studio(opt) {
  this.name = opt.name;
  this.age = opt.age;
}
Studio.prototype.work = function() {
  console.log(this.name + this.age + 'years old.' + ' And we lover ' + work);
}
var a = new Studio( {
  name: 'PUJI',
  age: 36
} );
a.work('Design');
// 改进3,将属性与方法统一在一个对象
function Studio(opt) {
    this._init(opt); // 使用下划线定义,用于区分此方法仅在内部使用
  }
  Studio.prototype = {
      constructor: Studio, // 指向回原来的构造函数
    _init: function(opt) {
      this.name = opt.name;
      this.age = opt.age;
    },
    work: function(work) {
      console.log(this.name + this.age + 'years old.' + ' And we lover ' + work);
    }
  }
  var a = new Studio( {
    name: 'PUJI',
    age: 36,
    work: 'Design'
  } );
  console.log(Studio.prototype.constructor);
  console.log(a.__proto__.constructor);
new 关键词的执行过程
  1. 通过 new 构造子类,生成一个新的实例对象;
  2. 在内存中创建一个空的 this 对象;
  3. this 对象指向构造函数原型对象;
  4. 执行构造函数里的代码,给 this 对象添加属性和方法。
构造函数与对象之间的联系
  • 构造函数是抽象了对象的公共部分,封装到了函数里面,泛指的某一大类,
  • 对象是特指某一个,通过 new 关键字创建对象的过程我们也称为对象的实例化

构造函数的成员

构造函数中的属性和方法都称为成员,成员可以随意添加,成员分为实例成员和静态成员。实例成员就是构造函数内部通过 this 添加的成员,实例成员只能通过实例化的对象来访问。静态成员是在构造函数本身上添加的成员。静态成员只能通过构造函数来访问。

function Studio(name) {
  this.name = name;
}
var puji = new Studio('PUJI');
console.log(puji.name); // 实例成员只能通过实例化的对象来访问,不可通过构造函数来访问,如 Star.name
Studio.age = 36;
console.log(Studio.age); // 静态成员只能通过构造函数来访问,不能通过实例化对象来访问

原型 Prototype

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

JavaScript 规定,每一个构造函数都有一个原型属性,指向另一个对象,这个原型就是一个对象,对象的所有属性和方法都会被构造函数所拥有。我们可以把一些不变的方法,直接定义在原型对象上,这样所有对象的实例就可以共享这些方法。

一般情况下,我们公共属性定义到构造函数里,方法放在原型对象里。

实例化的对象系统会自动添加一个 __proto__ 原型,指向了构造函数的原型对象 prototype, __proto__ 和 prototype 是相同的空间。方法查找规则,先看对象身上是否有此方法,如果有就执行,没有就去原型对象身上去查找。

添加/修改/删除属性

使用 “对象名+属性名” 的方式进行添加或修改属性,也可使用 Object.defineProperty()

// 添加属性
var obj = {
  uname: 'PUJI',
  age: 36
}
obj.sex = 'male';
// 修改属性
var obj = {
  uname: 'PUJI',
  age: 36,
  sex: 'male'
}
obj.uname = 'Wewe';
// 删除属性
var obj = {
  uname: 'PUJI',
  age: 36,
  sex: 'male'
}
delete obj.sex;

使用对象

调用对象的属性有两种方法,第一种方法采取 “对象名.属性名” 的形式,第二种方法 “对象名[‘属性名’]”,调用对象的方法采用 “对象名.方法名()”

// 调用属性方法1
obj.age;
// 调用属性方法2
obj['age'];
// 调用方法的方法
obj.sayHi();

变量、属性、函数、方法的区别

变量和属性都是用于存储数据的,变量单独声明并赋值,使用的时候直接写变量名,属性是在对象里,不需要声明,使用的时候必须是对象.属性名

函数和方法都是实现某种功能或做某件事,函数是单独声明并且调用的,方法在对象里,无需声明,调用的时候需 对象.方法()

简化对象写法 (ES6)

允许在花括号里,直接写入变量和函数,作为对象的属性和方法。

// 之前的写法
var name = 'PUJI';
var work = function() {
  console.log(name)
}
var obj = {
  name: name,
  work: work,
  change: function() {
    console.log('Design Studio');
  }
}
// ES6 简化写法
let name = 'PUJI';
let work = function() {
  console.log(name)
}
const obj = {
  name,
  work, // 属性名和变量名一致时赋值的简化
  change() {
    console.log('Design Studio'); // 声明方法的简化
  }
}

遍历对象 for … in

for … in 语句用于对数组或对象的属性进行循环操作

for (变量 in 对象);
// 示例
var obj = {
  uname: 'Wewe',
  age: 36,
  sex: 'male',
  sayHi: function() {
    ...
  }
}
for (var k in obj) {
  console.log(k); //得到的是属性名
  console.log(obj[k]);// 得到的是属性值
}

使用for in语句时,变量名常常使用 k 或者 key

Object.keys()

用于获取对象自身所有的属性,效果类似 for … in。返回一个由属性名组成的新数组。

var obj = {
  name: 'PUJI',
  age: 36
}
var arr = Object.keys(obj);
console.log(arr); // ['name', 'age']

Object.defineProperty()

视频教程:https://www.bilibili.com/video/BV17y4y1J7L1?p=49

定义对象中新属性或修改原有的属性。

Object.defineProperty(obj, prop, descriptor);
  • obj: 目标对象
  • prop: 需定义或修改的属性名称
  • descriptor: 目标属性所拥有的特性。
    • value ( 设置属性值,默认为 undefined )
    • writeable ( 值是否可重写,true / false 默认为 false )
    • enumerable ( 目标属性是否可以被枚举(遍历),true / false 默认为 false )
    • configurable ( 目标属性是否可以被删除或再次修改 descriptor 里的特性,true / false 默认为 false )
var obj = {
  uname: 'PUJI',
  age: 36
}
Object.defineProperty(obj, 'sex', {
  value: 'male'
});
console.log(obj);
  • 使用此函数,如果以前的对象中有新增的这个属性名则进行修改,如果没有则添加

面向对象 (ES6)

  • 能够说出什么事面向对象
  • 能够说出类和对象的关系
  • 能够使用 class 创建自定义类
  • 能够说出什么是继承

概述

编程的两大思想:
  • 面向过程 POP:Process-oriented programming,就是分析出解决问题所需要的步骤,然后再用函数把这些步骤一步步实现,使用的时候再一个一个的依次调用即可;
  • 面向对象 OOP:Object Oriented Programming,就是把事物分解成一个个对象,然后由对象之间分工与合作。面向对象就是以对象功能来划分问题,而不是步骤。面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作或复杂的大型软件项目。
面向过程与面向对象的对比
  • 面向过程性能比面向对象高,适合跟硬件联系很紧密的东西,但没有面向对象易维护、易复用、易扩展。
面向对象的特性:
  • 封装性
  • 继承性
  • 多态性
面向对象的思维特点

面向对象更贴近我们的实际生活,可以使用面向对象描述现实世界的事物,但是事物分为抽象的事物和具体的事物

  • 抽取对象共同的属性和行为封装成一个类(模版);
  • 对类进行实例化,获取类的对象。

在 javascript 中,对象是一组无须的相关属性和方法的集合,所有的事物都是对象,如字符串、数值、数组、函数等。对象由属性和方法组成。

类 class

在 ES6 中新增加了类的概念,可以使用 class 关键词声明一个类,之后以这个类来实例化对象。类抽象了对象的公共部分,泛指某一大类,对象特指通过实例化出的一个具体对象

书写语法

class Name {
  ...
}
var a = new Name();

类 constructor 构造函数

constructor() 方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过 new 命令生成对象实例时,自动调用该方法,如果没有定义,类内部会自动给我们创建一个 constructor()

对象原型 ( __proto__ ) 和构造函数原型对象 ( prototype )里都有一个构造函数 constructor,因为他指回构造函数本身,constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。

class Star {
  constructor(uname, age) {
    this.uname = uname;
    this.age = age;
  }
}
var ldh = new Star('刘德华',18);
var zxy = new Star('张学友',19);
console.log(ldh);
console.log(zxy);
console.log(Star.prototype.constructor); //返回原来的构造函数
console.log(ldh.__proto__.constructor);
  • 通过 class 关键字创建类,类名首字母大写;
  • 类里有个 constructor 函数,可以接受传递过来的参数,同时返回实例对象;
  • constructor 函数只要 new 生成实例时,就会自动调用这个函数,如果不写这个函数,类也会自动生成;
  • 生成实例 new 不能省略;
  • 创建类类名后面不要小括号,生成实例类名后面加小括号,构造函数不需要加 function

类中添加方法

window.addEventListener('load', function() {
    class Star {
        constructor(uname, age) {
            this.uname = uname;
            this.age = age;
        }
        sing(song) {
            console.log(this.uname + song);
        }
    }
    var ldh = new Star('刘德华',18);
    var zxy = new Star('张学友',19);
    ldh.sing('冰雨');
    zxy.sing('李香兰');
}); 
  • 类里的所有函数不需要写 function;
  • 多个函数方法之间不需要添加逗号分隔;

extends 类的继承

可以继承父类的属性与方法

class A {
  constructor() {
    this.name = 'PUJI';
  }
}
class B extends A {}
var son = new B();
console.log(son);  // {name: PUJI}
  • 如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类
  • 如果子类没有,就去查找父类有没有这个方法,如果有就执行(就近原则)

super 关键字

super 关键字用于访问和调用对象父类上的函数,可以调用父类的构造函数,也可以调用父类的普通函数。

// 调用父类的构造函数
class A {
  constructor(x,y) {
    this.x = x;
    this.y = y;
  }
  sum() {
    console.log(this.x + this.y);
  }
}
class B extends A {
  constructor(x,y) {
    super(x,y); // 调用了父类中的构造函数
  }
}
var son = new B(1,2);
son.sum();
// 调用父类的普通函数
class A {
  say() {
    return 'PUJI Design';
  }
}
class B extends A {
  say() {
    console.log( super.say() + ' * Digital * Branding * Coding');
  }
}
var son = new B();
son.say();
// 子类可以继承父类的方法也可以扩展自己的方法
class A {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  sum() {
    console.log(this.x + this.y);
  }
}
class B extends A {
  constructor(x, y) {
    super(x,y);
    this.x = x;
    this.y = y;
  }
  substract() {
    console.log(this.x - this.y);
  }
}
var son = new B(6,3);
son.sum();
son.substract();
  • 当子类有自己的 this 时,super 必须在子类 this 之前调用

注意点

  1. 在 ES6 中,类没有进行变量提升,所以必须先定义类,才能通过 new 实例化对象
  2. 类里有公用的属性和方法,一定要加 this 使用
  3. constructor 里的this指向的是创建的实例对象

正则表达式 (Regular Expression)

视频教程:https://www.bilibili.com/video/BV17y4y1J7L1?p=76

正则表达式在线测试工具:https://c.runoob.com/front-end/854

目标:

  • 能够说出正则表达式的作用;
  • 能够写出简单的正则表达式;
  • 能够使用正则表达式对表单进行验证;
  • 能够使用正则表达式替换内容。

正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。

正则表达式的用途

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

  • 正则表达式具有灵活性、逻辑性和功能性非常强;
  • 可以迅速地用极简单的方式达到字符串的复杂控制;
  • 在实际开发,一般都是直接复制写好的正则表达式,但是要求会使用正则表达式并且根据实际情况修改正则表达式。

创建正则表达式

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

// 通过调用 RegExp 对象的构造函数创建
var 变量名 = new RegExp(/表达式/);
var regexp = new RegExp(/123/);
// 通过字面量创建 (更常用)
var 变量名 = /表达式/;
var regexp = /123/;
  • 正则表达式里的内容不需要加引号,不管是数字型还是字符串型

测试正则表达式

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

regexObj.test(str);
// 示例
var rg = /abc/;
console.log(rg.test('abc')); // true
console.log(rg.test('abcd')); // true
console.log(rg.test('aabcd')); // true
  • regexObj 是写的正则表达式
  • str 需要测试的文本
  • test 中的字符串需要引号
  • 文字中只要其中一部分匹配表达式里的文字,就返回 true

元字符

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

更多字符参考链接
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions

边界符
边界符说明
^表示匹配行首的文本(以谁开始)
$表示匹配行尾的文本(以谁结束)
// 以 abc 开头的字符
var rg = /^abc/;
console.log(rg.test('abc')); // true
console.log(rg.test('abcd')); // true
console.log(rg.test('aabcd')); // false
// 以 abc 结尾的字符
var rg = /abc$/;
console.log(rg.test('abc')); // true
console.log(rg.test('abcaaabc')); // true
console.log(rg.test('aabcd')); // false
// 精确匹配,必须是 abc 字符串
var rg = /^abc$/;
console.log(rg.test('abc')); // true
console.log(rg.test('abcabc')); // false
console.log(rg.test('aabcd')); // false
字符类

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

// 只要包含有 a 或者有 b 或者有 c 都返回 true
var rg = /[abc]/;
console.log(rg.test('ikaie')); // true
console.log(rg.test('bcoej')); // true
console.log(rg.test('eiowloo')); // false
// 只有是 a / b / c 这三个字母才返回 true
var rg = /^[abc]$/;
console.log(rg.test('a')); // true
console.log(rg.test('b')); // true
console.log(rg.test('d')); // false
// 只要是单个英文小写字母都返回 true '-' 表示一个范围
var rg = /^[a-z]$/;
console.log(rg.test('a')); // true
console.log(rg.test('f')); // true
console.log(rg.test('B')); // false
// 只要是单个英文字母(含大小写)或数字或_或-都返回 true
var rg = /^[a-zA-Z0-9_-]$/;
console.log(rg.test('a')); // true
console.log(rg.test('D')); // true
console.log(rg.test('code')); // false
console.log(rg.test('6')); // true
// 中括号内有^ 表示取反,不能包含里面的内容
var rg = /^[^a-zA-Z0-9_-]$/;
console.log(rg.test('a')); // false
console.log(rg.test('D')); // false
console.log(rg.test('code')); // false
console.log(rg.test('*')); // true
量词符

用于设定某个模式出现的次数

量词说明
*重复零次或更多次
+重复一次或更多次
?重复零次或一次
{n}重复n次
{n,}重复n次或更多次
{n,m}重复n到m次
// 重复0次或以上
var rg = /^a*$/;
console.log(rg.test('')); // true
console.log(rg.test('aaa')); // true
console.log(rg.test('aaabaaa')); // false

括号的使用

方括号 [] 表示字符集合,匹配方括号中的任意字符

var rg = /[abc]/; // 只要包含有 a / b / c 任意一个

花括号 {} 为量词符,表示重复次数

rg = /^abc{3}$/; // 只是让花括号前面一个字符重复三次 abccc

圆括号 () 表示优先级

rg = /^(abc){3}$/; // 让 abc 重复三次 abcabcabc

| 表示或

预定义类

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

预定义类说明
\d匹配0-9之间的任一数字,相当于[0-9]
\D匹配所有0-9以外的字符,相当于[^0-9]
\w匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]
\W除所有字母、数字和下划线以外的字符,相当于[^A-Za-z0-9_]
\s匹配空格(包含换行符、制表符、空格符等),相当于[\t\r\n\v\f]
\S匹配非空格的字符,相当于[^\t\r\n\v\f]

正则表达式参数

/表达式/[switch];
/激情|刺激/g

switch 也称为修饰符,按照什么样的模式来匹配,有三种值

  • g: 全局匹配,表示可获取整段文字中所有符合的内容。
  • i: 忽略大小写
  • gi: 全局匹配 + 忽略大小写

replace() 替换

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

stringObject.replace(regexp/substr, replacement)
// 替换字符串
var str = 'PUJI Design';
var newStr = str.replace('Design', 'Lab');
console.log(newStr); // PUJI Lab
// 正则表达式
// 替换字符串
var str = 'PUJI Design';
var newStr = str.replace(/Design/gi, 'Lab');
console.log(newStr); // PUJI Lab

内置对象

目标

  • 能够说出什么是内置对象
  • 能够根据文档查询指定API的使用方法
  • 能够使用 Math 对象的常用方法
  • 能够使用 Date 对象的常用方法
  • 能够使用 Array 对象的常用方法
  • 能够使用 String 对象的常用方法

概述

内置对象就是 JavaScript 语言自带的一些对象,这些对象供开发者使用,并提供了一些常用的或是最基本而必要的功能(属性和方法)。常用的内置对象包含 Math, Date, Array, String等

查阅文档

可以通过 MDN / W3C 来查询,Mozilla 开发者网络(MDN) 提供了有关开放网络技术 (Open Web)的信息,包括 HTML、CSS和万维网及HTML5应用的API。

https://developer.mozilla.org/zh-CN/

Math 数学对象

Math 是一个内置对象,它拥有一些数学常数属性和数学函数方法。Math 不是一个函数对象。

封装自己的数学对象

var myMath = {
    PI: 3.1415926,
    max: function() {
        var max = arguments[0];
        for (i = 1; i < arguments.length; i++) {
            if (arguments[i] > max) {
                max = arguments[i];
            }
        }
        return max;
    },
    min: function() {
        var min = arguments[0];
        for (i = 1; i < arguments.length; i++) {
            if (arguments[i] < min) {
                min = arguments[i];
            }
        }
        return min;
    }
}
console.log(myMath.PI);
console.log(myMath.max(1, 6, 9, 36, -15));
console.log(myMath.min(1, 6, 9, 36, -15));

日期对象

是一个构造函数,使用new来调用创建我们的日期对象

var date = new Date();
  • 使用Date如果没有参数,返回当前系统的当前时间
  • 参数常用写法,数字型 2022, 03, 03 或者是字符串型 ‘2022-03-03 12:36:18’
var date = new Date('2022-03-03 12:36:18');

扩展内置对象

视频教程:https://www.bilibili.com/video/BV17y4y1J7L1?p=33&spm_id_from=pageDriver

可以通过原型对象,对原来的内置对象进行扩展自定义的方法,比如给数组增加自定义求偶数和的功能。

基本包装类型

在 JavaScript 上提供了三个特殊的引用类型,分别是 String / Number / Boolean。把简单数据类型包装为复杂数据类型就是基本包装类型,这样基本数据类型就有了属性和方法。

// 示例
var str = 'PUJI';
console.log(str.length);
// js的处理过程
var temp = new String('PUJI'); // 新建一个对象
str = temp; // 把临时变量的值赋予 str
temp = null; //销毁临时变量

预解析(重要)

目标

  • 能够知道解析器运行js分为哪两步
  • 能够说出变量提升的步骤和运行过程
  • 能够说出函数提升的步走和运行过程

什么是预解析

// 示例1
console.log(num); // undefined
var num = 10;
// 示例2 (成功调用)
fn();
function fn() {
  console.log(1);
}
// 示例3 (报错)
fn();
var fun = function() {
  console.log(1);
}

JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器在运行 JavaScript 代码的时候分为两步:预解析和代码执行。

  1. 预解析:JavaScript 引擎会把 js 里所有的 var 还有 function 提升到当前作用域的最前面
  2. 预解析分为变量预解析(变量提升)和函数预解析(函数提升)
  3. 变量提升,就是把所有的变量声明提升到当前的作用与前面,不提升赋值。
  4. 函数提升,就是把所有的函数声明与函数体提升到当前作用域的前面,但不做调用。
  5. 代码执行:按照代码书写的顺序从上往下执行

按照预解析的过程来分析,我们可以把上面的代码的流程按照解析过程来进行顺序分析,相当于代码按照以下顺序进行执行

// 示例1 (由于在执行语句前只声明了变量,但没赋值)
var num;
console.log(num); // undefined
num = 10;
// 示例2 (成功调用)
function fn() {
  console.log(1);
}
fn();
// 示例3 (报错)
var fun; (由于在调用语句前只声明了变量,但没赋值,fn属于变量而非函数)
fn();
fun = function() {
  console.log(1);
}

严格模式 (Strict mode)

JavaScript 除了提供正常模式外,还提供了严格模式。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。

  • 严格模式对正常的 JavaScript 语法的一些不合理、不严谨之处,减少了一些怪异行为;
  • 消除代码运行的一些不安全之处,保证代码运行的安全;
  • 提高编译效率,增加运行速度;
  • 禁用了在 ECMAScript 的未来版本中可能会定义的一些语法不可用于变量名。

开启严格模式

视频教程:https://www.bilibili.com/video/BV17y4y1J7L1?p=59
更多规则参考链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode

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

为脚本开启严格模式

为整个脚本文件开启严格模式,只需在所有语句之前放一个特定语句 “use strict” 或 ‘use strict’。

// 开启严格模式
'use strict';

为函数开启严格模式

function fn() {
  'use strict';
  ...
}

变量规定

  • 变量必须先声明然后再使用;
  • 严禁删除已经声明过的变量。

this 指向

  • 全局作用域中定义的函数的 this 是 undefined 而不再是 window;
  • 构造函数如果不加 new 进行调用,this 会报错,new 实例化的构造函数指向创建的对象实例;
  • 定时器的 this 还是指向 window;
  • 事件、对象里的 this 还是指向调用者。

函数变化

  • 函数的形参名称不可重名;
  • 函数声明必须在顶层,不允许在非函数的代码块内声明函数 (如括号内,if,for 等)。

参考链接

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript

开发者社区

https://www.bilibili.com/video/BV17y4y1J7L1/?spm_id_from=333.788.recommend_more_video.0

bilibili 视频教程
https://www.bilibili.com/video/BV11p4y1C7K9?spm_id_from=333.999.0.0

ES6 兼容性
http://kangax.github.io/compat-table/es6/

Bilibili ES6 新特性教程
https://www.bilibili.com/video/BV1uK411H7on?spm_id_from=333.999.0.0

内容目录

PUJI Design 朴及设计 (c) 2024. 沪ICP备17052229号