1. JavaScript 是专门编写网页交互行为的语言,HTML 和 CSS 是静态语言,编写静态内容,无法编写逻辑,无法执行计算,所有静态网页必须使用 JavaScript 添加上交互行为,才能让用户使用
(1). 实现交互行为需要 3 步
①. 用户输入数据或执行操作
②. 程序接受并处理用户输入的数据
③. 程序返回处理结果
(2). JavaScript 典型用途3 个
①. 客户端表单验证
②. 数据计算
③. 动画效果
2. ECMAScript JavaScript JScript
(1). ECMA: 欧洲计算机制造协会
(2). ECMAScript: 是欧洲计算机制造协会以第一版 JavaScript 为基础,指定的 JavaScript语言标准,只固定了 JS 语言的核心语法,简称 ES.3.1=>5=> 6=>7
(3). JavaScript: 狭义: 网景参照 ES 标准,实现的自己的 JavaScript 语言版本
广义的 JavaScript: 包括三大部分
①. ES: 固定了核心语法
②. DOM: 专门操作网页内容的程序
③. BOM: 专门操作浏览器窗口的程序
(4). JScript: 微软参照 ES 标准,实现的自己的 JavaScript 语言版本
3. JavaScript 语言的特点4 个
(1). 运行在脚本解释引擎中
①. 浏览器已经自带: 不用单独安装
内容排版引擎: 专门解析 HTML 和 CSS,并绘制网页内容的软件
脚本解释引擎: 专门解析 js 程序,并执行任务的软件
②. 独立安装: Node.js Chrome V8=>单独提取
以前: 前端 JS 服务器端 PHP/Java 数据库 SQL
现在: 前端 JS 服务器端 Node.js 数据库 MongoDB JS
启动: win+R 输入 node 回车
(2). 弱类型: 3 个特点
4. 网页中的所有 js 程序,必须写在 script 标签内,script 标签可放在网页任何位置,但强烈建议放在 body 最后,所有程序都可概括为 3 步:
(1). Input 输入: input=prompt("提示信息")
(2). Process 处理数据
(3). Output 输出: 3 种
①. 在网页内容中输出一句话: document.write("内容")
问题: 会破坏网页结构 , 几乎不用
②. 弹出提示框: alert("内容")
问题: 无法修改样式和字体, 少用
③. 在控制台输出: console.log("内容")
控制台: 输出一行日志,专门编写和测试 javascript 程序的窗口
优势: 即不影响网页内容,又可修改字体大小
何时:
A. 测试简短的 js 小程序
B. 查看正式的 js 程序的输出结果和错误提示
如何:
A. 输出一条 js 语句,回车执行
B. 多行输入: shift+回车
C. 清屏: ctrl+L 或 点左上角×
D. 切换出曾经写过的程序 上下箭头
5. 变量,内存中存储一个数据的存储空间,只要反复使用的数据,必须先保存在变量中,再反复使用变量
(1). 声明:创建一个存储空间,并起一个名字
何时: 所有变量使用前必须先声明再使用
如何: var 变量名;
注意: 所有变量都用 var 声明
简写: var 变量 1,变量 2,...;
变量名:
①. 不能使用 js 语言的保留字
②. 字母,数字或下划线,不能以数字开头
③. 见名知意
④. 驼峰命名: 首字母小写,之后每个单词首字母大写
list-style-type=>istStyleType
background-color=> backgroundColor
注意: 声明但暂未赋值的变量,默认值为 undefined(一个特殊值,表示空变量)
(2). 赋值:将等号右边的数据,保存到等号左边的变量中
如何: 变量=值;
简写: var 变量名=值;
特殊: 鄙视: var a , b=2;
结果: a=undefined b=2
如果非要简写:
var a,
b=2;
(3). 取值:取出变量中的值做处理
如何: 任何情况下,使用变量等效于使用变量中的值
注意: 只有=才能改变变量的值,如果仅取出变量值做计算,则原变量中的值不变
(4). 注意:
①. 仅声明但未赋值的变量,其中不是空的,而是保存默认值 undefined
②. 尝试从未声明过的变量中取值,会报错!—ReferenceError
引用错误: 要找的变量没找到
错误原因:
A. 根本没声明
B. 声明了,但后边使用时,变量名拼写错误
③. 尝试给未声明的变量赋值,会自动创建该变量,并保存,值会产生歧义,禁止使用!
ES5: 严格模式: 比普通 js 运行模式更严格的机制
何时: 凡是新项目,所有 js 必须启用严格模式
要求: 禁止给未声明过的变量赋值,报错: ReferenceError
6. 常量-常量是一旦赋值,值不能改变的变量,只要一个变量的值不允许修改,就必须使用常量
如何 : const 常量名=值;
注意 : 常量声明时必须初始化(初始化就是第一个为变量赋值)
命名 : 常量习惯全大写
问题 : 在旧版本浏览器中,修改常量的值,虽然无法修改,但也不报错
解决: 启用严格模式,严格模式将所有静默失败升级为错误
7. 数据类型-数据类型是存在内存中的存储格式,不同用途的数据,就必须保存为对应的数据类型,使用变量 typeof 返回变量中数据的类型名
(1). 原始类型: 值直接保存在变量本地的数据类型
5 个: number| string |boolean |undefined| null
(2). 引用类型: 值无法直接保存在变量本地的数据类型
8. number-专门存储数字的数据类型,凡是用作比较和计算的数字都必须保存为 number 类型,凡是不加引号的数字,自动就是 number 类型
存储结构: 都是用二进制存储数字
十进制转二进制: n.toString(2)
存储空间: 整数 4 字节,浮点数(小数)8 字节
注意: 不管数值大小,所有整数所占空间都是一样的
9. string-专门存储文字字符的数据类型,经常用作标识和描述的字符内容保存为 string类型,凡是包裹在一对儿引号之间的字符,都是字符串类型
由于计算机只认数字,不认字符,所以字符串的存储结构为 unicode 编码(unicod 编码是人为的为全球主要语言中的每个文字编一个号),查看每个字符的 unicode 编码使用str.charCodeAt();
存储空间: 一位字母或数字,占 1 个字节(8 位),一位汉字,占 2 个字节,如"小明 666"7 个字节
转义字符:
(1). 防止字符串内容和程序的标点符号冲突
比如: "来自\"新华社\"的消息"
(2). 表示特殊意义:\n 换行 \t 制表符 \unicode 编码的一个字符
比如: "\u4e00" 表示汉字"一"
(3). 特殊: 路径:"c:\window\temp"
①. 问题:
A. 查找不到\w,就擅自去掉\,就保留 w
B. \t 误翻译为制表符
②. 解决:
A. 字符串中所有作为正文的\,都必须写为\\
B. 如果是路径,所有路径中的\必须都换成/
如: "/imgs/products/product01-s.jpg"
10. boolean-只有两个值 true/false 的数据类型
何时: 经常用作表示判断的结果
如何: 只要不加引号的 true/false,自动就保存为 bool 类型
11. undefined-专门表示一个变量声明后暂时未赋值
用途: 不会主动使用,都是 js 程序自动为变量赋初值之用,经常用于判断变量赋值错误
12. null-专门用于清空一个变量的特殊值
何时: 如果一个变量中的值不想要了,可将变量赋值为 null,经常用于程序员主动清空变量
13. 数据类型转换
(1). 弱类型语言转换
①. 声明变量时不必提前指定变量的数据类型
②. 同一个变量先后可保存不同类型的数据
③. js 引擎会根据自身的需要隐式自动转换数据的类型
优点 : 为程序员省事
缺点 : 混乱,转换的结果也不一定是想要的
(2). 如果数据类型不是想要的,就需要类型转换,转换包括隐式转换和强制转换
①. 隐式转换: 程序根据自身的需要,无需程序员干预自动完成的数据类型转换
何时: 伴随运算和判断自动执行
②. 强制转换: 程序员主动调用功能,执行的数据类型转换
何时: 如果隐式转换的结果还不是想要的,就要强制转换
(3). 隐式转换
默认一切转为数字(number)类型,再计算,因为只有数字类型才最适合算数计算,其中bool 类型中 true 转为 1 ,false 转为 0
+运算中,只要碰到一个字符串,另一个也被转为字符串,+法计算,变为字符串拼接,开发中字符串拼接使用非常频繁,所以为了简化字符串拼接,就用+代替
页面上获得的数据,都是字符串类型,不可能从页面上直接获得 number 和 bool 类型
字符串参与+运算,不再做加法计算,而改为字符串拼接
从页面上获得的字符串,要参与算数计算,必须先强制转为数字类型再计算
(4). 强制转换
①. 只要参与算数计算的非数字类型,都要先转数字再计算,将纯数字字符串或布尔类型转为数字: Number(x),隐式转换中,转数字,用的都是 Number(x)
特殊: Number(null/"") = 0 Number(undefined) = NaN
CSS 中样式属性都是带单位,Number 不能转换包含非数字字符的字符串会返回 NaN,NaN 即为 Not a Number,它是一个特殊值,表示所有非数字的数据,NaN 不会主动使用,只在无法转为数字时,自动返回,NaN 参与任何算数计算,结果依然为 NaN,NaN 做比较时不大于,不小于,不等于任何值,NaN 不等于一切
②. 将带单位的字符串去单位转为数字使用 parseFloat(str) :
原理: 从第一个字符开始,读取字符串中每个字符,碰到第一个非数字字符就停止
注意: parseFloat(true) => parseFloat("true") => NaN
说明: 只能去掉结尾的非数字字符,不能去掉开头的非数字字符
③. 将小数取整使用 parseInt(str),此函数不认识小数点,它会省略小数部分,转为整数开发中首选 parseFloat,除非确实需要去掉小数部分时采用 parseInt
④. 查看一个复杂数据结构中的内容时,可将复杂的数据结构转为字符串再输出
转字符串的方法有两种:
A. x.toString();将除 null 和 undefined 之外的任意数据,转为字符串(转 null 和 undefined会报错)
B. String(x);将任意 x 数据,转为字符串,只要转字符串,就用 String(x)
⑤. 转布尔: Boolean(x), 0 , NaN , null , undefined, ""5 个值转为 false,其余都转为 true
⑥. 固定用法
如果查看十进制数字的 2,8,16 进制结果,可用 toString(n)
将其他进制转回十进制: parseInt("num",进制)
14. 运算符和表达式
程序: 人的想法在计算机中的执行
运算符: 程序中模拟人的想法的特殊符号
表达式: 变量和运算符组成的一条程序语句
15. 算数计算符号:+, -, *, /, %
%模运算(取余数): m%n ,用 m 除以 n,不取商,取除不尽的余数部分
使用场合:
(1). 取零头
(2). 限制计算的结果不能超出一个最大值
16. 舍入误差: 计算机中也有计算不尽的小数,解决方法:
(1). 存储: 尽量多的保留小数位数, 比如: 8 位或 9 位
(2). 显示: 按指定小数位数四舍五入,n.toFixed(2)将 n 按 2 位小数四舍五入,只要显示钱数,都要 toFixed(2)
17. 关系运算: 做比较做判断
包括: ><>= <= == !=
返回值: 一定返回 bool: true/false
18. 特殊比较结果
(1). 两个字符串作比较: 不再转数字,而是依次比较每个字符的 unicode 号大小
unicode 范围: 数字: "0"-"9"48~57
"A"-"Z" 65~90
"a"-"z" 97~122
(2). NaN 问题, 用普通的==无法鉴别一个值是否是 NaN,因为 NaN 不等于一切
NaN==NaN 返回 false,如果要判断 NaN,必须用专门的 isNaN(num),另外 isNaN 可用判断一个数据是否是数字或是否可当做数字使用
(3). null 和 undefined 问题,关系运算中,自动将 undefined 隐式转为 null 再比较,解决方法是用全等“===”比较
全等“===”比较中首先类型相同,其次值相等,相当于不带隐式转换的“==”比较,只要不希望比较时自动隐式转换,就可用“===”代替“==”
全等“===”比较优点: 严格,避免不必要的隐式转换.
全等“===”比较缺点: 必须自己手动转换数据类型再比较
不全等“!==” 全等的反义词
19. 逻辑运算: 逻辑运算中都自动转为 boolean 类型进行运算,由 true 或者 false 来看要不要进一步判断,逻辑运算的返回值不一定是布尔值
(1). &&: 与
语法: 条件 1&&条件 2&&....,只要必须满足多个条件时,就使用&&
(2). ||: 或
语法: 条件 1 || 条件 2 ||....,多个条件,只要满足其一即可
(3). !: 非
颠倒一个判断结果: !true=false !false=true
20. 逻辑运算中的短路逻辑,只要前一个条件已经可以得出最终结论,则后续条件不再执行,利用短路将不再关心逻辑运算的返回值,参与运算的都是值,将在两个值之间选择一个返回,短路逻辑可实现简单的分支,一个条件,一件事儿,满足条件才执行,不满足就不执行
(1). && 只要前一个条件为 false,则后续所有条件不再执行,若是&&后面是赋值的运算,最好加上括号,因为赋值的等号的优先级特别低
语法: 条件 && (操作)
(2). || 只要前一个条件为 true,则后续条件不再执行,利用 || 短路实现默认值
语法: var 变量=值 1 || 默认值
如果值 1 有效: 不是 0、null、undefined、NaN、"",就使用值 1,否则使用值 2
21. 运算符优先级
22. 位运算: 用二进制移动位置
(1). 左移/右移
①. 左移m<<n,读作m左移n位:相当于m*2的n次方
比如:1<<2=4 ,1<<3=8(不用位运算:1*Math.pow(2,3))
②. 右移: m>>n ,读作 m 右移 n 位: 相当于 m/ 2 的 n 次方
比如: 4>>2=1 , 8>>3=1
(2). 取整: m^0 m>>>0 m|0
(3). 不声明第三个变量,而交换两个变量的值,共有几种方法
方法一: a+=b; b=a-b; a-=b
方法二: a^=b; b^=a; a^=b
问题: +和^运算只能计算数字,所以以上两种方法只能交换数字类型
23. 扩展赋值运算: 对普通赋值运算的简写
(1). a=a+b 可简写为: a+=b,也称为将 b 累加到 a 中
a=a-b 可简写为: a-=b
a=a*b 可简写为: a*=b
a=a/b 可简写为: a/=b
a=a%可简写为: a%=b
(2). 当 b 始终等于 1 时
a+=1 可简写为 a++,也称为递增
a-=1 可简写为 a--,也称为递减
(3). 前++和后++
①. a++返回+1 之前的"旧"值,代表先用旧值做操作,然后再加1
②. ++a 返回+1 之后的"新"值,代表希望先加1,再用新值做操作
24. 函数: 函数是封装一项任务的步骤清单的代码段,再起一个名字,只要一项任务,可能被反复使用,都要将任务的代码定义为一个函数,再反复使用函数
(1). 声明
语法:function 函数名(参数 1,参数 2,...){
函数体;
return 返回值;
}
①. 参数,保存一项任务必须的数据的变量,参数其实就是变量,只不过不用 var 声明,用逗号分隔多个参数变量,参数变量只能在函数内使用,参数让函数变的更灵活,只要一项任务必须某些数据才能正常执行时就要加参数
②. 函数体,就是执行任务的步骤清单的代码段
③. 返回值,任务的执行结果,如果使用函数的人希望获得函数的执行结果时
(2). 调用
让引擎按照函数的步骤执行任务,任何函数,只要不调用就不执行,只有调用才执行
函数中的代码段
语法: var 变量=函数名(参数值 1,参数值 2,...)
①. 参数值,传递给函数参数变量的执行任务必须的数据
②. 返回值,除非定义函数时,内部定义了 return 返回值,才有必要用变量保存住函数的执行结果
25. 函数的原理
(1). 存储
函数名其实就是一个普通的变量,函数本身是一个引用类型的对象(保存多个数据的复杂数据结构),函数名通过函数的地址值引用函数对象,声明时,不会执行函数的内容
(2). 调用
①. window 中声明变量,准备保存函数的返回值
②. 找到函数对象,创建函数执行时的临时存储空间
③. 将调用时传入的参数值,放入临时存储空间的参数变量中
④. 执行函数体,修改临时存储空间中的变量值
⑤. 将返回值,返回 window 中,保存到指定变量
⑥. 函数执行后,临时存储空间释放!导致,函数内局部变量一同释放,由此可见,函数中的参数变量和 var 的变量,只能在函数调用时,内部使用,出了函数,无法使用
26. 作用域(scope)-一个变量的可用范围
(1). 全局作用域
直接保存在 window 中的变量,在 window 中始终存在,如果希望一个变量可反复使用,可随处使用时就用全局变量
(2). 函数作用域: Actived Object(AO 活动对象)
保存在函数作用 AO 中的变量,仅函数调用时存在,调用后就释放了,不可重用,如果希望变量仅在函数内可用时就用局部变量
(3). 全局函数与局部函数的调用规则
调用函数时,优先使用局部变量,只要局部有,就不用全局的,如果局部没有,会去全局找,判断函数是否包含局部变量 2 种方法:
①. 参数变量
②. 函数内 var 的变量
27. 声明提前(hoist)
声明提前是在开始执行程序前,引擎会首先查找所有 var 声明的变量和 function 声明的函数,将他们集中到当前作用域的顶部优先创建,而赋值会留在原地
面试中,凡是先使用,后声明,都是在考声明提前,对于这类问题可先将程序改为提前之后的样子,再判断输出
声明提前的弊端解决办法:
(1). 强烈建议,所有要用到的变量集中声明在当前作用域的顶部
(2). ES6: 尽量用 let 代替 var
①. let 必须在严格模式下使用
②. let 作为局部变量时,必须放在代码段开头
③. 局部变量 let 之前的变量不可用
(3). 用 var 函数名=function(){...} 代替 function 函数名(){...}
function 函数名(){...}会被声明提前而 var 函数名=function(){...} 不会被声明提前,由此可见函数名其实就是一个变量,函数名通过地址引用着函数对象
28. 按值传递
两变量间赋值时,或将变量作为参数传递给函数的参数变量时,其实只是将原变量中的值复制一个副本给对方,修改新变量的值,不会影响原变量的值
29. 全局函数
全局函数是 ES 标准中规定的,浏览器厂商已经实现,不需要就可直接调用的函数
包括: W3C 手册=>JavaScript=>JavaScript 对象=>JS Functions
常见: isNaN(num), parseFloat/Int(str)
反例: BOM: alert(xxx), prompt(xxx)
经验: 凡是用才能访问的都不是全局函数如 document.write(xxx)、console.log(xxx)
30. 编码解码
(1). 基本信息
url 不支持多字节字符,会出现乱码,只要 url 中包含多字节字符,要先编码再发送
编码是将 url 中的多字节字符(汉字)转为由单字节组成的编码
解码是将编码后的单字节字符序列再转回多字节字符原文
(2). 使用方法
编码: var 编码后的 url=encodeURI(带多字节字符的 url)
解码: var 多字节原文=decodeURI(编码后的 url)
问题: url 还不允许使用个别保留字符: 比如: ":","/"
(3). 完美解决方案
编码: var 编码后的 url=encodeURIComponent(带多字节字符的 url)
解码: var 多字节原文=decodeURIComponent (编码后的 url)
注意: 既可编码多字节,又可编码保留字符
31. eval: 执行字符串格式的代码
语法: eval("js 语句")
注意: 放入 eval 的语句必须是语法正确的,否则报错
32. isFinit(num): 判断一个数字是否在计算机可表示的有效范围内
计算机可表示的最大值: Number.MAX_VALUE
无穷大: ∞ Infinity ,除数为 0 时,会返回无穷大
33. 分支结构,让程序根据不同条件执行不同的任务
(1). 一个条件,一件事,满足条件才执行,不满足不执行
①. 如果代码简单,可用短路: 条件&&(操作)
②. 如果代码复杂,可用 if,如果 if 后只有一句话,可省略{}——强烈不建议使用
if(条件){
代码段
}
(2). 一个条件,两件事,二选一执行
①. 如果代码简单,三目/三元/条件运算
条件?满足条件才执行的操作(值 1):不满足条件才执行的操作(值 2)
②. 如果代码复杂,可用 if...else
if(条件){
满足条件才执行的操作
}else{
不满足条件才执行的操作
}
(3). 多个条件,多件事,多选一执行
①. 代码简单,使用三目运算,默认操作不可省略
条件 1?操作 1(值 1):
条件 2?操作 2(值 2):
默认操作;
②. 如果代码复杂使用 if...else if...:,最后一个 else 可省略
if(条件 1){
操作 1
}else if(条件 2){
操作 2
}else if(条件 3){
...
}else{
默认操作;
}
(4). 如果所有条件都是等于比较,可用 switch case:语句
switch(表达式){
case 值 1:
操作 1;
case 值 2:
操作 2;
default:
默认操作
}
switch case 执行的时全等比较,要求表达式值的数据类型必须和每个 case 的数据类型一致,只要一个 case 匹配,后续所有 case 都会触发,因此要在每个 case 和 default 之间插入 break;,表示终端执行,以突出 switch case 结构
34. 循环,循环就是让程序反复执行相同代码段,他可以让程序反复执行同一任务
循环有三大要素:
(1). 循环条件: 让循环可以继续执行的条件
(2). 循环变量: 循环条件中用作判断和比较的变量,循环变量的值,每循环一次,都要向着不满足循环条件的趋势不断变化,如果循环变量值不变,或循环条件始终为 true,循环无法退出形成死循环
(3). 循环体: 反复执行的代码段
35. 三种循环
(1). while 循环
语法:声明并初始化循环变量;
while(循环条件){
循环体;
修改循环变量的值;
}
当循环条件非常复杂时:
①. 使用 while(true),不用预设循环条件,也能先进入循环再说
②. 循环体中,根据条件,手动退出循环: break;
(2). do while 循环: 其实就是有一次试用机会的 while 循环
语法:声明并初始化循环变量;
do{
循环体;
修改循环变量的值;
}while(
循环条件
);
如果第一次循环条件都满足,则 while 和 do while 完全等效
如果第一次循环条件不满足,则 while 是一次都不执行,do while 至少可执行一次
(3). for 循环: 就是循环变量的变化有规律的 while 循环
语法:
for(声明并初始化循环变量;循环条件;修改循环变量的值){
循环体;
}
循环变量要考虑三件事:
①. 从几开始
②. 每次递增/减几
③. 到几结束
for 循环的简写:
①. for 的第一部分: 可同时声明并初始化多个变量
JS 语言没有块级作用域,if(){} 、while(){}、for(){}都不是作用域,其中的变量,在块{}外依然可用,只有 function(){}才是作用域
Java 语言中有块级作用域,if(){}、 while(){}、 for(){}都是块级作用域,其中的变量出了块{}就无法使用
②. for 的第三部分: 可同时执行多个短小的表达式,用逗号分隔
简写不能改变原程序的执行顺序
其实 if else、 else if 、while 、for 之后如果只有一句话都可省略大括号
36. 循环嵌套是在一个循环内,又执行了另一个循环,最常用的最 for循环嵌套
37. 数组是在内存中连续存储多个数据的存储空间,只要存储多个相关的数据,都要放在数组中,数组便于批量管理和操作多个相关的数据
程序=数据结构+算法,因此好的数据结构可极大提高程序的执行效率
创建数组的三种方法:
(1). 如果创建数组时,暂时不知道数组的内容,可创建空数组
var arr=[];
var arr=new Array();
(2). 如果创建数组时,已经知道数组的内容,可以创建初始化数组
var arr=[值 1,值 2,...]
var arr=new Array(值 1,值 2,...)
(3). 如果创建数组时,已知数组的元素个数,但暂时不知道内容时,可以创建 n 个空元素的数组
var arr=new Array(n);
38. 访问数组元素
数组中每一个数据称为一个元素,每个元素都有一个下标,下标(index)是数组中唯一标示一个元素存储位置的序号,从 0 开始,默认连续不重复
访问数组元素: arr[i],i 代表下标序号
每个数组元素的用法和普通变量完全一样,所以,数组也称为一组连续的变量的集合,共用同一个变量名
数组的三个注意事项:
(1). JS数组不限制元素的数据类型
(2). JS数组不限制下标越界
赋值: 如果下标越界,不报错,会自动在新位置创建新元素保存数据
取值: 如果下标越界,不报错,会返回 undefined
稀疏数组: 下标不连续的数组
(3). JS数组不限制元素的个数
39. 数组的 length 属性
属性是保存在对象中的一个变量,属性的用法和普通变量完全一样
访问属性: arr.length 注意: 访问 length 必须用 arr.,其中.读作"的"
length 属性记录了数组中理论上的元素个数,还可用来修改元素个数: arr.length--,相当于数组容量减1,length 属性始终等于最大下标加1,它标记着最后一个元素之后的下一个新位置
固定使用套路:
(1). 向数组末尾追加一个新元素:
arr[arr.length]=新值;
(2). 获取数组最后一个位置的元素:
arr[arr.length-1]
(3). 获取数组倒数第 n 个元素:
arr[arr.length-n]
(4). 删除末尾的 n 个元素:
arr.length-=n;
40. 数组是引用类型的对象
按值传递: 将两变量间赋值或将变量传递给函数作为参数,按值传递其实仅是将原变量中的值复制一个副本给对方
原始类型: 修改新变量,不影响原变量的值
引用类型: 用新变量修改对象,等效于直接修改原对象,原变量同样受影响
41. 垃圾回收
垃圾回收就是引擎会自动释放不再被任何变量引用的对象,浏览器中的垃圾回收器就是用于专门释放不再被使用的对象的小程序,它伴随主程序执行而执行,会自动回收不再使用的对象
每个对象都有一个引用计数器 count,记录该对象被几个变量引用着,每多一个变量引用对象 count 就+1,每当一个变量不再引用该对象时 count 就-1,垃圾回收器会定时检查每个变量的引用计数器,如果对象的引用计数器为 0,垃圾回收器会自动回收该对象
如果使用一个较大的对象后,应该尽快主动释放: 赋值为 null
42. 遍历数组
遍历数组就是依次访问数组中每个元素,并对每个元素执行相同的操作,只要对数组中每个元素执行相同操作时就需要用到遍历数组
固定套路:
for(var i=0; i<arr.length;i++){
arr[i] //当前正在遍历的元素
}
43. 关联数组
关联数组是下标可自定义名称的数组,由于索引数组的下标是无意义的数字,不便于快速定位想要的元素,如果给每个元素起一个有意义的名字,就可用名称,快速定义想要的元素
如何定义:
(1). 先定义空数组: var ym=[];
(2). 向空数组中添加新元素,下标使用自定义的名称字符串
ym["name"]="杨幂";
ym["math"]=81;
ym["chs"]=59;
ym["eng"]=89
访问关联数组的元素: ym["下标名称"]
44. 遍历关联数组(for in循环)
由于关联数组的下标都是自定义的字符串,导致关联数组 length 属性失效,始终为 0,for(var i=0;i<arr.length;i++)中 i 和 length 因此都不能用了
解决方法:
for(var key in ym){
// in 取出每个元素的下标保存到变量 key 中
// 要想进一步获得每个元素值: ym[key]
// 注意: key 是变量,不要加""
}
45. 数组 API
数组转字符串 2 种:
(1). String(arr): 将每个元素都转为字符串,然后用逗号连接
(2). arr.join("连接符"):将每个元素都转为字符串,可自定义连接符
固定套路:
(1). 将字符数组,拼接为单词,无缝拼接: arr.join("")
(2). 动态生成页面元素的内容
46. 拼接 concat
语法: var newArr=arr1.concat(值 1,值 2,arr2,...);
注意:
(1). 不修改原数组,而是返回新数组
(2). 将另一个数组作为参数,则首先打散数组为单个元素,再分别拼接
47. 选取 slice,复制出原数组中开始位置到结束位置之前的元素组成临时子数组
语法: var subarr=arr.slice(starti,endi+1);
注意:
(1). 不修改原数组,仅复制指定位置的元素,组成新的临时数组
(2). 凡是两个参数都是下标的函数,都含头不含尾
简写:
(1). 省略第二个参数表示从 starti 位置开始选取所有剩余元素
(2). 两个参数全省略表示复制一个数组
(3). 负数参数表示倒数第 n 个,一般用于位置离结尾近时arr.slice(-n)等效于: arr.slice(arr.length-n)
48. 修改数组 splice
(1). 删除
语法:arr.splice(starti,n),删除 arr 中 starti 位置开始的 n 个元素,会直接修改原数组
简写:
①. starti 支持负数参数,表示倒数下标
②. 省略 n 表示删除 starti 之后所有剩余元素
splice 是有返回值的,被删除的元素组成的临时数组
语法:var deletes=arr.splice(starti,n);
(2). 插入
语法:arr.splice(starti,0,值 1,值 2,....),在 arr 中 starti 位置插入值 1,值 2,...
注意: splice 不能打散数组参数,它会将数组作为一个整体保存在一个元素中——形成二维数组
Splice 与 concat 的区别
①. concat 不修改原数组,而是返回新数组,splice 直接修改原数组
②. concat 会打散数组参数,单个元素拼接, splice 不打散数组参数,将数组整体插入到一个元素中
③. concat 只能在数组结尾拼接, splice 可插入到任何位置
(3). 替换(先删除,再插入)
语法:arr.splice(starti,n,值 1,值 2,...)
注意:删除的元素个数和插入的新元素个数不必相同
49. 排序 arr.sort(),默认将所有元素,临时转为字符串,再按字符串,升序排列
(1). 默认的排序只能按字符串类型升序排列,如果要比较数字类型排序,就要定义比较器函数,比较器函数是比较任意两数大小的函数
定义比较器函数分为两步:
①. 定义比较器函数: function compare(a,b){return a-b;}
②. 将比较器函数作为 sort 函数的参数传入:arr.sort(compare)
注意: 将比较器函数作为 sort 的参数时,不加(a,b)
(2). 按数字降序排列
方法:颠倒比较器结果的正负号
语法:function compare(a,b){return b-a;}
50. 栈和队列
JS中没有专门的栈和队列结构,都是用普通数组模拟的,当程序中必须按一定的顺序使用数组元素时就要用栈和队列
(1). 栈(stack): 一端封闭,只能从另一端进出的数组,它的特点是先进后出,分两种:
①. 结尾出入栈
入: arr.push(值)代替 arr[arr.length]=值
出: var last=arr.pop();
②. 开头出入栈
入: arr.unshift(值)
出:var first=arr.shift();
注意: 开头入栈的结果和结尾入栈的结果刚好是颠倒的
(2). 队列(queue): 只能从一头进入,从另一头出的数组,它的特点是先进先出
结尾入: arr.push(值);
开头出: var first=arr.shift();
51. 二维数组
二维数组是数组中的元素又引用了另一个子数组,二维数组可以在在大数组中,对内部元素,进行更细致分类,还可以保存横行竖列的二维数据,创建数组有两种方法:
(1). 先创建空数组,再向数组中添加子数组
var arr=[];
arr[0]=[0,0,0,0];
arr[1]=[0,0,0,0];
(2). 创建数组同时,就初始化子数组
var.arr=[
[0,0,0,0], // 0
[0,0,0,0], // 1
[0,0,0,0], // 2
[0,0,0,0], // 3
]
(3). 访问元素:arr[r][c] 二维数组中每个元素的用法和普通数组元素的用法一样
注意: 访问二维数组: 列下标越界,不报错,返回 undefined,行下标越界,报错
遍历: 外层循环控制行,内存循环控制列
for(var r=0;r<arr.length;r++){
for(var c=0;c<arr[r].length;c++){
arr[r][c] //当前正在遍历的元素
}
}
52. String 是一串字符组成的字符数组
String 与数组的相同点:
(1). 下标
(2). length
(3). slice
String 与数组的不同点:
(1). 类型不同: String 不能使用数组类型的 API,因为 String 是只读的字符数组
53. 内置对象
ES 标准中规定的,浏览器厂商已经实现的类型共 11 个
String 、Number、 Boolean
Array 、Date 、RegExp 、Math
Error
Function 、Object
Global(在浏览器中被 window 代替)
原始类型的值本身不具有任何功能,如果要对原始类型的值做操作,必须有其他工具的辅助,包装类型专门用于封装原始类型的值,并提供操作原始类型值的 API 的对象,浏览器中内置了 3 种包装类型的对象,分别 对应一种原始类型
String Number Boolean
每种包装类型,都能够保存一个原始类型的值,并已经预定义了操作原始类型值的现成 API,当试图对原始类型的值调用函数时,引擎首先判断原始类型值的类型名,根据类型名,创建对应的包装类型的对象并保存原始类型的值,再调用包装类型对象预定义好的方法执行任务,任务完成后包装类型对象自动释放
54. String API
(1). 大小写转换: 将一个字符串中的字母统一都转为大写/小写,只要不区分大小写时,都要先转为统一的大小写,再判断和比较,比如:验证码
①. str.toUpperCase();全部转成大写
②. str.toLowerCase(); 全部转成小写
(2). 获得指定位置的字符:str.charAt(i) =>str[i]
(3). 获得指定位置字符的 unicode 号: var uncode=str.charCodeAt(i);
(4). 从 unicode 号转回字符: var char=String.fromCharCode(unicode);
(5). 获得子字符串: str.substring(starti,endi+1) => str.slice(starti,endi+1);
str.substr(starti, n) 选取 str 中 starti 位置开始的 n 个字符
注意: substring 用法和 slice 几乎完全一样
差别: substring 不支持负数参数=>可用 length-n 代替
55. 查找,在一个字符串中查找指定关键词出现的位置
(1). 查找一个固定的关键词出现的位置
①. 查找 fromi 位置后的下一个"关键词"出现的位置
var index=str.indexOf("关键词",fromi);
省略 fromi 将默认从头开始查找,每次只找一个,如果找不到,返回-1
②. 查找 str 中最后一个"关键词"的位置
var lastIndex=str.lastIndexOf("关键词");
③. 使用 index 查找时,存在一些不足
关键词稍微一变化,就找不到,比如: 我操、我草、我艹、卧槽,解决办法是用正则表达式模糊查找
(2). 判断是否包含指定关键词(支持正则)
var i=str.search(/正则表达式/)
在 str 中找符合正则表达式要求的敏感词的位置,返回值为返回第一个找到的敏感词的下标,如果没找到返回-1
正则表达式默认都是区分大小写的,如果一个字符串里的关键词有大写又有小写,为了能够判断全面,需要在正则表达式第二个/后面加后缀 i(ignore),表示忽略大小写
使用 search 的有以下两点不足
①. 永远只能找第一个
②. 只能返回位置,无法返回关键词内容
(3). 获得所有关键词的内容
var kwords=str.match(/正则表达式/ig)
查找 str 中所有和正则表达式匹配的关键词内容,返回值为包含所有关键词内容的数组,如果没找到返回 null
使用 match 存在以下两点不足:
①. 仅返回关键词的内容,无法返回每个关键词的位置
②. 正则表达式默认仅匹配第一个关键词,解决方法是在第二个/后加后缀 g(global),表示查找全部
(4). 即查找每个关键词的内容,又查找每个关键词的位置(见 58)
56. 替换,将找到的关键词替换为指定的新字符
(1). 简单替换: 将所有敏感词都替换为统一的新值
var str=str.replace(/正则/ig,"替换值");
注意: 所有字符串 API 都无权修改原字符串,只能返回新字符串
(2). 高级替换: 根据不同的关键词,动态选择替换不同的值
var str=str.replace(/正则/ig,function(kw){
return 根据本次 kw 的不同,动态决定返回何种替换值
});
(3). 删除,其实就是将敏感词替换为""
57. 分割,按指定的字符,将字符串分割为多段子字符串
var arr=str.split("分隔符" | /正则表达式/);
切割后的子字符串组成的数组,切割后的结果中,不包含切割符本身
固定套路: 将字符串打散为字符数组:
var chars=str.split("");
58. 正则表达式, 规定一个字符串中字符出现规律的规则
(1). 使用场合
①. 使用规则模糊查找多种关键词
②. 表单中验证输入项的格式
(2). 使用方法
①. 最简单的规则,就是关键词原文
②. 字符集,规定一位字符备选字符列表的集合语法: [备选字符列表]
注意:
①. 一个字符集[ ],包含的字符,再多也只能匹配 1 位
②. 不能不选
如果备选字符列表中部分字符是连续的,可用-省略中间字符,常用字符集如下:
①. [0-9]1 位数字
②. [a-z] 1 位小写字母
③. [A-Z] 1 位大写字母
④. [A-Za-z] 1 位字母
⑤. [A-Za-z0-9] 1 位字母或数字
⑥. [\u3e00-\u9fa5] 1 位汉字
⑦. 除了 xxx [^xxx]
(3). 预定义字符集: 4 个
①. \d 1 位数字: [0-9]
②. \w 1 位字母,数字或下划线: [ A-Za-z0-9_ ]
③. \s 1 位空白字符: 空格 制表符...
④. . 1 位任意字符
预定义字符集只能匹配固定字符个数的关键词,解决办法是用量词
(4). 量词,规定一位字符集出现次数的规则
①. 有明确个数限制: 3 种
A. 符集{m,n}至少出现 m 次,最多 n 次
B. 字符集{m,}至少出现 m 次,多了不限
C. 字符集{m}必须 m 位,不能多也不能少
②. 没有明确个数: 3 种:
A. 字符集?,可有可无,最多 1 个
B. 字符集*,可有可无,多了不限
C. 字符集+,至少一个,多了不限
一个量词只能修饰紧邻的前一个字符集,解决办法是分组
(5). 选择和分组,用( )将多个字符集,分为一组
语法:选择: 或(规则 1|规则 2),只要匹配任意一个规则即可
①. 身份证号: 15 位数字 2 位数字 1 位数字或 Xx
\d{15} (\d\d[0-9Xx] ) ? 问号代表后三位整体可有可无,最多 1 次
②. 手机号: +86 或 0086 可有可无,最多 1 次,开头为 1,中间 3,4,5,7,8 中任选一个,后面 9 位数字,空字符可有可无,多了不限
(\+86|0086)?\s*1[34578]\d{9}
③. 滴滴打车屏蔽“微信”字样: (微|w(ei)?)\s*(信|x(in)?)
(6). 指定匹配位置
①. ^字符串开头 ,比如: 开头的空字符: ^\s+
②. 字符串结尾 ,比如: 结尾的空字符:\s+
③. 开头或结尾的空字符: ^\s+|\s+$
④. \b 单词边界: 开头, 结尾, 空字符, 标点符号…
59. RegExp: (Regular Expression)
封装一个正则表达式,并提供使用正则表达式执行验证和查找功能的 API 的对象即为正则表达式对象(RegExp)
(1). 正则表达式对象一般有以下两种用途:
①. 格式验证
②. 查找关键词内容,又查找关键词位置
(2). 创建表达式对象有以下两种
①. 创建一个固定的正则表达式(不允许加入 JS 语句)
var reg=/正则/ig;
字符冲突: 两/之间如果再包含/,需要在内部的/之前加转义字符\
比如: /<\/option>/
②. 动态生成一个正则表达式(允许加入 JS 语句)
var reg=new RegExp("正则","ig");
字符冲突: ""之间如果包含\和", 都要在符号前加转义字符\变为\\和\"
比如: new RegExp("\\d{6}","ig")
(3). 验证格式
var bool=reg.test(str); 验证 str 是否符合 reg 的规则要求
问题: 正则表达式默认只要包含就算匹配
解决: 凡是验证,必须前加^,后加$,表示从头到尾完全匹配
(4). 查找关键词: 即查找所有关键词内容,又查找位置
var arr=reg.exec(str);
解释: 在str 中查找符合reg 要求的下一个关键词的位置和内容保存在数组 arr 中
返回值: arr: [0: 关键词, "index": 下标 ]
注意:
①. reg.exec 可自动从本次查找位置开始执行下一次查找,但是需要循环推动。
②. 要想找所有,reg 也必须加 g
60. Math 对象
Math 对象专门封装算数计算的常量和函数的对象,所有 API 都通过大写 Math 直接调用不能加 new
(1). 取整 3 种
①. 上取整: Math.ceil(num)
②. 下取整: Math.floor(num)
类型转换时,Math.floor 只能对纯数字的内容下取整,而 parseInt(str)可去掉结尾非数字单位
③. 四舍五入取整: Math.round(num)
Math.round(num)与 n.toFixed(d)有以下区别
A. round 只能取整, toFixed 可按任意小数位数四舍五入
B. 返回值: round 返回数字,可直接做计算
toFixed 返回字符串,做+运算前必须强转为数字
(2). 乘方和开平方
①. Math.pow(底数,幂)比如: Math.pow(10,2) =>10 的 2 次方=>100
②. Math.sqrt(num) 比如 Math.sqrt(9)=3
(3). 最大值和最小值:
①. Math.max(值 1, 值 2,....)②、Math.min(值 1, 值 2,....)
注意: 不支持获得数组中的最大/最小值
解决: Math.max.apply(null,arr);
(4). 随机数
Math.random(),返回 0~1 之间的一个随机小数 0<=r<1
公式: 在任意 min~max 之间取一个随机整数
parseInt(Math.random()*(max-min+1)+min)
简化: 从 0~n 之间生成一个随机整数
parseInt(Math.random()*(n+1))
61. Date 对象
Date 对象是封装一个时间,并提供操作时间的 API 的对象,它专门用来存储时间或计算时间
Data 对象创建的四种方法
(1). 创建日期对象,同时获得客户端当前系统时间
var now=new Date()
(2). 创建日期对象并保存自定义时间
var date=new Date("yyyy/MM/dd hh:mm:ss")
new Date(yyyy,MM-1,dd,hh,mm,ss)
计算机里面存储的月份为 0~11,所以第二个表达式里的 MM-1 的结果即为运行后的MM
(3). 使用毫秒数创建日期对象
日期对象中保存一个毫秒数,毫秒数不受时区影响,在跨国系统中存储时间,不要使用字符串,而要使用毫秒数,它专门用于将存储的毫秒数转化为日期对象
var date=new Date(ms)
(4). 复制一个日期对象:
日期计算时,都是直接修改原日期对象,无法同时保存计算前后的两个时间,如果希望同时保存计算前后的两个时间,就要在计算前,将起始时间复制一个副本,再用副本计算截止时间
var date2=new Date(date1);
(5). 日期 API:
FullYear、 Month 、Date 、Day
单数,无 s 结尾
Hours 、Minutes、 Seconds 、 Milliseconds
复数,有 S 结尾
①. 每个单位都有一对儿 get/set 方法
getXXX()负责获取指定单位的值
setXXX()负责设置指定单位的值
date.getDate() //获得 date 中的日期
date.getHours() //获得 date 中的小时
date.setDate(20) //将 date 中日期设为 20 号
date.setHours(11) //将 date 中小时设为 11 点
特例: Day 没有 set 方法,因为星期几是通过换算得到的,不能人为修改
②. 返回值/取值范围:
FullYear 年份该是多少就是多少
Month 0~11 计算机中保存的月份比现实中小 1
Date 1~31
Day 0~6
Hours 0~23
Minutes/Seconds 0~59
③. 日期计算: 2 种:
A. 两日期对象可相减,结果是毫秒差,一般用于计算倒计时或时间差
B. 对任意单位做加减: 3 步:
date.setDate(date.getDate()+/-n)
当前时间+30 天:
now.setDate(now.getDate()+30)
当前时间-2 个月:
now.setMonth(now.getMonth()-2)
问题: setXXX 会直接修改原日期对象,旧时间无法保存
解决: 先将起始时间复制一个副本,再用副本计算截止时间
④. 日期转字符串
A. String(date)
将 date 转为当地时间的完整日期格式
B. date.toLocaleString()
转为当地时间的简化版格式
C. date.toLocaleDateString()
转为当地时间的简化版格式仅保留日期部分
D. date.toLocaleTimeString()
转为当地时间的简化版格式仅保留时间部分
E. date.toGMTString()
转为国际标准时间(0 时区)
62. 错误处理
错误 bug 是程序执行过程中,导致程序无法正常执行的情况,它会让程序强行中断退 出,错误处理是使程序出现错误,也保证程序不异常中断的机制
语法: try{
// 始终执行
// 可能出错的代码
}catch(err){
// 发生错误时才执行
// 错误对象: 发生错误时,自动创建的
// 出错时,执行的错误处理代码
// 如: 通知用户,记录日志,保存进度
}[finally{
// 无论是否出错,都必须执行的代码
// 一般用于释放资源
}]
问题: 放在 try catch 中的代码,执行效率会降低,解决方法两种
(1). 尽量少的将代码放入 try catch
(2). 如果可提前预知错误原因,可用 if...else 代替 try catch,提前预防错误
(3). 抛出自定义异常: throw new Error("错误信息")
63. JS 中错误对象的类型 6 种
(1). SyntaxError
语法错误,程序的语法写错了
(2). ReferenceError:
引用错误,要用的变量没找到
(3). TypeError:
类型错误,错误的使用对象的类型,常见的有以下几种
①. 对非数组使用[]为错误
②. 对非函数使用()为错误
③. 对 null/undefined 用.xxx 为错误
④. 要调用的函数,对象中没有,比如: console.write(); document.log();
(4). RangeError: 范围错误,即参数值超规定范围,比如: toFixed(d) d: 0~20 之间 如果 写-2,就会超出它的范围
(5). URIError
(6). EvelError
64. Functions 常见的有以下 3 种创建方法
(1). 声明:
function 函数名(参数列表){
函数体;
return 返回值;
}
问题: 被声明提前
(2). 函数直接量:
var 函数名=function (参数列表){...}
优点: 不会被声明提前
本质: 函数是一个引用类型的对象,函数名只是一个引用函数对象的普通变量
(3). 用 new(了解)
var 函数名=new Function("参数","参数","函数体;...")
笔试: 以下创建函数方式正确的是:
function fun(a,b){return a-b}; //true
var fun=function(a,b){return a-b}; //true
var fun=new Function(a,b,"return a-b"); //false
var fun=new Function("a","b","return a-b"); //false
65. 重载: overload
重载是相同函数名,不同参数列表的多个函数,在调用时可自动根据传入参数的不同, 选择对应的函数执行,重载可以减少 API 的名字,减轻调用者的负担,只要一项任务,可 能根据不同的参数执行不同的逻辑时就要使用重载
JS 语法默认不支持重载,因为JS中不允许多个相同名称的函数同时存在,后定义的同 名函数,会覆盖先定义的,解决方案如下(2 步):
(1). 将多个函数合并为一个函数,省略参数列表
(2). 函数内,用 arguments 接住所有传入的变量值
arguments: 每个函数中自动创建的自动接收所有参数值的类数组对象,类数组对象即 为长的像数组的对象,它与数组有以下相同和不同
①. 相同
A. 下标
B. length
C. for 遍历
②. 不同,类型不同,类数组对象不能使用数组的 API,比如: childNodes children
arguments 使用时参数变量依然必要:
(1). 参数变量可指导调用者正确的使用函数
(2). 参数名都是有意义的,便于维护/提高程序可读性
(3). 自定义的参数名通常简洁
66. 匿名函数,匿名函数是在定义函数时,不指定函数名的函数,匿名函数可以节约 内存,一个函数只使用一次时就用匿名函数,它有两种使用方式
(1). 回调 callback: 将一个函数作为参数传给另一个函数中调用,比如
arr.sort(function(a,b){return a-b;})
str.replace(reg,function(kw){return ......})
addEventListener("click",function(){...}
setInterval/Timeout(function(){...}, ms )
特例: 如果函数可能被反复调用或可能被多个元素共享时,应该用有名的函数
(2). 匿名函数自调
匿名函数自调为定义一个函数后,立刻调用该函数,调用后立即释放,一般在划分临 时作用域,避免全局变量污染时使用,两种使用形式
①. (function([参数列表]){...})([参数值])
②. +function(...){}(...)
67. 作用域和作用域链
(1). 作用域(scope): 变量的可用范围
①. 全局作用域对象 window,全局变量的优点是可重复使用,随处可用,缺点是极 易造成全局污染,程序中尽量避免使用全局变量
②. 函数作用域对象 AO,它是局部变量,优点是仅函数内可用,不会造成全局污染 ,缺点是不可重用
(2). 作用域链(scope chain)
作用域链由各级作用域对象连续引用,形成的链式结构,在作用域链中函数的声明周 期如下:
①. 程序开始执行前,程序会创建全局作用域对象 window
②. 定义函数时,在 window 中创建函数名变量引用函数对象,函数对象的隐藏属性 scope 指回函数来自的全局作用域对象 window,就好像人的祖籍
③. 调用函数时,创建本次函数调用时使用的 AO 对象,在 AO 对象中添加函数的局部 变量,设置 AO 的隐藏属性 parent 指向函数的祖籍作用域对象——执行时,如果 AO 中没 有的变量,可延 parnet 向祖籍作用域对象找
④. 函数调用后,函数作用域对象 AO 释放,导致 AO 中局部变量释放
(3). 作用域链 2 项任务
①. 保存了所有的变量
②. 控制了变量的使用顺序: 先用局部,局部没有才延作用域链向下查找
68. 闭包(closure)
闭包即重用变量又保护变量不被污染的一种结构,它实现了全局变量可重用又兼顾了 局部变量不会被污染的两大优势,实现闭包分为以下 3 步完成
(1). 使用外层函数包裹受保护的变量和操作变量的内层函数
(2). 外层函数将内层函数返回到外部
(3). 调用者用外部变量接住返回的内层函数
由于外层函数的函数作用域对象,闭包结构比普通函数占用更多的内存,所以使用完 闭包后,要主动释放闭包,将引用内层函数的外部变量置为 null
69. 闭包面试方法(画简图)
(1). 找受保护的变量,即外层函数的局部变量,同时,在内层函数中被使用
(2). 找外层函数都返回了哪些内层函数用于操作受保护的变量,2 种手段
①. return function
②. 可直接给一个全局变量赋值一个 function
70. 面向对象,程序中用对象结构来描述现实中一个具体事务的属性和功能,面向对 象的三大特点: 封装、继承、多态,几乎所有大型项目都采用面向对象的思想,因为这样 更便于维护
71. 封装是用一个对象结构集中保存现实中一个事物的多个属性和功能,而对象是封 装一个事物的属性,并提供操作事物的功能的数据结构 ,现实中任何一个数据或一项功能都属于一个具体事物,这样有利于维护,使用面向对 象的方式,首先都要创建对象,创建对象有以下三种方法
(1). 对象直接量:
var obj={
属性名:值,
属性名:值,
方法:function(){
...this.属性名...
}
}
属性: 保存在对象中的一个普通变量
方法: 保存在对象中的一个普通函数
问题: 对象自己的方法,通常要使用对象自己的属性值
错误: 直接使用属性名当变量
正确: 使用 this 引用正在调用函数的“.”前的对象的关键词,只要对象自己的方法要 使用对象自己的属性,必须用 this.属性名
访问对象的成员: 成员=属性+方法
访问属性: 对象.属性名,用法和普通变量完全一样,如果要访问的属性不存在,不报 错,返回 undefined
调用方法: 对象.方法名(),用法和普通函数完全一样
(2). 用 new
①. var obj=new Object();
先创建一个空对象
②. obj.属性名=属性值;
再向空对象中添加属性和方法
obj.方法名=function(){
...this.属性名...
}
JS 中的对象可在任何时候添加新属性和方法,如果要赋值的属性不存在,不会报错, 而是在对象中动态添加该属性,JS 中对象的本质,就是关联数组,其实访问成员可用这 种形式: 对象["属性名"] 、对象["方法名"]()
JS 中如果要访问的属性名是固定的,就用“.”访问,如果要访问的属性名是动态生成 的,就用[ ]
用 new 创建对象一次只能创建一个单独的对象,反复创建多个相同结构的对象时,代 码重复严重,解决办法是用构造函数
(3). 用构造函数反复创建多个相同结构的对象
构造函数是描述同一类型的所有对象共同成员结构的函数,它可以实现代码重用,可以反复创建同一类型的多个对象,构造函数分两步完成
①. 定义构造函数
function 类型名(属性参数列表){
this.属性名=参数;
this.方法名=function(){
...this.属性名...
}
}
②. 调用构造函数创建一个对象
语法:var obj=new 类型名(属性值列表),原理如下
A. 创建新的空对象,将构造函数中的 this->新对象
B. 让新对象自动继承构造函数的原型对象
C. 执行构造函数中的语句,向新对象中添加新成员并赋值
D. 返回新对象地址保存到变量中
72. 继承是父对象的成员子对象无需重复创建,就可直接使用,代码重用,节约内存.
原型对象是集中保存同一类型的子对象共有成员的父对象,它在定义构造函数时,自动创建(买一赠一),它有以下两个特性:
(1). 使用构造函数创建子对象时,会自动设置子对象继承构造函数的原型对象
(2). 放在原型对象中的成员,所有子对象共用
如何访问构造函数的原型对象:
构造函数.prototype.成员名=值/function(){...}
总结:只要希望所有子对象共有的成员,都要添加到构造函数的原型对象中,而不应该放在构造函数内
73. 共有属性和自有属性
共有属性: 添加到原型对象中,所有子对象共有的属性
自有属性: 直接保存在对象本地的属性
访问读取: 两者用法完全相同: 对象.属性,先在对象本地找,没有才去父对象中找
属性修改:
(1). 自有属性: 对象.属性=值
(2). 共有属性: 子对象不允许修改共有属性,必须通过构造函数
语法:构造函数.prototype.属性=值
判断属性是否可用/包含: 使用"成员"in 对象,返回 bool 值,in 不仅可以判断自有属 性,而且判断父对象中的共有属性
74. 内置对象的原型对象
内置对象,也有构造函数和原型对象,构造函数负责创建指定类型的对象,原型对象 负责保存该类型子对象共有的 API, 它可以解决新旧浏览器 API 兼容性问题(2 步):
(1). 判断当前浏览器是否支持 API
(2). 如果不支持,就自定义一个 API 放入该类型的原型对象中
75. 原型链是由多级父对象逐级继承形成的链式结构,保存了所有对象的成员(属性和 方法),并定义了成员的使用顺序: 先用自有成员,自己没有,才延原型链向父对象查找 .
作用域链保存了所有变量(全局/局部),并定义了变量的使用顺序: 先用局部,如果没 有,才延作用域链向父级作用域查找
总结: 不需要对象,就可直接访问的变量都存在作用域链;必须用对象,才能访问的 对象成员都存在原型链中
76. 多态: 同一个方法,在不同情况下表现出不同的状态
JS 中仅支持重写(override),从父对象继承来的成员,不一定总是想要的,如果子对象 觉得父对象的成员不好用,可自定义自有成员,覆盖父对象中的成员
(1). 仅修改两个对象间的继承关系
Object.setPrototypeOf(child, father)
让 child 继承 father( 设置 child 的__proto__继承 father)
问题: 一次只能修改一个对象的继承关系
(2). 修改构造函数的原型对象来修改所有子对象的父对象
构造函数.prototype=新 father
注意: 必须在创建子对象之前更换
(3). 两种类型间的继承
问题: 如果两种类型间有部分相同的属性结构和方法,可用以下 3 步进行优化
①. 定义抽象父类型
父类型构造函数中定义公共的属性结构,父类型原型对象中定义公共的方法
②. 在子类型构造函数中借用父类型构造函数
错误: 直接调用父类型构造函数(),因为如果一个函数没有用 new 或任何对象.调用, 则 this 默认window
正确: 用 call/apply,将父类型构造中的 this 临时替换为子类型构造函数中的 this
父类型构造.call(this,参数....)
(4). 设置子类型的原型对象继承父类型的原型对象
77. ES5 保护对象
命名属性即可用“.”直接访问的属性,数据属性即实际存储属性值的属性,每个属性 其实由四大特性组成,它们为属性提供了保护功能
语法:{
// 实际存储属性值
value: 值,
// 控制是否可修改
writable: true,
// 控制是否可用 for in 遍历到
// 即使不能 for in,用“.”也能访问
enumerable:true,
// 控制是否可修改其他特性
// 控制能否删除该属性
// 改为 false 后不可逆
configurable:true,
}
获取一个对象的属性的四大特性:
Object.getOwnPropertyDescriptor(obj,"属性名")
修改一个对象的属性的四大特性:
Object.defineProperty(obj,"属性名",{
特性名:特性值,
... : ... ,
});
问题: 一次只能修改一个属性
解决: 同时修改多个属性
Object.defineProperties(obj,{
属性名:{
特性名:特性值,
},
属性名:{
特性名:特性值,
},
})
强调: 必须启用严格模式才能报错
问题: 只能进行简单的保护,无法自定义保护规则
解决: 访问器属性-不实际存储属性值,仅提供对另一个属性值的保护,定义方法如 下(2 步)
(1). 必须先定义一个隐藏的属性来实际存储属性值
(2). 定义访问器属性,提供对受保护的属性的读写操作和验证
Object.defineProperty(obj,"属性名",{
get:function(){
return //受保护的属性值
},
set:function(val){
//如果 val 符合规则
受保护的属性=val
//否则
抛出自定义错误
},
enumerable:true,
configurable:true,
})
强调: 访问器属性中要使用对象自己的属性,也必须加 this
使用: 访问器属性的用法和普通属性的用法完全一样,区别在于获取访问器属性值时, 自动调用 get 方法,实际获得的是受保护的隐藏属性的值
修改访问器属性值时,自动调用 set 方法,自动将新值传给 val,经过验证才修改,实 际保存到受保护的隐藏属性中
内部属性: 不可用“.”访问的属性: __proto__
防篡改: 防止在对象创建后,修改对象的结构/内容,共有 3 个级别
(1). 防扩展: 禁止给对象添加任何新属性
Object.preventExtensions(obj);
(2). 密封: seal 在防扩展基础上,再禁止删除现有属性
Object.seal(obj);
相当于将所有属性的 configurable 特性都改为 false
(3). 冻结: 禁止对对象做任何修改(包括结构和值)
Object.freeze(obj);
冻结实际完成了 3 件事
①. 禁止扩展
②. 修改所有属性的 configurable 为 false
③. 修改所有属性的 writable 为 false
78. 数组 API: 3 组
(1). 判断: 判断数组中的元素是否符合要求
①. every 判断数组中的元素是否*都*符合要求
var bool=arr.every(function(val,i,arr){
// val 当前元素值
// i 当前位置
// arr 当前数组
return 判断条件
})
②. some 判断数组中是否*包含*符合要求的元素
(2). 遍历: 对每个元素执行相同操作
①. forEach: 对原数组中每个元素执行相同操作,它会直接修改原数组
arr.forEach(function(val,i,arr){
arr[i]=新值;
})
②. map: 取出原数组中每个元素,执行相同操作后,放入一个新数组中返回,它不 修改原数组,仅返回新数组
var 新数组=arr.map(function(val,i,arr){
return 新值;
});
(3). 过滤和汇总
①. 过滤: 筛选出原数组中符合条件的元素组成新数组,而原数组不变
var subArr=arr.filter(function(val,i,arr){
return 判断条件
})
②. 汇总: 将数组中每个元素的值,汇总成一个最终结果,返回值是一个汇总结果
var result=arr.reduce(function(prev,val,i,arr){
return prev+val;//累加
},base);
问题: 仅简化了代码,未提高程序的性能,因为所有遍历 API 中使用的都是 for 循环遍 历每个元素
79. Object.create 方法:直接用父对象创建子对象,并扩展子对象的自有属性,多用于 只有父对象,也想创建子对象时
语法:
var child=Object.create(father,{
新属性:{四大特性},
... : ...
});
80. call/apply/bind: 替换函数中不想要的 this
(1). call 临时替换函数中的 this,要求传入函数的参数必须单独传入
(2). apply 临时替换函数中的 this,要求传入函数的参数必须放入数组中整体传入
apply 会将数组打散为单个参数值分别传入
注意: call/apply 相当于调用函数,立刻执行
(3). bind: 永久绑定函数中的 this,bind 完成了 3 件事
①. 创建一个和原函数功能完全一样的新函数
②. 将新函数中的 this 永久绑定为指定对象
③. 将新函数中的部分固定参数提前永久绑定
注意: bind 绑定在新函数中的 this,无法被 call/apply 再替换
总结: 如果临时调用一个函数,立刻执行时用 call/apply,如果创建一个函数提前绑定 this 时,不一定立刻执行时用 bind
81. 严格模式特点 (在头部声明“use strict”即为严格模式)
(1). 修改常量的值是非法的
(2). 禁止给未声明的变量赋值
(3). 将静默失败升级为错误
(4). 匿名函数的this不再指向全局
82. ES6 模板字符串: 简化字符串拼接,当一个字符串需要动态拼接而成时,就要使用反引号 以简化字符串拼接
83. let: 解决声明提前的问题,声明一个变量,首选 let,它具有以下两个特点
(1). let 会将变量的作用域,限制在一个块内部,增加了块级作用域
(2). let 之前不允许再出现未声明的同名变量,避免了声明提前
84. 箭头函数: 简化所有回调函数
(1). 去掉 function,在()和{}之间加=>(箭头函数不支持 arguments)
(2). 更简化
①. 如果只有一个参数,可省略()
②. 如果没有参数,必须保留()
③. 如果函数体只有一句话,可省略{}
④. 如果函数体只有一句话,且是 return,可省了{}和 return
强调: 箭头函数简写后,函数中的 this 和外部 this 一致了
所以: 当回调函数内外的 this 不相同时,不能使用箭头函数简化
反之: 如果希望函数内外公用一个 this 时,就可用箭头函数简化回调函数
85. for...of: 简化普通 for 循环遍历
普通写法:
for(var i=0;i<arr.length;i++){
arr[i] //当前元素值
}
简化写法:
for(var val of arr){
val //当前元素值
}
问题 1: 仅适用于读取元素值的情况,不能修改原数组元素
问题 2: 只能遍历数字下标的索引数组和类数组对象,不能遍历关联数组中的元素值
86. class:简化: 封装、继承、多态
(1). 创建一个类型
①. 用 class 结构包裹构造函数和原型对象方法, 将类型名定义在 class 后
②. 构造函数的 function 函数名,改为 constructor
③. 所有原型方法省略"类型.prototype"和"=function"
(2). 两种类型间的继承
①. 不再需要 Object.setPrototype,而是 class 子类型 extends 父类型
②. 借用构造函数不再用 call/apply,而用 super(...),其中 super 中省略 this
(3). 语法示例:
// class关键字必须用严格模式
"use strict";
// 声明一个父类
class Emp{
constructor(name){
// 构造方法
this.name=name;
}
work(){
console.log(`ENAME:${this.name}`);
}
}
// 创建类的实例
var student=new Emp('Augus');
student.work();
// 声明一个子类继承父类
class Programmer extends Emp{
constructor(name,skills){
// 调用父类构造方法
super(name);
this.skills=skills;
}
work(){
return super.work()+`SKILLS:${this.skills}`;
}
}
var user=new Programmer('Zoe','Java');
console.log(user.work());
更多ES6新特性请查看本博客ES6新特性篇!