JavaScript
什么是
前端三大语言
HTML
专门编写网页内容的语言
CSS
专门编写网页样式的语言
JS
为什么
仅用HTML和CSS编写的网页,只能看不能用!
有些不需要服务器端数据,就可执行的功能,就不应该反复与服务器交互,频繁让用户等待。
发展史
专门编写客户端交互行为的语言
交互(IPO)
1. 用户输入
2. 程序接受输入,处理数据
3. 输出处理结果
何时
今后,凡是HTML和CSS编写的网页,必须用js添加交互行为后,才能让用户使用
今后,不需要服务器数据,就可执行的操作,只要在客户端用JavaScript实现即可,不用频繁与服务器交互
如何使用JavaScript
在何处编写JavaScript
2处
1. HTML网页中的script标签内
问题:
无法重用
程序的准则:
DRY
尽量:
一次定义,处处使用,一次修改,处处生效
解决
另见:
2. 在独立的js文件中编写JavaScript
再用script标签引入网页中
<script src="url"></script>
如何运行
脚本解释引擎
解析并执行js程序的小软件
如何
1. 浏览器已经自带脚本解释引擎
浏览器包含两个小软件
内容排版引擎
解析HTML和CSS的程序
效率高
脚本解释引擎
解析js并运行js程序的小程序
效率比排版引擎略低
2. 独立安装
Node.js
JavaScript语言基本语法
1. 区分大小写
2. 字符串必须用引号包裹, 但单双引号都行
特殊
如果字符串内又包含引号冲突, 只要内外使用不同的引号区分即可
3. 每句话结尾必须用分号结束
4. 注释
//单行注释
/* 多行注释 */
调试JavaScript
只要想要的效果没出来,就是出错了
只要出错,先开F12控制台(Console)
Console中显示错误的原因,和出错位置
JavaScript能做什么
客户端表单验证
客户端数据计算
客户端动画效果和网页游戏
总之: js可对网页中的一切内容执行一切想要的操作——只有想不到,没有做不到
常用输出
向网页中输出内容
document.write("HTML片段或文字")
问题: 影响网页中现有结构和内容
弹出警告框
alert("提示内容...")
问题:
1. 样式写死,不可定制修改
2. 模态,阻碍用户操作
向控制台输出:
console.log("输出内容...")
今后,凡是调试程序,查看输出结果,都要在控制台输出
控制台的使用
1. 单行代码按回车直接执行
2. 切换出之前执行过的代码记录,微调
按键盘上下按键切换
3. 清屏
点控制台左上角圆形叉号
4. 仅换行输入,不执行
shift+enter
原生js
不依赖于第三方文件,仅依靠浏览器就可直接执行的代码
包含3大部分
ECMAScript
规定了js语言核心语法的标准
3 5 6(2015)
JavaScript
网景(Mozilla火狐)参照ES标准实现的JavaScript语言版本
JScript
微软参照ES标准实现的JavaScript语言版本
DOM
专门操作网页内容的API
比如:
document.write(...)
向网页内容中输出一行话
BOM
专门操作浏览器窗口的API
比如:
alert,prompt
弹出浏览器的提示框
变量
什么是
内存中存储*一个*数据的存储空间,再起一个名字
为什么
重用一个数据
何时
只要一个数据可能被反复使用时,都要保存在变量中
如何
声明
什么是
在内存中创建一块存储空间,再起一个名字
何时
任何变量在使用前,必须先声明
如何
var 变量名;
变量名
1. 字母,数字,下划线和$组成,但不能以数字开头
2. 不能使用保留字
3. 见名知义
4. 驼峰命名
默认值
仅声明,未赋值的变量,默认值都是undefined
简写
var 变量1, 变量2, ...;
赋值
变量名=值
将等号右边的值保存到等号左边的变量中
只有=才能赋值
=不是追加,而是完全替换!
覆盖原值
特殊
如果给未声明的变量强行赋值
普通模式
不会报错!
会自动在全局创建该变量
全局污染
ES5
严格模式
什么是
比普通js运行模式要求更严格的运行机制
要求1:
禁止给未声明的变量赋值
报错!
变量未定义
如何启用:
在当前代码段的顶部先插入"use strict";
总结
禁止使用!
简写
问题:
变量的默认值undefined是造成最多错误的根源
强烈建议
声明变量同时初始化变量的值
var 变量名=值
特殊
同时声明并初始化多个变量
var 变量1=值1, 变量2=值2, ...;
鄙视
var a , b=2;
a: undefined
b: 2
取值
任何情况下使用变量名等效于复制出变量中的值使用
尝试从未声明的变量中取值都会报错!
ReferenceError
常量
什么是
一旦创建,值不能改变的量
何时
只要程序中的一个值,任何情况下都不能改变
如何
const 常量名=值;
强调:
常量名必须全大写
创建常量时必须立刻赋值
强行修改常量的值:
在最新的Chrome浏览器中,已经内置了对常量赋值的检查
在ES3标准中,普通运行模式下,强行给常量赋值
不报错!
也不让修改
静默失败
解决
启用严格模式
要求:
将所有静默失败升级为错误!
数据类型
什么是
数据在内存中的存储格式
为什么
不同类型的数据,可执行的操作不一样
不同的操作,要求专门的存储结构支持
包括
原始类型
值直接保存在变量本地的数据类型
number string boolean null undefined
引用类型
值无法保存在变量本地的复杂数据类型
如果一个复杂的数据结构,同时保存多个数据,就不能直接保存在变量中
存储在window之外的一块独立存储空间
独立存储空间中可同时存储多个数据,拥有唯一的地址
变量名仅保存存储空间的地址
number
什么是
专门存储数字(即包含整数,也包含小数)的类型
何时
如果一个数值可能需要比较大小和进行数学计算时
如何
程序中只要不加引号的数字,自动就是number类型
底层都是二进制存储
n.toString(2)
存储
整数占4字节
浮点数占8字节
存储空间和数值大小无关
string
什么是
专门存储一串用于显示的文字
何时
记录一串文字
不用做比较和计算时
如何
凡是加引号的一串字符直接量称为字符串类型
存储
unicode
对全球主要语言的每个字编一个号
为什么
计算机不认识字符,只认识数字
范围:
"0"~"9"
48~57
"A"~"Z"
65~90
"a"~"z"
97~122
汉字
"\u4e0"~"\u9fa5"
每个字母/数字字符,占1字节
每个汉字,占2字节
其他
utf-8
字母数字1字节
汉字3字节
GBK
字母数字1字节
汉字2字节
boolean
什么是
专门表示判断结果的
何时
只要作为判断条件的结论时
如何
只有两个值
true/false
不加引号
undefined
空
专门由程序自动为一个变量赋初值
程序员很少主动使用
null
空
专门给程序员用于手动清空一个变量之用
数据类型转换
弱类型语言
1. 声明变量时无需提前规定变量中存储数据的类型
2. 一个变量先后可保存不同类型的数据
3. js会根据自身的需要,隐式转化数据的类型
什么是
将数据从一种类型转化为另一种类型
何时
只要数据类型不是想要的
2大类
隐式转换
无需程序员干预,程序自动完成的类型转换
何时
如果给定的数据类型和js运算要求的数据类型不相符,都会首先自动类型转换为规定的类型,再运行
强制转换
什么是
程序员主动调用转化函数实现的类型转换
何时
只要隐式转换的结果不是想要的
3种情况
转为number
2种
1. 将纯数字组成的字符串和bool类型转为number
Number(x)
Number(true)->1 Number(false)->0
Number(null)->0 , Number("")->0
Number(undefined)->NaN
其实也可以转字符串
只能转纯数字组成的字符串
其实,所有隐式转化都是自动调用Number()
2. 将字符串转为number
parseFloat(str)
将str转为number类型的浮点数(小数)
原理
从开头开始,依次读取每个字符
只要碰到数字和小数点就保留
直到碰到第一个不是数字和小数点的字符就不再继续读取
总结:
可保留小数
仅认识第一个小数点
去掉结尾的非数字字符
无法去掉开头的非数字字符
何时
只要希望去掉字符串结尾的单位时
parseInt(str)
将str转为number类型的整数
原理
和parseFloat完全一样
只是不认识小数点
总结
去掉小数
去掉结尾的非数字字符
何时
只要希望去掉结尾的单位,且同时舍弃小数部分时
特殊:
parseFloat(null/true/false)
NaN
先将null/true/false隐式转化为字符串,再按照原理转为数字
总结:
如果将字符串转数字
首选parseFloat
除非确定要舍弃小数部分采用parseInt
如果将非字符串转数字
首选Number
很少主动使用
NaN
Not a Number
js中number类型的一个特殊值
代表一切不是数字的值
只要将其它类型转为数字时,无法正常转换,都转为NaN
特点:
1. 参与任何计算,结果依然是NaN
2. 不大于,不小于,不等于任何值
转为string
x.toString()
问题:
x不能是null和undefined
解决
另见:
String(x)
万能
其实隐式转换都是自动调用String()
转为boolean
Boolean(x)
规则
只有五个值被转为false
0, null, NaN, undefined, ""
其余任何值都转为true
运算符和表达式
概念
程序
人的想法在计算机中的执行
运算符
程序中模拟人的想法的特殊符号
表达式
由数据,变量和运算符组成的一条程序的语句
执行过程
从左向右,依次执行
每读到一个变量就将变量换成变量中的值
包括
算数计算
+ - * / %
%
被除数/除数,不取商,取除不尽的余数部分
何时:
1. 取余数
2. 判断能否整除
比如
判断奇偶数
判断闰年
隐式转换
默认
一切转为number,再算数计算
特殊
+运算中只要有一个字符串
则全部转为字符串
+计算改为字符串拼接
问题:
凡是从页面上获得的数据都是字符串
解决:
做算数计算前,必须用parseFloat()强转为数字类型
关系运算
做比较,做判断
> < >= <= != ==
返回值
只能是true/false
隐式转换
默认
一切都转为number,再做比较
特殊
两个string类型做比较
不再转为number
而是按位PK每个字符的unicode号
只要一位字符能比较出结果,就不再继续
null和undefined
问题
用==无法区分null和undefined
普通关系运算会将undefined先隐式转为null再做比较
解决
===
先要类型相同,然后再值相等
不带隐式转换的==比较
强烈建议使用===代替==
!==
不带隐式转换的!=比较
强烈建议使用!==代替!=
NaN
问题
无法判断是不是NaN
NaN不等于,不大于,不小于一切
因为NaN表示所有不是数字的内容
是一个范围,不表示一个具体值
做比较无任何意义
解决
isNaN(num)
专门判断num是不是NaN
经常反用
只要不是NaN,一定是数字
!isNaN(num)
专门用来判断num是不是有效的数字
两个对象做==比较
不再做任何转换,而是比较两个对象的*地址*是否相同
判断两个变量是否引用同一个对象
比如
[]==[]
false
逻辑运算
将多个关系运算,组合起来,综合得出最终的结论
返回true/false
3种
1. 逻辑与&&
而且
条件1&&条件2
必须同时满足条件1和条件2才返回true
只要一个条件不满足,都返回false
2. 逻辑或
或/要么
条件1||条件2
只要满足条件1或条件2中任意一个条件就返回true
除非所有条件都为false时,才为false
3. 逻辑非
不/没有
!条件
颠倒条件的判断结果
隐式转换
默认
每个条件都转为bool类型,再联合判断
短路逻辑
在逻辑运算中,如果前一个条件已经可以得出最终的结论,则后续条件不再执行
&&
如果前一个条件为false,则后续条件不再执行
如果前一个条件为true,则后续条件才可能执行
||
如果前一个条件为true,则后续条件不再执行
如果前一个条线为false, 则后续条件才有必要执行
利用短路
利用&&的短路
1. 实现简单分支
一个条件一件事,满足条件才执行,不满足就不执行
条件&&(操作)
利用||的短路
2. 定义默认值
值1||默认值
优先使用值1
如果值1转为bool后为false,才使用默认值作为备用
位运算
左移和右移
左移
m<<n
将m的二进制数左移n位
相当于m* 2的n次方
右移
m>>n
将m的二进制数右移n位
相当于m/ 2的n次方
下取整
m>>>0
m|0
m^0
不声明第三个变量,交换两变量的值
方法一:
a^=b; b^=a; a^=b;
方法二:
a+=b; b=a-b; a-=b;
问题: 只能交换数字类型的数据
方法三:
a=[b,b=a][0]
赋值运算
赋值运算也有返回值
返回保存到等号左边的变量中的新值
扩展赋值运算
一种简写
包括
a+=b
累加
a=a+b
如果只是+1
更简化
递增
a++
a-=b
a=a-b
如果只是-1
更简化
递减
a--
a*=b
a/=b
a%=b
何时
只要先取出变量值,计算后,想再保存回原变量时
递增/递减1
++a vs a++
单独使用
没有差别
如果嵌入其它表达式中
相同
变量a中的值,一定都会被+1
不同
返回值
++a,返回+1后的新值
a++,返回+1前的旧值
运算符优先级
20 圆括号 ( x+y )*3
19 成员访问 arr.length 需计算的成员访问 arr[ i+1 ] new (带参数列表) new Array( 1,2,3 ) 函数调用 fun ( 1,2,3 )
18 new (无参数列表) new Array
17 后置递增(运算符在后) n++ 后置递减(运算符在后) n --
16 逻辑非 !bool 按位非 ~ … 正号 +1 负号 -1 前置递增 ++n 前置递减 --n typeof typeof n void void … delete delete lilei.sname await await new Promise( ... )
15 幂 2**3
14 乘法 2*3 除法 6 / 2 取模 6 % 2
13 加法 a+b 减法 a-b
12 按位左移 2<<3 按位右移 8 >>3 无符号右移 8>>> 3
11 小于 a< b 小于等于 a<=b 大于 a>b 大于等于 a>= b in math in ym instanceof arr instanceof Array
10 等号 a == b 非等号 a != b 全等号 a === b 非全等号 a !== b
9 按位与 … & …
8 按位异或 … ^ …
7 按位或 … | …
6 逻辑与 xxx && xxx
5 逻辑或 xxx || xxx
4 条件运算符 sex==1 ? "男" : "女"
3 赋值 var a = 5 a+=1 a-= 1 a*= 3 a/= 3 a%=2 … <<= … … >>= … … >>>= … … &= … … ^= … … |= …
2 yield
1 展开运算符 ...arr
0 逗号 a+=b, b=a-b, a-=b
函数
什么是
封装一项任务步骤清单的代码段,再起一个名字
函数也是引用类型的对象
函数名其实只是保存函数对象地址的变量
为什么
代码重用
何时
只要一项任务可能被反复使用,都要先定义在函数中,再反复调用函数
如何
声明函数
function 函数名(参数列表){ 函数体; return 返回值; }
参数
函数执行过程中必须的数据
为什么
有些功能必须某些外来数据才能正常执行
何时
只要函数本身需要某些数据才能正常执行时
如何
声明
函数名后的圆括号中, 用逗号分隔每个参数名
不用加var
访问
在函数内,参数的用法和普通变量完全一样
返回值
函数执行的结果
何时
只要调用者需要获得函数的执行结果时
如何
函数结尾 return 值
向外部抛出函数处理结果的主要手段
调用
让引擎找到函数,按照函数的步骤清单执行程序
如何
var 返回值=函数名(参数值列表)
如果函数定义时,定义了参数列表,调用时必须传入参数值
传入的参数值的顺序和个数必须和函数定义中的参数列表一致
如果函数定义时,定义了返回值,调用时可以用变量接收函数的返回值
作用域(scope)
什么是
一个变量的可用范围
为什么
避免不同范围的变量间互相污染
包含:
2种
全局作用域
window
全局变量
在函数外声明的不属于任何函数的变量
特点
随处可用
可反复使用
何时:
只要一个变量希望反复使用或跨多个函数随处可用时
函数作用域
函数内部
局部变量
包含2种
在函数内用var声明的变量
参数变量也是局部变量
特点
仅函数内可用
不可重用
何时:
如果希望一个变量仅在函数内可用,不希望污染函数外部时
使用顺序
优先在函数作用域中找局部变量使用
如果局部没有,才去全局找
如果全局没有,才报错
声明提前
在程序正式执行前,先将所有var声明的变量和function声明的函数提前到当前作用域的顶部,集中创建
赋值留在原地
问题
破坏了程序执行的顺序
避免
1. 尽量将变量或函数的声明集中在当前作用域顶部创建
2. 用let代替var
3. 用var 函数名=function(...){...}代替function 函数名(...){...}
按值传递byValue
在两变量间赋值时,或将变量作为参数传入函数时,仅将原变量中的值复制一个副本给对方
影响
如果传递的是原始类型的值,在函数中修改新变量,不会影响原变量
如果传递的是引用类型的对象,在函数中用新变量修改对象,等效于直接修改原对象
全局函数
ES标准中规定的,不需要任何前缀.就可直接调用的函数
比如:
Number() String() Boolean() isNaN() parseFloat/parseInt()
encodeURI/decodeURI()
encodeURIComponent()/decodeURIComponent()
eval()
反例
document.write() console.log();
alert() prompt()
分支结构
让程序根据不同的条件,执行不同的任务
包括
1. 一个条件一件事,满足就做,不满足就不做
如果操作简单
条件&&(操作1,操作2,...)
如果操作复杂
if(条件){ 操作 }
2. 一个条件2件事,二选一执行
如果操作复杂
if(条件){ 操作1 }else{ 操作2 }
如果操作简单
三目/三元/条件
条件?操作1:操作2;
如果根据不同条件选择不同的值
三目
条件?值1:值2
3. 多个条件多件事,多选一执行
如果操作复杂
if(条件1){ 操作1 }else if(条件2){ 操作2 }else if(...){ ... }else{ 默认操作 }
最后一个else不是必须
如果操作简单
条件1?操作1: 条件2?操作2: ... ? ... : 默认操作
最后必须有一个默认操作
如果只是根据不同条件返回不同的值时
条件1?值1: 条件2?值2: ... ? ... : 默认值
默认值不能省略!
特殊
如果所有条件都是等于比较时
switch(表达式){ case 值1: 操作1; break; case 值2: 操作2; break; case ... : ... ; break; default: 默认操作 }
原理:
先计算表达式的值
再用表达式的值和每个case做===比较
只要表达式的值和某个case的值全等,就进入执行该case下的操作
强调:
表达式的值和case的值做全等比较,意味着首先类型必须相同
每个case其实仅是一个入口而已
问题
默认
一但进入一个case开始执行,则会连续触发之后所有case和default的操作
解决
在每个case之间加break
break退出当前结构
循环结构
让程序反复执行一段相同代码
循环三要素
循环条件
控制循环可以继续反复执行的条件
每执行完一次重复的操作,都要重新判断一次循环条件是否满足
一旦循环条件不再满足,则退出循环,不再反复执行
循环变量
循环条件中用作比较和判断的变量
都要考虑
从几开始,到几结束,每次增/减几
通常都会向着不满足循环条件的趋势不断变化
循环体
循环反复执行的代码段
三种
while
声明并初始化循环变量 while(循环条件){ 循环体; 修改循环变量; }
何时
循环变量的变化没有规律时
do while
声明循环变量 do{ 循环体; 修改循环变量; }while(循环条件);
vs while
如果第一次循环条件都满足
二者效果完全相同
如果第一次循环条件不满足
while是一次都不执行
do while至少可以执行一次
何时
即使第一次条件就不满足,也希望至少能执行一次时
for
for(声明循环变量; 循环条件; 修改循环变量){ 循环体; }
何时
循环变量的变化是有规律的
简写
1. 声明循环变量部分,可同时声明并初始化多个变量,用逗号分隔
问题: 循环内声明的变量,循环外是否可用
答: 可用
因为js没有块级作用域
vs Java
有块级作用域
2. 修改循环变量部分,可同时执行多个短小的操作,用逗号分隔
不能修改原程序的执行顺序
结尾的分号不能省
了解
其实分支结构和循环结构中,如果if/else/else if/for/while之后只有一句话,可省略{}
禁止使用!
死循环
循环条件永远为true的不能自己退出的循环
while(true)
for(;;)
break和continue
break
退出当前结构,不再循环
continue
仅跳过本轮循环,依然继续执行下一轮
退出循环
2种办法
1. 用循环条件控制退出
优雅
难度高
2. 用死循环+break方式退出
野蛮
简单
数组
什么是
内存中连续存储多个数据的存储空间,再起一个名字
为什么
连续存储的一组数据,可极大提高程序的执行效率
便于维护和查找
何时
只要存储多个数据都要用数组
如何
创建
1. 创建空数组
何时
如果创建数组时,暂时不知道数组的内容
数组直接量
var arr=[]
用new
var arr=new Array();
2. 创建数组同时初始化数组元素
何时
如果创建数组时,已经知道数组内容
数组直接量
var arr=[值1, 值2,...]
用new
var arr=new Array(值1,值2,...);
3. 创建n个空元素的数组
何时
如果创建数组时只知道数组的元素个数,暂时不知道内容时
用new
var arr=new Array(n)
访问
元素
保存在数组中的一个数据
下标
数组中唯一标识元素存储位置的序号
默认从0开始,依次递增,连续不重复,到length-1结束
arr[i]
单个元素的用法和单个变量完全一样
数组其实是一组变量的集合,再起一个统一的变量名
三个不限制
不限制元素的数据类型
不限制下标越界
取值
不报错,返回undefined
赋值
不报错,自动在指定位置创建新元素
稀疏数组
下标不连续的数组
自动将length调整到最大下标+1
不限制元素个数
可随时在任意位置添加新元素
添加新元素后,都会自动改变length属性为最大数字下标+1
.length属性
规定了数组理论上的元素个数
始终等于最大下标+1
自动维护
固定套路
获取最后一个元素
arr[arr.length-1]
获取倒数第n个元素
arr[arr.length-n]
末尾追加一个新元素
arr[arr.length]=值
缩容
删除末尾一个元素
arr.length--
删除末尾n个元素
arr.length-=n
清空数组
arr.length=0
数组是引用类型
不直接存储在变量本地
一个变量只能存1个值
实际存储在变量之外
每个数组都有一个唯一的地址值
变量中只保存数组的地址值
也称变量引用着数组
按值传递
两变量间赋值或将变量传给函数参数时,其实只是将原变量中的值复制一个副本给对方
原始类型
修改新变量,不影响原变量
引用类型
通过新变量修改数组,同样会影响原变量
为什么
将原变量中的地址值复制给新变量
结果
两个变量引用同一个数组
遍历
依次取出数组中每个元素的值,执行相同的操作
何时
只要对数组中每个元素执行相同操作时
如何
索引数组
for(var i=0;i<arr.length;i++){ arr[i] //当前数组元素 }
关联数组
什么是
下标都是数字的数组,称为索引数组
可自定义下标名称的数组,称为关联数组
为什么
索引数组的数字下标没有意义,只能通过遍历查找指定的元素
查找速度受数组元素个数和元素位置的影响
何时
希望通过下标名称快速查找某个元素时
无需遍历
不受元素个数和元素存储位置的影响
如何
创建
2步
创建空数组
var hash=[]
向数组中添加新元素
hash["下标名(key)"]=值(value)
访问
hash["下标名(key)"]
用法同访问索引数组中的元素
特点
.length属性始终为0
.length属性只能统计数字下标的房间,不能统计自定义下标的房间。
无法使用索引数组的API
遍历
for(var key in hash){ key //仅获取当前下标名称 hash[key] //获取当前元素值 }
in 依次取出关联数组中每个下标名称保存在变量key
固定套路
仅获取hash中的所有key
var keys=[]; var i=0; for(keys[i++] in hash); //结束后: keys中保存了hash的所有key
数组API
转字符串
String(arr)
将arr中每个元素转为字符串,用逗号链接
拍照
arr.join("连接符")
将arr中每个元素转为字符串,可自定义连接符
固定套路
无缝拼接
arr.join("")
判断空数组
arr.join("")===""
动态生成页面元素
var html="<ANY>"+arr.join("</ANY><ANY>")+"</ANY>"
elem.innerHTML=html;
拼接和选取
强调: 都无权修改原数组,只能返回新数组,必须用变量接住返回值
拼接
var newArr=arr1.concat(值1,值2,arr2,....)
将值1,值2,arr2中的每个元素,拼接到arr1结尾
强调:
可打散数组类型参数
选取
var subArr=arr.slice(starti,endi+1);
选取arr中starti位置到endi位置的元素,组成新数组返回
强调:
凡是两个参数都是下标的API
含头不含尾
简写
负数参数
倒数第n个
本质:
自动执行length-n
省略第二个参数
从starti一直选取到结尾
省略全部两个参数
复制整个数组
用途
将类数组对象转化为数组对象
Array.prototype.slice.call(arguments)
相当于
arguments.slice()
固定套路:
获得i位置开始的n个元素
arr.slice(i,i+n)
修改数组
删除元素
arr.splice(starti,n)
删除arr中starti位置开始的n个元素
强调:
直接修改原数组
不用考虑含头不含尾
简写:
支持负数参数,表示倒数第n个
省略第二个参数
删除starti位置后所有元素
其实有返回值
返回被删除的元素组成的临时数组
var deletes=arr.splice(starti,n)
插入元素
arr.splice(starti,0,值1,值2,...)
在arr中starti位置插入新值,原starti位置的值及其之后的值被向后顺移
强调:
不支持打散数组类型参数
如果插入子数组,会变成二维数组
替换
arr.splice(starti,n,值1,值2...)
先删除arr中starti位置的n个元素,再在starti位置插入新元素
强调:
删除的元素个数不一定和插入的元素个数一致
固定套路
广告轮播
移除开头的n个元素拼到结尾
imgs=imgs.concat(imgs.splice(0,n))
移除结尾的n个元素拼到开头
imgs=imgs.splice(-n).concat(imgs)
翻转
arr.reverse()
排序
手写排序:
冒泡,快速,插入
冒泡
依次比较相邻两数,如果前数>后数,就交换两数位置
for(var r=1;r<arr.length;r++){ for(var i=0;i<arr.length-r;i++){ if(arr[i]>arr[i+1]){ arr[i]=[arr[i+1],arr[i+1]=arr[i]][0]; } } }
arr.sort()
将arr中每个元素转为字符串,再按字符串升序排列
问题:
只能按字符串升序排列
解决:
自定义比较器函数
2步:
1. 定义比较器函数
专门比较两个值大小的函数
2个要求
1. 两个参数a,b
2. 返回值
a>b,返回正数
a<b,返回负数
a==b,返回0
比如
最简单的数字升序比较器
function compare(a,b){return a-b;}
2. 将比较器函数作为对象传入sort方法
arr.sort(compare)
强调:
回调函数
将一个函数,作为对象传入另一个函数内,被另一个函数使用
传入回调函数时,不要加()
因为不是立刻调用!也不是只调用一次!而是交给别人去调用
其实都会简写为:
arr.sort(function(a,b){return a-b;})
ES6
arr.sort((a,b)=>a-b)
降序排列
颠倒比较器函数返回值的正负号
比如:
数字降序比较器
function compare(a,b){return b-a;}
栈和队列
何时
只要按照顺序使用数组中的元素时
js中没有专门的栈和队列的类型,都是用普通数组模拟的
栈stack
一端封闭只能从另一端进出的数组
FILO
何时
希望始终使用最新进入数组的元素时
如何
结尾出入栈
入
arr.push(值)
arr[arr.length]=值
出
var last=arr.pop()
开头出入栈
入
arr.unshift(值)
arr.splice(0,0,值)
出
var first=arr.shift()
强调:
开头入栈和结尾入栈的顺序是相反的
队列queue
只能从结尾进入,从开头出的数组
FIFO
何时
只要按照先来后到的顺序使用数组元素时
如何
结尾入
另见:
开头出
另见:
二维数组
什么是
数组中的元素内容,又是一个子数组
何时
1. 保存横行竖列的二维数据
2. 对大的数组中的元素,再进行细分类
如何
创建
1. 先创建空数组,再赋值
var arr=[];
arr[i]=[值1,值2,...]
2. 在创建数组同时,初始化子数组
var arr=[ [值1,值2,...], [值1,值2,...], ]
访问
arr[r][c]
用法和普通数组元素的用法完全一样
强调:
任何情况下行下标r不能越界
报错!
遍历:
for(var r=0;r<arr.length;r++){ for(var c=0;c<arr[r].length;c++){ arr[r][c] //当前元素 } }
String
什么是
多个字符组成的只读字符数组
vs 数组
相同
下标
.length
for遍历
slice()
不同
两者类型不同
API不通用
API
强调
所有String API都无权修改原字符串,只能返回新字符串
大小写转换
何时
只要不区分大小写时,都要先转换为一致的大小写,再比较
str.toUpperCase()
str.toLowerCase()
获取指定位置字符
var char=str.charAt(i)
str[i]
var unicode=str.charCodeAt(i)
简写
省略i, 默认为0
var char=String.fromCharCode(unicode)
选取子字符串
var subStr=str.slice(starti,endi+1)
简写:
同数组的slice
str.substring(starti,endi+1)
简写
另见:
不支持负数参数
变通:
str.length-n
str.substr(starti,n)
不考虑含头不含尾
str.substring(starti,starti+n)
查找关键词
1. 查找一个固定的关键词出现的位置
var i=str.indexOf("关键词",fromi)
在str中从fromi位置开始查找下一个关键词的位置
返回值
返回找到的关键词所在的位置
如果找不到返回-1
固定套路
查找所有关键词出现的位置
var i=-1; while((i=str.indexOf("关键词",i+1))!=-1){ i //本次找到的关键词位置 }
简写:
省略fromi,默认为0
优:
可以指定开始位置,可以找所有
缺:
不支持正则,一次只能找一种关键词
专门查找最后一个关键词的位置
var i=str.lastIndexOf("关键词")
2. 判断是否包含符合规则的关键词
var i=str.search(/正则表达式/i)
如果返回-1,说明不包含,如果返回不是-1,说明包含
永远只找第一个关键词的位置
忽略大小写
/正则表达式/i
强调
不支持g
优:
支持正则
缺:
不能设置开始查找位置,只能找第一个,不能找所有
只能返回位置,不能返回关键词内容
3. 获取关键词的内容
只获得一个关键词的内容
a. var arr=str.match(/正则/i);
b. 在str中查找第一个符合正则要求的敏感词的内容和位置
c. 返回值:
1). 如果找到,返回一个数组,包含两个元素: arr: [ 0: "敏感词内容", "index":"位置" ]
2). 如果没找到,返回null
获得所有关键词的内容
var kwards=str.match(/正则表达式/ig)
强调:
省略g,只找第1个
加g,才找所有
返回包含所有关键词的数组
如果没找到,返回null
如果一个函数可能返回null,都要先验证,再使用结果
优:
获得所有关键词的内容
缺:
无法返回每个关键词的位置
4. 即获得每个关键词的内容,又获得每个关键词的位置
regexp.exec(str)
替换
简单替换:
将所有关键词都替换为统一的新值
str=str.replace(/正则表达式/ig,"替换值")
问题: 无法根据不同的关键词,选择不同的值替换
高级替换:
根据每个关键词的不同,动态返回不同的替换值
str=str.replace(/正则表达式/ig,function(kw,,,...){ //kw: 会自动获得本次找到的完整关键词 //$n: 会自动获得本次找到的关键词中第n个分组的子内容 return 根据不同kw,返回不同替换值 })
衍生
删除
替换为空字符串
格式化
2步
1. 用正则对原始字符串分组
var reg=/(\d{4})(\d数据结构)(\d数据结构)/
正则中每个分组都会自动获得一个分组序号,从1开始
2. 在replace的替换值中使用$n,重新拼接新的格式
birth.replace(reg,"年月日")
切割
简单切割
var subs=str.split("分隔符")
复杂切割
var subs=str.split(/正则表达式/)
返回值
多段子字符串组成的数组
切割后的结果中不包含分隔符
固定套路
将字符串打散为字符数组
var chars=str.split("")
正则表达式
什么是
规定一个字符串中字符出现规律的规则
何时
1. 按规则模糊查找多种关键词时
2. 用规则验证用户输入的格式时
1. 关键词的原文就是最简单的正则表达式
2. 字符集
何时
只要一位字符有多个备选字时
[备选字符列表]
强调:
一个字符集只能匹配一位字符
简写
[0-9]
[a-z]
[A-Z]
[A-Za-z]
[A-Za-z0-9]
[\u4e00-\u9fa5]
除了
[^47]
3. 预定义字符集
\d
[0-9]
\w
[A-Za-z0-9_]
\s
空字符
空格,制表符...
.
通配符
4. 量词
何时
只要规定一个字符集出现次数时
如何
字符集量词
强调:
默认仅修饰相邻的前一个字符集
有明确数量边界
字符集{n,m}
至少n个,最多m个
字符集{n,}
n个以上
字符集{n}
必须n个
没有明确边界
字符集?
{0,1}
字符集*
{0,}
字符集+
{1,}
5. 选择和分组
分组
(多个规则)
何时:
1. 希望一个量词同时修饰多个字符集时
身份证号
\d{15}(\d\d[0-9Xx])?
2. 希望分段获取或处理字符串中部分子内容时
格式化生日
选择
规则1|规则2
| 优先级最低
何时
在两种规则间任选其一匹配
微信
(微|w(ei)?)\s*(信|x(in)?)
6. 指定匹配位置
^ 字符串开头
比如
开头的空字符
^\s+
$ 字符串结尾
比如
结尾的空字符
\s+$
开头或结尾的空字符
^\s+|\s+$
\b 单词边界
^ $ 空字符 标点
比如:
查找每个单词首字母
紧挨着单词边界之后的一个字符
\b[a-z]
7. 密码强度:
6~8位字母,数字的组合,至少包含一个大写字母和一位数字
^(?![a-z0-9]+$)(?![A-Za-z]+$)[A-Za-z0-9]{6,8}$
RegExp
什么是
封装一条正则表达式,并提供用正则表达式执行验证和查找的API
何时
1. 使用正则表达式验证字符串格式
2. 即查找关键词内容,又查找关键词位置
创建
1. 直接量
var reg=/正则表达式/ig
何时
如果正则表达式是固定不变的
字符冲突
/ -> \/
2. 用new
var reg=new RegExp("正则表达式","ig");
何时
如果正则表达式需要动态生成
字符冲突:
\->\ \" \'
new RegExp("\d{6}")
API
查找关键词
var arr=reg.exec(str)
即查找内容又查找位置
在str中查找下一个满足reg要求的关键词
返回值:
arr: [0: "完整关键词", 1: , 2: ,..., index: 本次找到关键词的位置]
reg.lastIndex
下次开始位置
如果没找到,返回null
都要先判断不是null,再使用
exec做三件事
1. 将本次找到的关键词,放入数组第0个元素, 将每个分组的子内容放入后续元素
2. 修改数组的index属性,记录本次找到关键词的位置
3. 修改reg.lastIndex属性=index+关键词的长度
固定套路:
找全部
var arr=null; while((arr=reg.exec(str))!=null){ arr[0] //完整关键词 arr[n] //第n个分组的子内容 arr.index //本次找到关键词的位置 reg.lastIndex //下次开始查找的位置 }
简写
如果只获得某个分组的子内容
while(reg.exec(str)!=null){ RegExp.$n //第n个分组的子内容 }
验证
var bool=reg.test(str)
验证str是否符合reg的规则要求
问题:
test默认,只要部分匹配就返回true
解决:
只要验证,正则都要前加^,后加$
表示从头到尾完全匹配
Math
不能new
所有API都用Math.直接调用
API
1. 取整
上取整
Math.ceil(num)
下取整
Math.floor(num)
只能对纯数字内容下取整
如果传入的不是数字,就自动调用Number(x)隐式转换为数字
vs parseInt(str)
先去掉字符串后非数字字符,再省略小数部分
如果传入的不是字符串,就自动调用String(x),先转为字符串
问题:
多数情况下,只去单位,还要保留小数
解决:
如果只是去单位,首选parseFloat
四舍五入取整
Math.round(num)
返回值是number
可直接做算术计算
只能取整
不能指定小数位数
vs num.toFixed(d)
返回值string
必须先转换,再计算
可以按任意小数位数四舍五入
自定义round
function round(num,d){ num*=Math.pow(10,d); num=Math.round(num); return num/Math.pow(10,d); }
2. 乘方和开平方
乘方
Math.pow(底数,幂)
开平方
Math.sqrt(num)
for(var i=2;i<=Math.sqrt(num);i++){ if(num%i==0){不是质数,return} } 是质数
3. 最大值和最小值
Math.max/min(值1,值2,...)
问题:
不支持查找一个数组中的最大值/最小值
解决:
Math.max/min.apply(null,arr)
4. 随机数
默认
0<=Math.random()<1
在任意min~max之间生成随机整数
parseInt(Math.random()*(max-min+1)+min)
在0~max之间生成随机整数
parseInt(Math.random()*(max+1))
Date
封装一个时间,并提供操作时间的API
创建
4种
1. 自动获得客户端当前系统时间
var now=new Date()
2. 创建日期对象保存自定义时间
var date=new Date("yyyy/MM/dd hh:mm:ss")
var date=new Date(yyyy,MM-1,dd,hh,mm,ss)
3. 复制一个日期对象
为什么
日期的计算都是直接修改原日期对象,计算后,原日期无法保留
何时
只要需要同时保存开始和结束时间时,都要先将开始时间复制一个副本,再用副本计算截止时间
var date2=new Date(date1)
4. 用毫秒数
var date=new Date(ms)
何时:
将获得ms数转化为当地的时间时
本质
日期对象中存储的是1970年1月1日0点至今的毫秒数
为什么
毫秒数不受时区影响
同一个毫秒数,在不同时区可用new Date()轻松转化为当地时间显示
var ms=date.getTime();
API
单位
FullYear, Month, Date, Day
没有s结尾
Hours, Minutes, Seconds Milliseconds
有s结尾
每个单位都有一对儿getXXX/setXXX方法
date.getXXX()负责获取指定单位的数值
date.setXXX(num)负责设置指定单位的数值
优
自动调整时间进制
特例:
Day没有setXXX()方法
取值范围:
只有月中的日Date,从1开始到31结束
其余都从0~进制-1结束
只有月份Month需要修正
0~11
计算机中的月份比现实中的月份至小1
其余都不需要修正
计算
两日期对象可相减
得到ms差
可计算倒计时
对任意单位做加减
date.setXXX(date.getXXX()+n)
强调:
1. 直接修改原日期对象
另见:
2. setXXX() 可自动调整时间进制
格式化
toString()
当地标准时间的完整格式
toLocaleString()
当地时间的简化版格式
toLocaleDateString()
当地时间格式
仅保留日期部分
有兼容性问题
toLocaleTimeString()
当地时间格式
仅保留时间部分
toGMTString()
国际标准时间(0时区)
Error
什么是错误
代表程序执行过程中导致程序无法正常执行的原因
错误处理
什么是
即使程序发生错误,也保证不异常退出的机制
为什么
任何程序只要发生错误,就会立刻中断退出
何时
只要希望程序即使出错,也不会中断退出
如何
try{ 可能出错的代码 }catch(err){ 只有出错才执行的错误处理代码 比如: 错误提示,记录日志,保存进度/数据 }finally{ 无论是否出错都必须执行的代码 释放资源 }
Error错误对象
什么是
在发生错误时自动创建的封装错误信息的对象
name
错误类型
6种
SyntaxError, ReferenceError, TypeError, RangeError, EvalError, URIError
message
错误提示信息
String(err)
错误类型:错误提示信息
优化
如果可以提前预知错误的原因
建议用if...else...代替try...catch
抛出自定义错误
在协作开发中,程序的作者用于提醒调用者错误的使用了你的程序
throw new Error("自定义错误信息")
Function
什么是
函数其实是一个封装一段代码段的对象
函数名其实仅是引用函数对象的一个普通变量
为什么
代码重用
何时
只要一项任务,可能被反复使用,都要定义为函数,反复使用函数
创建
3种
1. 声明
function 函数名(参数列表){函数体; return 返回值}
会被声明提前
hoist
什么是
在开始执行程序前
引擎会首先查找var声明的变量和function声明的函数
将其提前到当前作用域的顶部集中创建
赋值留在原地
鄙视时
凡是看到先使用,后声明,都是在声明提前
先改为声明提前之后的程序,再判断输出
解决问题
1. 所有变量和函数的声明都放在当前作用域的顶部
2. ES6
可用let代替var
要求
当前作用域中let a之前不允许出现未声明的a
3. 函数
另见:
2. 直接量
var 函数名=function(参数列表){函数体; return 返回值}
不会被声明提前
3. 用new
var 函数名=new Function("参数名1","参数名2",...,"函数体; return 返回值")
参数:
接收函数执行时必须的数据的变量
为什么
可让函数更灵活
何时
只要函数必须某些数据才能正常执行
返回值:
函数的执行结果
何时
如果调用者需要获得函数的返回值
调用
让引擎按照函数的步骤清单,执行任务
强调:
函数不调用不执行
只有调用才执行
重载(overload)
什么是
相同函数名,不同参数列表的多个函数
在调用时
根据传入参数的不同,自动选择匹配的函数执行
为什么
减少API的数量,减轻调用者的负担
何时
只要一项任务,需要根据不同的参数,执行不同的操作时
如何
问题
js语法不支持重载
js不允许多个同名函数同时存在
解决
每一个函数内,都有一个arguments对象接住所有传入函数的参数值
根据arguments的元素内容或元素个数,判断执行不同的操作
arguments
函数调用时,自动创建的
自动接收所有传入函数的参数值的
类数组对象
长的像数组的对象
vs 数组
相同
1. 下标
2. length
3. for/for of遍历
不同
类型不同
数组是Array类型
类数组对象是Object类型
类数组对象无法使用数组的API
匿名函数
什么是
函数创建时,没有指定函数名
使用后自动释放!
为什么
节约内存
划分临时作用域,避免全局变量
何时
只要一个函数只用一次
1. 回调callback
将一个函数作为参数传入另一个函数内,被其他函数调用
比如:
arr.sort(function(a,b){return a-b;})
ES6
arr.sort((a,b)=>a-b)
str.replace(/reg/g, function(kw,,,...){return 替换值})
ES6
str.replace(/reg/g, (kw,,,...)=>{return 替换值})
2. 自调
定义函数后自己调用自己,调用后,立刻释放
何时
定义一个临时作用域,减少使用全局变量, 避免全局污染
如何
(function(参数列表){函数体; return 返回值})(参数值列表)
+function(参数列表){函数体; return 返回值}(参数值列表)
作用域和作用域链
作用域(scope)
变量的可用范围
其实是一个对象
包括
全局作用域对象
window
保存全局变量
优
可反复使用
缺
随处可用
全局污染
建议:
尽量少用全局变量
函数作用域对象
AO
保存局部变量
包括
2种
参数变量
函数内var出的变量
优
仅函数内可用
不会被污染
缺
不可重用
函数的生命周期
开始执行程序前
创建ECS
专门保存正在调用的函数的执行环境的 数组
首先在ECS中添加浏览器主程序的执行环境main
创建全局作用域对象window
所有全局变量都是保存在window对象中
main执行环境引用window
定义函数时
用函数名声明全局变量
创建函数对象,封装函数定义
函数对象的scope属性,指回函数创建时的作用域
函数名变量引用函数对象
调用函数时
向ECS中压入本次函数调用的执行环境元素
创建本次函数调用时使用的函数作用域对象(AO)
在AO中创建所有局部变量
包括
形参变量
函数内用var声明的变量
设置AO的parent属性引用函数的scope属性指向的父级作用域对象
函数的执行环境引用AO
变量的使用顺序
先在AO中找局部变量
AO中没有才去父级作用域中找
调用后
函数的执行环境出栈
导致AO释放
导致AO中的局部变量一同被释放
作用域链(scope chain)
由各级作用域逐级引用,形成的链式结构
保存着所有的变量
控制着
变量的使用顺序
另见:
函数中,没有用任何对象/this就直接访问的变量,在作用域链中找
闭包(closure)
什么是
即重用变量,又保护变量不被污染的机制
为什么
全局变量
优:
可重用
缺:
易被污染
局部变量
缺:
不可重用
优:
不会被污染
何时:
即重用变量,又保护变量不被污染
如何
三特点(3步)
1. 外层函数
包裹受保护的变量和操作变量的内层函数
2. 外层函数要返回内层函数的对象
3种
1. return function(){...}
2. 直接给全局变量赋值一个内部function
3. 将内部函数保存在一个对象的属性或数组元素中
return [function,function,function]
return { fun:function(){...} }
3. 调用外层函数,用外部变量接住返回的内层函数对象
形成闭包
闭包如何形成
外层函数调用后,外层函数的作用域对象(AO),无法释放
被内层函数对象的scope引用着
缺:
比普通函数占用更多内存
多的是外层函数的作用域对象(AO)始终存在
容易造成内存泄漏
解决:
如何释放闭包
将引用内层函数对象的外部变量置为null
导致内层函数对象被释放
导致外层函数的AO被释放
鄙视
画简图
2步
1. 找受保护的的变量,确定外层函数调用后,受保护变量的最终值
2. 找操作受保护的变量的内层函数对象
另见:
结论:
同一次外层函数调用,返回的内层函数对象,共用同一个受保护的变量
OOP
面向对象三大特点:
封装
创建一个对象,集中存储一个事物的属性和功能
继承
父对象中的成员,子对象无需重复创建,就可直接使用
多态
同一事物,在不同情况下,表现出不同的状态
封装——创建对象
也称为封装
将一个事物的属性和功能集中定义在一个对象中
事物的属性会成为对象的属性
其实就是保存在对象中的普通变量
事物的功能会成为对象的方法
其实就是保存在对象中的普通函数
对象的成员
属性和方法统称为成员
为什么
便于维护
何时
只要使用面向对象,都要先创建对象,再按需调用对象的方法执行操作
如何
3种
创建一个单独的对象
1. 用对象直接量
var obj={ 属性名:属性值, ... : ... , 方法名: function(){ ... this.属性名... } }
ES6
var obj={ 属性名:属性值, ... : ... , 方法名(){ ... this.属性名... } }
何时
在创建对象时,已知对象的成员
问题:
对象自己的方法,访问自己的属性,如果不加this,仅会在作用域链中找,不会在对象中找
解决:
this.属性名
今后,只要对象自己的方法要访问自己的属性,必须用this.属性名
强调:
不带this.的变量,在作用域链中查找
this.属性在当前对象和当前对象的原型链中找
2. 用new
先创建一个空对象
var obj=new Object();
{}
简写:
new可省略,()可省略,但不能同时省略
可推广到其它内置对象
为新对象添加新属性
obj.属性名=属性值
obj.方法名=function(){ ... this.属性名 ... }
何时:
在创建对象时,暂时不知道对象的成员
揭示:
js中一切对象底层都是关联数组
相同
obj.属性名
等效于
obj["属性名"]
只要访问对象的属性时,属性名是变化的变量,就只能用[变量]
for in 遍历每个成员
for(var key in obj){ key //当前属性名 obj[key] //当前属性值 }
访问不存在的属性,不报错,返回undefined
强行给不存在的属性赋值
不报错
自动添加该属性
不同
类型不同
API不通用
问题:
反复创建多个相同结构的对象时,会造成大量重复的代码
解决: 用构造函数反复创建多个相同结构的对象
3. 用构造函数
什么是
规定一类对象统一结构的函数
何时
反复创建多个相同结构的对象
作用:
1. 描述统一的结构
2. 将空对象构建成要求的结构
如何
2步
1. 定义构造函数
function 类型名(属性参数){ this.属性名=属性参数; /*this.方法名=function(){ ... this.属性名 ... }*///js中强烈不推荐将方法定义在构造函数中 }
2. 用new调用构造函数
实例化一个对象
var obj=new 类型名(属性值)
new
1. 创建一个新的空对象
2. 设置新的子对象的__proto__继承构造函数的prototype对象
3. 调用构造函数,将构造函数中的this自动替换为当前新对象
构造函数将规定的属性添加到新对象中,并将传入的参数值保存在新对象的新属性中
4. 返回新对象的地址保存到变量中
优:
代码重用
问题:
无法节约内存
放在构造函数中的方法定义,每new一次,都会创建函数对象副本
解决:
继承
this
什么是
自动引用正在调用当前方法的.前的对象
为什么
不用this的普通变量,只能在作用域链中查找,无法进入对象中
一般对象的变量名可能变化,所以不能写死在对象的方法内部
何时
只要希望去当前对象中找属性值时,就必须用this.属性名
obj.fun()
fun中的this->obj
new Fun()
Fun中的this->正在创建的新对象
fun()和匿名函数自调
this默认->window
类型.prototype.fun
fun中的this->将来调用fun的.前的子对象
子对象一定是当前类型
如果this不是想要的
fun.call(替换this的对象)
相当于 对象.fun()
访问对象成员
访问属性
对象.属性名
用法和普通变量完全一样
特殊:
如果访问对象的属性时,属性名需要动态拼接
只能用obj[属性名]
调用方法
对象.方法名()
用法和普通函数完全一样
继承
什么是
父对象中的成员,子对象无需重复创建即可直接使用
何时:
只要多个子对象拥有相同的属性值或方法时,仅需要集中定义在父对象中一份,所有子对象共用即可
为什么
代码重用,节约内存
如何
js中的继承都是继承原型对象
原型继承
原型对象
什么是
集中保存同一类型的所有子对象共有成员的父对象
何时
只要多个子对象,拥有相同的成员时,都要将相同的成员集中保存在原型对象中一份即可
如何
不用创建
买一赠一
在定义构造函数同时,已经自动创建了该类型的原型对象
构造函数.prototype指向原型对象
原型对象.constructor指回构造函数
new的第二步
每创建一个新子对象,都会自动设置子对象的__proto__继承构造函数的原型对象
向原型对象中添加共有成员:
构造函数.prototype.成员=值
自有属性和共有属性
自有属性:
直接保存在对象本地的属性
共有属性:
保存在原型对象中,所有子对象共有的属性
操作
读取
子对象.属性
修改
自有属性
必须用 子对象.属性名=值
共有属性
必须用 构造函数.prototype.属性名=值
判断
自有
var bool=obj.hasOwnProperty("属性名")
共有
不是自有
&&
obj.属性名!==undefined
属性名 in obj
判断属性名是否在obj的原型链中
否则,就是没有
内置对象的原型对象
内置对象
ES标准中规定的,浏览器厂商已经实现的对象
11个
String Number Boolean
包装类型
专门封装原始类型的值,并提供操作原始类型值的API
为什么
原始类型的值本身不具有任何功能
何时
只要试图对原始类型的值调用函数时,引擎会自动创建对应类型的包装类型对象
封装原始类型的值
调用包装类型中的方法操作原始类型的值
比如:
n.toFixed(2)
typeof n
number
new Number(n).toFixed(2)
str.charCodeAt()
typeof str
string
new String(str).charCodeAt();
Array Date Math RegExp
Error
Function Object
Global
全局对象
在浏览器中被window代替
其实内置对象类型Array,Date...都是构造函数
每种类型都有自己的原型对象
Array.prototype, Date.prototype, ..
内置对象的原型对象中保存了所有该类型的子对象共用的API
固定套路:
解决旧浏览器无法使用新API的问题
2步:
判断
if(typeof 内置类型.prototype.API !=="function")
if(!("API" in 内置类型.prototype))
添加
内置类型.prototype.方法=funciton(参数){ ... this //获得将来调用方法的.前的对象 }
原型链(prototype chain)
由多级父对象逐级继承形成的链式结构
保存了
所有对象的属性
控制着
对象成员的使用顺序
先用自有属性
自己没有,才延原型链向父级找
原型链上没有,返回undefined
vs 作用域链
保存了
局部和全局变量
控制着变量的使用顺序
先用局部变量
局部没有,才去作用域链上找
找不到,报错
总结
所有不用.的变量都在作用域链上找
作用域链中的变量不用.,可直接访问
所有对象.访问的属性都保存在原型链上
原型链上的属性必须用"对象."才能访问
判断对象的类型
0. typeof
只能区分基础类型和function
不能进一步区分对象的类型
1. var bool=类型.prototype.isPrototypeOf(child)
不仅检查直接父对象,而且检查整个原型链
2. var bool= child instanceof 构造函数
不仅检查直接父对象,而且检查整个原型链
问题:
检查整个原型链
解决: 3. 检查内置class属性
Object.prototype.toString.call(obj)=="[object 类型名]"
obj.toString()
更严格
class属性直接保存在对象本地
只在创建对象时确定类型
对象创建后,不随继承关系的改变而改变
如果检查数组类型: 4. var bool=Array.isArray(obj)
ES5
IE9+
原理和
Object.prototype.toString.call一样
鄙视:
方法定义在原型对象中,还是定义在构造函数对象上
答
如果方法仅限当前类型的子对象可用,其他类型的对象不可用,就定义在原型对象中
必须当前类型的子对象才能调用
如果方法不确定将来调用它的对象类型,就定义在构造函数对象上
不需要任何对象实例,即可用构造函数名直接调用
多态
同一个方法,在不同情况下表现出不同的状态
重写(override)
如果子对象觉得父对象的成员不好用,可在本地定义同名自有成员,来覆盖父对象中的成员
自定义继承
1. 仅修改两个对象间的继承关系:
获得子对象的父对象
var father=Object.getPrototypeOf(child)
设置子对象继承指定父对象
Object.setPrototypeOf(child,father)
2. 修改构造函数原型对象,来修改所有子对象的父对象
构造函数.prototype=father
时机:
必须紧跟在构造函数定义之后
开始创建子对象之前
3. 仅基于现有父对象,创建子对象,并扩展自有属性: Object.create()
创建新子对象,继承父对象,扩展子对象自有属性
var child=Object.create(father,{ 属性名:{四大特性}, ... : ... })
鄙视: 模拟实现Object.create()
Object.create=function(father,props){ //var child=new Object(); //Object.setPrototypeOf(child,father); var Fun=function(){}; Fun.prototype=father; var child=new Fun(); //Object.defineProperties(child,props); if(props!==undefined){ for(var key in props){ child[key]=props[key].value; } } return child; }
4. 两种类型间的继承
何时:
如果发现多个类型拥有部分相同的属性结构和方法定义
都要抽象父类型
如何
2步:
1. 定义抽象父类型
相同的属性结构定义在父类型的构造函数中
相同的方法定义在父类型的原型对象中
2. 让子类型继承父类型
1. 在子类型构造函数中借用父类型构造
extends
让父类型构造函数帮助添加相同部分的属性定义
子类型构造函数仅负责添加独有的属性定义即可
错误:
直接调用父类型构造函数
this->window
父类型中的属性都泄露到全局
正确
父类型构造.call(this, 参数1,参数2,...)
简写:
父类型构造.apply(this, arguments)
鄙视:
call vs apply
相同:
都是强行借用一个本来无法调用的函数,并临时替换函数中this为指定对象
不同:
call
传入借用函数的参数,必须单独传入,逗号分隔
apply
传入借用函数的参数,放在一个数组中整体传入
可自动打散数组类型参数
2. 让子类型原型对象继承父类型原型对象
inherits
Object.setPrototypeOf( 子类型构造.prototype, 父类型构造.prototype )
ES5
对对象的保护
对象的属性
命名属性
可直接用.访问到的属性
分为
数据属性
实际存储属性值的属性
四大特性
{ value: 属性值, writable: true/false, //控制是否可修改 enumerable: true/false, //控制是否可被for in遍历 configurable: true/false, //1. 控制是否可删除 //2. 控制是否可修改前两个特性 }
强调
configurable一旦改为false,不可逆
访问器属性
不实际存储数据,专门提供对其它数据/变量的保护
何时
1. 用自定义规则保护属性时
比如
age属性必须介于18~65之间
2. 为对象添加虚拟属性
fullName
获取时
firstName+lastName
赋值时
将fullName按空格分隔,一半给firstName,另一半给lastName
2步:
1. 定义一个隐藏的数据属性实际存储属性值
2. 定义访问器属性专门读写隐藏的数据属性
四大特性
{ get:function(){return this.隐藏属性;}, set:function(val){ //如果val符合条件 this.隐藏属性=val //否则 报错 }, enumerable, configurable }
试图用访问器属性读取受保护的值时,自动调用get方法
试图用访问器属性修改受保护的值时,自动调用set方法,参数val可自动获得要赋的新值
如果省略set方法,则该访问器属性相当于只读
获取一个属性的四大特性
var attrs=
Object.getOwnPropertyDescriptor(obj,"属性名")
attrs: {四大特性}
修改四大特性
只改一个属性的四大特性
Object.defineProperty(obj,"属性名",{ 特性名:值, ... : ... })
同时修改多个属性的四大特性
Object.defineProperties(obj,{ 属性名1: { 特性名:值, ... : ... }, 属性名2: {四大特性} })
强调:
双保险
修改writable或enumerable时,最好同时定义configurable为false,禁止反向修改
要修改的属性不存在,会自动添加该属性
特性的默认值
用.添加的新属性
特性的默认值为true
defineProperty/defineProperties添加的新属性
特性的默认值为false
内部属性
不能用.访问到的属性
__proto__
Object.getPrototypeOf(obj)
Object.setPrototypeOf(child,father)
class
Object.prototype.toString.call(obj)
extensible:true
var bool=Object.isExtensible(obj)
Object.preventExtensions(obj)
防篡改
1. 防扩展
禁止添加新属性
相当于
将对象的extensible:false
判断是否已禁止扩展
Object.isExtensible(obj)
设置防扩展
Object.preventExtensions(obj)
2. 密封
在防扩展同时,禁止删除现有属性
相当于
将每个属性的configurable:false
其他属性在修改特性时,不必反复修改configurable:false
判断是否已密封
Object.isSealed(obj)
密封对象
Object.seal(obj)
最像Java的构造函数
function Emp(id,ename,salary,age){ this.id=id; this.ename=ename; this.salary=salary; Object.defineProperties(this,{ id:{writable:false}, salary:{enumerable:false}, _age:{ writable:true, enumerable:false }, age:{ get:function(){return this._age;}, set:function(val){ if(val<18||val>65) throw new Error(...) this._age=val; }, enumerable:true } }); this.age=age; //防篡改: Object.seal(this);//密封 }
鄙视:
实现一个js类型,包含public属性和private属性
3. 冻结
在密封同时,禁止修改所有属性的值
相当于
将每个属性的writable:false
判断是否被冻结
Object.isFrozen(obj);
冻结对象
Object.freeze(obj)
何时:
如果一个对象中所有属性值都不允许擅自修改
call/apply/bind()
何时:
只要函数中的this不是想要的,就可用call/apply/bind替换
call和apply
立刻调用函数执行
同时临时替换函数中的this
何时
如果立刻执行,且临时替换this
如何
fun.call(obj, 参数值列表)
调用fun
替换fun中的this为obj
将参数值列表传递给fun
fun.apply(obj, 参数值数组)
vs call
apply要求传入fun的参数必须放在数组中整体传入
apply可自动将数组打散为单个参数值分别传入
bind
基于一个现有函数,创建一个新函数,并永久绑定this和部分参数
何时:
只要替换回调函数中的this时
如何:
var newFun=fun.bind(obj, 参数值列表 )
创建一个和fun功能完全一样的新函数
永久绑定新函数中的this为obj
永久绑定新函数中的部分参数为参数值列表中的值
强调:
被bind创建的函数中的this和绑定的变量,任何情况下不能再被call替换
数组API
判断
判断arr中每个元素是否都符合条件
arr.every(function(val,i,arr){ //val: 当前元素值 //i : 当前位置 //arr : 当前数组 return 判断条件; })
只判断arr中是否包含符合条件的元素
arr.some(function(val,i,arr){ return 判断条件; })
遍历
对数组中每个元素执行相同的操作
直接修改原数组
arr.forEach(function(val,i,arr){ arr[i]=新值; })
IE8
Array.prototype.forEach=function(callback){ for(var i=0;i<this.length;i++){ if(this[i]!==undefined)//防稀疏数组 callback(this[i],i,this); } }
不修改原数组,返回新数组
var newArr=arr.map(function(val,i,arr){ return 修改后的新值 })
IE8
Array.prototype.map=function(callback){ for(var i=0,result=[];i<this.length;i++){ if(this[i]!==undefined)//防稀疏数组 result[i]=callback(this[i],i,this); } return result; }
其实map也可像forEach一样使用
回调函数中的this,指window
过滤和汇总
过滤
选取arr中符合条件的元素组成新的子数组
var subArr=arr.filter(function(val,i,arr){ return 判断条件 })
汇总
将数组中每个元素值汇总出一个结果
var r=arr.reduce(function(prev,val,i,arr){ return prev+val; }, 初始值)
严格模式
何时:
1. 新项目,都要启用严格模式
2. 旧项目,逐个模块向严格模式迁移
如何
2个范围
整个代码块或js文件启用严格模式
在代码块或js文件的开头插入: "use strict";
仅在一个函数内启用严格模式
在function内,函数体顶部插入: "use strict";
要求:
1. 禁止给未声明的变量赋值
2. 静默失败升级为错误
3. 普通函数调用或匿名函数自调中的this不再默认指向window,而是undefined
4. arguments, arguments.callee不推荐使用
ES6
模板字符串
简化
字符串拼接
反引号包裹的字符串`xxx`
支持换行
支持动态生成内容
在``中使用${js表达式}
${}中可以放任何有返回值的js表达式,比如:变量, 运算, 三目, 函数调用, 创建对象, 访问数组下标
${}中不能写程序结构: if else if else for while do while switch case
模板字符串会自动执行js表达式的结果,并拼接到最终生成的普通字符串中
let
什么是
专门代替var用于声明变量
var的缺点
1. 声明提前, 打乱程序正常的执行顺序
2. 没有块级作用域,导致块内的变量会被提前到块外,影响外部的程序
let的优点
1. 为js添加了块级作用域
避免了块内的变量污染外部
2. 避免了声明提前
在同一作用域内,let前不允许提前使用未声明的同名变量
let的小脾气
相同作用域内不允许同时let两个同名的变量
let 前不允许提前使用该同名变量
即使在全局let的变量,也不会保存在window中
原理/替代办法:
let其实是匿名函数自调,且修改变量名避免冲突
箭头函数
简化:
回调函数
如何
去function,在()和{}之间加=>
更简化:
1. 如果只有一个参数,可省略()
但是,如果没有参数,必须保留空()
2. 如果函数体只有一句话,可省略{}和结尾的分号
更简化
且一句话还是return
则必须省略return
箭头函数特征
内外this相同(通)
只要不希望内外的this相同时,就不能用箭头函数简化
比如: 对象中的方法
何时
几乎所有回调函数都可用箭头函数简化
反例
如果回调函数中的this和外部的this不相同时,不能简化
//this->window elem.addEventListener("click",function(){ //this->当前elem })
如果用箭头函数简化,结果内外this都是window
其实:
//this->window elem.addEventListener("click",e=>{ //this->window //e.target->当前elem })
for of
简化for循环:
遍历索引数组和类数组对象
如何
of是依次取出数组中每个元素值,保存到of前的变量中
for(var val of arr){ val//当前元素值 }
问题:
1. 不支持自定义下标或属性名的关联数组和对象
2. 只能读取元素值,不能修改元素值, 不能获得元素的位置
按值传递
3. 只能连续遍历所有,不能控制遍历的顺序和步调
Subtopic
参数增强
默认值
什么是
ES6中可给函数定义中形参列表的最后一个参数定义默认值
何时
如果函数只有最后一个形参不确定是否给值时
如何
function 函数名(形参1,形参2,...,形参n=默认值)
如果调用函数时,没有提供最后一个实参,就自动用默认值代替
替代
fromi=fromi||0
如果形参变量fromi的值有意义(可以转为true),就使用fromi的值
如果形参变量fromi没有传值,或传的值没有意义(会转为false),就使用||后的0作为备胎
剩余参数
什么是
代替arguments处理不确定参数个数的情况
为什么
arguments的两个问题
1. 不是数组类型,不能用数组的API
2. 只能全部获得实参值,不能有所选择
3. 名字太长,且没有意义
4. 箭头函数不支持arguments
何时
当多个形参不确定给不给值时,但不要求实参与形参对应时
如何
定义函数时
function 函数名(形参1,形参2,...数组名){ //数组会接住除之前形参1和形参2之外所有剩余的实参值 //保存剩余实参值的结构是一个纯正的数组,可用数组家全部API }
优点
1. 是纯正的数组类型,可用数组家全套API
2. 可获得部分实参值,不会与之前的形参变量争抢
3. 箭头函数只支持...rest语法
4. 数组名可自定义,简单
替代
获得所有实参值,并转为数组
var arr=[].slice.call(arguments)
将类数组对象转为数组的通用方法
获得部分实现值,并转为数组
var arr=[].slice.call(arguments, 开始截取的位置)
打散数组
什么是
代替apply,将数组打散为单个值,传入函数中
为什么
apply的本职工作是替换this,顺便打散数组
如果用apply单纯打散数组,必须给定一个无意义的对象作为第一个参数
比如: 查找数组中的最大值:
Math.max.apply(null,arr)
何时
当一个函数需要多个参数值,但是给定的实参值却是放在一个数组中
如何
调用函数时
函数名(...数组)
先将数组打散为单个值,再将单个值传入函数中
替代
函数.apply(任意对象, 数组)
如果该函数和this无关,则任意对象可随便写任何符合语法的值或对象都行,没有任何影响
固定套路
合并多个数组
var arr3=[].concat(arr1, arr2)
var arr3=[...arr1, ...arr2]
其实...不但可以打散数组,还可以打散对象
克隆一个对象
var obj2={...obj1}
合并多个对象
var obj3={...obj1, ...obj2}
数组降维
二维数组降维
arr=[].concat(...arr)
多维数组降维
function fun(arr){ //先降一次维 arr=[].concat(...arr); //再检查降维后的数组中是否还包含子数组 var hasArray=arr.some(function(elem){ return Array.isArray(elem); }) if(hasArray){ //如果包含子数组 arr=fun(arr);//就只能再降维一次,直到检查不再包含子数组为止 } return arr }
解构
什么是:
提取出一个大对象或数组中的个别成员,单独使用
何时
只要仅使用大对象或数组中的少数部分成员时,都用解构
如何
3种
1. 数组解构
下标对下标
什么是
提取出索引数组中指定位置的元素,保存在单独的变量中独立使用
如何
1. 在等号左边先将要接受数据的变量,装扮成和等号右边数组相同的结构
2. 执行时,等号右边的数组会将相同位置的元素值,赋值给等号左边相同位置的变量
var date=[2019,5,6]; 0 1 2 0 1 2 var [y,m,d]=date; y=2019, m=5, d=6
固定套路
不声明第三个变量,交换两个变量中的值
共有几种
a+=b, b=a-b, a-=b
只能交换数字值
a^=b, b^=a, a^=b
只能交换数字值
a=[b,b=a][0]
Subtopic
因为没有算术计算,所以可以交换任意类型的值
[a,b]=[b,a]
因为没有算术计算,所以可以交换任意类型的值
2. 对象解构
属性名对属性名
什么是
仅提取出大对象中的个别成员属性或方法,保存在变量中,单独使用
如何
1. 先将等号左边的变量装扮成和要解构的对象相同的属性结构
2. 在赋值时,等号右边的对象就会将相同属性名的属性值,赋值给等号右边的相同属性名对应的变量
var lilei={ sname: "Li Lei", sage: 11, signin:function(){ 登录... }, signout:function(){ 注销... } }
var { sname:sname, signin:signin }=lilei
结果:
变量sname="Li Lei"
变量signin=function(){ 登录... }
简写
如果属性名和变量名相同,只写一个即可
var {sname, signin}=lilei
一个名称,即当属性名配对用,又当变量名将来单独反复使用
3. 参数解构
什么是
将所有参数都定义在一个对象结构中,调用函数传参时,也是讲所有实参值都放在一个对象中传入
何时
多个形参不确定给不给时,且要求实参值必须传给指定的形参变量
如何
2步
1. 在定义函数时,将所有形参放在一个对象结构中
function ajax({ url:url, type:type, data:data, dataType:dataType }){ ... ... }
简写为
function ajax({url,type,data,dataType}){ ... ... }
结果:
函数中
形参url获得实参url的值"
http://localhost:3000/users/signup"
形参type获得实参type的值"post"
形参data获得实参data的值"uname=dingding&upwd=123456"
形参dataType获得实参dataType的值"json"
2. 在调用函数时,所有实参也必须放在相同结构的对象中
ajax({ url:"http://localhost:3000/users/signup", type:"post", data:"uname=dingding&upwd=123456", dataType:"json" })
另见:
优点
1. 无论有多少参数不确定,都不会报语法错误
如果解构时,实参列表中缺少参数,则函数中对应的形参变量获得undefined
2. 实参列表和形参列表只要名称对应即可,没有必然顺序
参数解构其实就是对象解构在函数调用时的应用而已
class
简化:
面向对象
封装,继承,多态
如何
封装
对象{}
如果一个属性的值来自于外部的变量,且属性名刚好等于变量名,则{}中只写一个名字即可
对象中的方法不能简写为箭头函数,但可去掉":function"
如果连属性名都需要动态拼接生成
应该将拼接字符串的表达式,放在[]中
class
如何
1. 用"class 类型名{}"包裹原来的构造函数和原型对象方法
2. 构造函数名提升为类型名,放在class关键词之后,修改构造函数的"function 函数名"为"constructor"
3. 原型对象方法:
可省略开头的"类型.prototype"
可省略方法名后的"=function"
所有直接保存在class中的方法,默认就是保存在原型对象中
比如:
旧
function Student(sname,sage){ this.sname=sname; this.sage=sage; } Student.prototype.intr=function(){ console.log(`I'm ${this.sname},I'm ${this.sage}`); }
新
class Student{ constructor(sname,sage){ this.sname=sname; this.sage=sage; } intr(){ console.log(`I'm ${this.sname},I'm ${this.sage}`); } }
4. 添加访问器属性:
在构造函数的平级
get 访问器属性名(){ return this.受保护的其他属性 }
set 访问器属性名(val){ if(条件) this.受保护的属性=val else 报错 }
继承
两种类型间的继承
一个class继承另一个class的属性结构和方法定义
何时
只要发现多个子class之间有部分相同的属性结构和方法定义时
如何
1. 定义父类型,集中存储相同部分的属性结构和方法
父类型的构造函数中包含相同部分的属性结构
父类型class内包含相同的方法定义
2. 让子class,继承父class
1. 在"class 类型名"后添加" extends 父类型"
0. 不用再设置Object.setPrototypeOf
2. 在构造函数中借用父类型构造函数
不用call/apply,不用加this
super(属性值列表)
没有继承的两个class
Subtopic
使用继承优化后,变成3个class
Subtopic
Promise
为什么
避免callback hell
何时
只要要求多个原本异步执行的函数,必须连续顺序执行
旧js中
用回调函数
1. 在前一个任务的形参列表结尾添加一个callback参数
在前一个任务内部,之后一句话执行完之后,自动调用传入的回调函数callback
function liang(next){ console.log("亮起跑...") setTimeout(function(){ console.log("亮到达终点!"); //亮到达终点后,自动调用下一项任务开始执行 next(); },6000); }
2. 在调用前一个任务时,就要将下一个任务函数对象,以参数形式提前交给前一个任务的callback形参保存起来备用
liang( //liang的next function(){ ran() } )
结果:
function(){ ran() }给了liang的形参next
当liang执行完,在内部自动调用next(),等效于调用下一个function
问题: 如果需要连续执行的异步任务很多,就会造成很深的嵌套
称为回调地狱
如何
2步
定义时
前一个函数
不用在参数中添加callback
在函数内返回Promise对象
function 前一项任务(){ return new Promise((resolve, reject)=>{ 如果正确,就继续调用resolve() 否则,就调用reject("错误提示") }) }
Promise的参数中是一个函数
函数封装了原函数正常的逻辑
函数由两个回调函数参数
resolve
在正常执行后,调用resolve()
就会自动执行.then()
reject
在出错时,调用reject()
就会自动执行.catch()
连续调用两个函数时
前一个函数() //return new Promise() .then(下一个函数) .catch((err)=>{错误处理函数})
下一个函数后不要加()
ES7
(async function(){ try{ var result=await 异步调用1() await 异步调用2() 其它主程序代码 }catch(err){ 错误处理... } })()
必需整个过程包裹在一个 (async function(){ })() 内
await是等待
所有promise中写then的地方,都可以用await代替
await会挂起当前程序,等待当前异步任务执行完,再开始执行当前程序之后的代码
Promise的三个状态
当首次调用Promise的函数时,整个Promise对象处于pending状态
当前一个任务执行完,自动调用resolve()函数后,整个Promise的状态就变成resolved
就会自动触发then()执行
当任务执行过程中出错,自动调用reject()函数后,整个Promise的状态就变为rejected
就会自动执行catch()
Subtopic
多个函数需要连续调用,每个函数,都要返回Promise对象,才能连续用then调用
如果需要等待多个函数执行完,才执行:
Promise.all([ 函数1(), 函数2(), ... ... , ]).then(function(result){ ... })
result
.then中下一项任务参数result会接住一个数组,其中保存
[ 函数1的返回值, 函数2的返回值, ...]
和调用结束的先后顺序无关,而始终和调用的顺序一致!