1、犀牛书–JavaScript权威指南(第六版)
下载软件猫头鹰!—>regexbuddy
猫头鹰:
上面写规则
下面写要检测的话
1、正则表达式:
正则表达式(Regular Expression):专门描述字符串中字符串出现规则的表达式。
可以用于:
验证字符串格式
查找敏感词(查水表)
定义正则表达式
普通字符
最简单的规则,就是一个关键词的原文
比如:“我草”
字符集:
问题: 第二个字符换成另一个同音字,就匹配不到了
例如: [草艹槽]
我[草艹槽]即可。
但是我cao却检测不到
小问题:
猫头鹰上面的规则换行符也可检测到,会影响匹配的结果!
扩展:修改规则,使其进一步匹配“卧槽”
[我卧][草艹槽操]
再如: 手机号规则:
简写:如果[]中部分备选字符连续,可用-省略中间字符
[3456789]可简写为[3-9]
猫头鹰中为何有蓝有黄?
可以用两种颜色区分是两个敏感词
其它简写:
要匹配一位小写字母:[a-z]一共26个
要匹配一位大写字母:[A-Z]一共26个
要匹配一位字母: [A-Za-z]共52个
要匹配一位字母或数字:[0-9A-Za-z]
要匹配一位汉字: [\u4e00-\u9fa5] unicode编码
为什么匹配一位字母,不区分大小写: [A-Za-z]而不是[A-z]
因为A-z之间不都是字母!
65-90 97-122
91-96是符号[ \ ] ^ _ `
会超出字母范围,包含部分特殊符号!
利用字符集简写定义车牌号规则:
[\u4e00-\u9fa5][A-Z]·[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]
From
regexper.com正则表达式转换为图像
正则表达式语法为四种最常见的字符集定义了最简化写法,称为预定义字符集。
digit 数字
space 空格
ignore 忽略
document 文档 js中特指当前网页
包括
\d
等效于 [0-9]\w
等效于[0-9A-Za-z]\s
可匹配空格、制表符Tab等空白.
所以手机号规则可进一步简写为:
1[3-8]\d\d\d\d\d\d\d\d\d
利用\s匹配一句英文中的每个空格
\s
数量词
问题: 手机号规则中\d写了9遍,车牌号规则中[0-9A-Z]写了5遍
原因: 一个字符集(\d或[0-9])只能匹配一位字符,要匹配9位字符,就必须重复写9遍
逐个字逐个规则匹配,内容符合规则,位数也要一致
数量词,是专门规定一个字符集可能出现次数的规则
今后,只要一个字符集在规则中可能连续反复出现多次,就要用数量词以简写方式定义出现次数。
数量词紧跟在其修饰的字符集之后,默认修饰相邻的前一个字符集
手机号中连续9个\d进一步简写
1[3-8]\d{9}
需求
让程序自动识别短信中的验证码!
短信验证码的数字可能是4或者6位,不能确定位数
其实,数量词包括两大类:
比如:\d{4,6}表示4-6位数字
字符集{n,}表示字符集匹配额内容至少重复n次,多了不限,
比如: \d{6,}表示6位以上数字
有明确数量边界的数量词
*
可有可无,多了不限?
可有可无,最多一次+
至少一次,多了不限\s*
\s+
\s?
匹配字符串中一组连续空字符\s+
手机验证码: \d{4,6}
\d{4,6}既能匹配4位数字,又能匹配6位,那么先匹配4还是6位?
正则表达式,大部分情况下,默认采用贪婪模式匹配。
总是尽量匹配最长的关键词,能匹配到6位,就不匹配4位
问题:
屏蔽敏感词时,把字换成拼音就查不出来了!
选择
正确做法: 选择,是指在多个子规则中选其一匹配
只要在多个子规则中选其一匹配时,就用选择
如何?
子规则1 | 子规则2
满足规则1或满足规则2
"|"选择符只分左右,不考虑单个字符
分组
分组,将多个子规则视为一组,再和分组外的规则匹配
只要希望将多个子规则视为一个整体,再和其它规则匹配时,就用分组
如何?
其它规则(多个子规则)
匹配"我草"或“我cao”
我(草|cao)
我([草艹槽]|cao)
可能在中间加不确定个数的空字符:
([我卧]|wo)\s*([草艹槽]|cao)
一些问题:
为什么在一个字符串中可以匹配多个敏感词?
\d{4,6}
12345764787875
猫头鹰软件中,匹配时,都是用正则从字符串开头,一直匹配到字符串结尾,只要找到符合条件的就高亮显示。
(我|w|wo)只能匹配到w,匹配不到wo
| 不遵守贪婪模式,而是采用类似短路逻辑的模式。
如果前一个规则已经匹配,则后一个规则,不再匹配!
我|wo?
总结:|
不到万不得已,不用!
定义完整的手机号规则:(+86|0086)
+86或0086至少一个空字符:\s+
上面的整体可有可无,最多一次: ()?
\转义字符
((\+86|0086)\s+)?1[3-9]\d{9}
定义完整的身份证号规则:
15位数字: \d{15}
2位数字: \d\d
最后一位: 1位数字或X: [0-9X]
最后三位可有可无,最多一次()?
\d{15}(\d\d[0-9X])?
如果只希望匹配特殊位置上的关键词时,就可用特殊符号表示特殊位置
包括:
^ 表示字符串开头
$ 表示字符串结尾
\b 表示单词边界,可以匹配:
匹配任意一组连续空字符\s+
仅匹配开头的空字符^\s+
仅匹配结尾的空字符\s+$
同时匹配开头和结尾的空字符:
^\s|\s+$
^\s|\s+$
和^\s*|\s*$
有什么差别?
在开头或结尾没有空字符时,^\s*|\s*$
即使没有空格也会多匹配两次,即使开头和结尾没有,也要白白执行两次没用的删除操作!
找到每个单词的首字母
所以\b[a-z]
\b表示位置,位置不等于字符。前后不是一种语言等等。是0宽度的。
,no
字符串: , | no
逗号 边界 单词no
使用正则匹配电子邮件:
String的正则函数:
查找敏感词: 4种
var i = str.indexOf(“敏感词”,fromi)
在str中,从fromi位置开始,向后找下一个"敏感词"的下标位置i
如果fromi参数没给,默认从开头位置(0位置)开始向后找
返回:如果找到,返回敏感词第一个字的下标位置
如果没找到,返回-1,返回-1,说明不包含敏感词!
问题,indexOf只能查找一种固定的敏感词!而且不支持正则!
用正则表达式模糊查找多种敏感词:
var i = str.search(/正则/);
原理和用法同indexOf
查别
问题:所有的正则默认区分大小写!只能返回位置,不能返回内容
解决:只要在//的第二个/后加后缀"i",i是ignore的意思,忽略。
替换
切割
indexOf例子:
<script>
var msg = prompt("输入消息");
var i = msg.indexOf("我草");
if(i!=-1){
document.write("有敏感词
");
}else{
document.write("消息内容:"+msg);
}
</script>
search:(注意后缀i)
<script>
var msg = prompt("输入消息");
var i = msg.search(/([我卧]|wo)\s*([草艹槽]|cao)/i);
if(i!=-1){
document.write("有敏感词
");
}else{
document.write("消息内容:"+msg);
}
</script>
Vscode小问题:
Open a folder or workspace…(File–>Open Folder)
VScode不能只打开一个文件,要打开就必须打开文件所在文件夹!
String的正则函数:
查找敏感词4种
查找一个固定的敏感词出现的位置
indexOf
获取敏感词的内容: 2种
只获取一个敏感词的内容和位置:
var arr = str.match(/正则/i);
在str中,查找第一个符合正则要求的敏感词的内容和位置!
str.match(/(微|w(ei)?)\s*(信|x(in)?)/i)
咱们微信xx
返回值,关联数组:[“微信”,“2”]
0 “index”
获取找到的敏感词内容: arr[0]
获取敏感词的位置: arr["index"]
不可更改,match内部建立好,返回给我们的!
如果没找到,返回null
问题:
解决:
global
全部的意思查找所有敏感词的内容
var arr = str.match(/正则/ig);
在str中查找所有符合正则要求的敏感词
问题:如果match加了g找到多个敏感词,只能返回内容,无法返回位置
没找到返回null
arr.length表示查找到几个
今后,只要只关心敏感词内容,不关心敏感词的位置,用match+g
微信match例子
<script>
var msg = prompt("输入消息");
var arr = msg.match(/(微|w(ei)?)\s*(信|x(in)?)/i);
if(arr!=null){
//(向)文档(中)写(一段html片段) 今后几乎不用
document.write(`在
${arr["index"]}发现有敏感词${arr[0]}`);
}else{
document.write(`消息内容:
${msg}`);
}
</script>
arr[“index”]可以访问"index"位置的元素,也可以用arr.index访问,arr[“length”]可以访问数组长度,arr.length也可以访问数组长度,为什么?
JS中一切对象底层,其实都是关联数组(不是索引数组!)
任何对象和数组的成员,都可以用[“下标”]访问
只要是非数字的下标,都可以用.
简写
任何对象和数组都可以在任何时候随意添加新成员。任何对象和数组不存在位置,都不会报错,而是返回undefined!
所以,arr[“index”]可简写为arr.index
arr.length也可以改为arr[“length”]
**数字下标不能简写!**只能用[]访问!
arr.0和小数冲突了!
有多个关键词怎么办?
既查找每个关键词的内容,又查找每个关键词的位置
需要借助RegExp对象的exec()函数解决
替换
简单替换:
将所有符合正则的敏感词替换为统一的新词,
str.replace(/正则/ig,“新词”)
问题:
替换后,不报错,也替换不成功
字符串都是不可变类型!
无法对原字符串直接修改,
所有字符串函数,都只能返回修改后的新字符串。原字符串不变!
数组是可变类型
[].reverse()翻转数组
高级替换:
根据每次找到的敏感词不同,动态选择新词进行替换
str.replace(/正则/ig,
//`/\b[a-z/ig`
/*回调函数:
replace会自动在每个找到的关键词上调用一次这个回调函数,找到几个就反复调用几次!
自动将找到的值传递给回调函数,用返回的新词替换找到的词
//两个要求
*/
function(keyword){
//必须有一个参数接住本次找到的一个关键词
//必须返回处理后的新词
return 'xxx';
//keyword.toUpperCase();
})
如果找不到,不会报错,只不过什么也不做
如果一个函数有可能返回null,则使用前必须先验证不是null才能使用。
回调函数callback:
回调函数,我们自己定义的,但是不是由我们自己调用执行,而是交给其它对象去执行。
衍生: 删除敏感词,其实就是将敏感词替换为""
定义三个函数,分别删除开头的空字符,结尾的空字符,以及同时删除开头和结尾的空字符
function ltrim(str){
return str.replace(/^\s+/,"");
}
将一个字符串,根据指定的切割符,切割成多段子字符串。
简单切割
切割符是固定的
var arr = str.split(“切割符”)
复杂切割
切割符是变化的,但是有规律的。
var arr = str.split(/正则/)
将字符串str,按符合正则的切割符,切割成多段子字符串。
var kwords = prompt("输入搜索关键词")
kwords = kwords.split(/\s+/)
去掉不确定个数的空格
打散字符串
var arr = str.split("");
str=arr.join("")重组字符串
查找,替换。删除,切割,打散敏感词
即查找每个敏感词的内容,又查找每个敏感词的位置
什么是RegExp对象
专门保存一条正则表达式,并提供用正则执行查找和验证方法的对象
什么时候用?
验证
高级查找
创建对象
var reg = /\b[a-z]/ig
reg.test(“字符串”)
RegExp对象的test()
方法用于测试给定的字符串是否符合条件。
用reg规则,检测str是否符合规则要求,返回true和false
test默认只要在str中找到部分和reg匹配,就返回true而不要求从头到尾完全匹配。
解决:
今后凡是验证,必须从头到尾必须完整匹配,前加^,同时后加$
意为从头到尾!
var arr = reg.exec(str);
如果正则表达式中定义了组,就可以在RegExp
对象上用exec()
方法提取出子串来。
exec()
方法在匹配成功后,会返回一个Array
,第一个元素是正则表达式匹配到的整个字符串,后面的字符串表示匹配成功的子串。
既查找每个关键词的内容,又查找每个关键词的位置
var arr = reg.exec(str);
在str中查找下一个关键词的内容和位置
返回值:和match不加g的时候是一样的。
因为exec有.lastIndex=0标记了下次从哪里开始查找
reg.lastIndex+=本次找到的关键词的长度,下次从本次关键词之后开始匹配
需要循环调用
arr[0:关键词的内容,index:位置]
如果没找到,返回null
什么是:
用途
保存一段可重用的代码段的程序结构,再起一个名字
本质:
内存中保存一段代码段的存储空间。(对象)
为什么?
代码重用
什么时候?
一堆代码可能被反复使用
第一种
function 函数名(形参列表){
函数体
return 返回值
}
什么是形参?
专门接收从函数外部传入函数内数据的变量
为什么?
有些函数执行时,需要动态获得必须的数据,才能正常执行。
只要一个函数,必须某些数据才能正常执行时。
返回值:
一个函数的执行结果
为什么?
外部调用者可能需要获得函数的执行结果。
什么时候?
只要外部调用者,需要获得函数的执行结果时。
调用函数:
var 变量 = 函数名(实参值列表)
调用函数名,等于调用函数中的函数体
实参值以赋值方式传递给形参变量
如果函数有返回值,则用变量接住。
如果一个函数,只是定义,没有调用,其内部代码是不执行的。
只有调用才能执行。出错也不会发现、报错。
问题: 会被声明提前
声明提前:
在程序开始执行前,程序会将var声明的变量和function声明的函数。提前到当前作用域的顶部集中创建。而赋值留在原地(=)
hoist钩子
找var变量
function函数
第二种
赋值方式创建
var 函数名 = function(形参列表){
函数体
return 返回值
}
说明:赋值方式创建的函数,和声明方式创建的函数在使用时,是完全一样的!
只不过在程序开始执行前,赋值方式可以避免函数被声明提前。
保持了程序原有的执行顺序。
JS核心理念:
一切对象都是关联数组
JS中其实函数也只是一个普通的对象而已。函数名仅仅是一个普通的变量。函数名变量通过对象地址引用着函数对象。每次调用函数名时,都是通过地址,找到函数对象,再执行其中的内容。
浏览器:
window(全局作用域对象)
变量:内存中存储一个数据的存储空间,再起一个名字。
浏览器全局对象是window没有栈
NodeJS中是global
用new创建(几乎不用)
var function = new Function(“形参1”,“形参2”,…,“函数体和返回值”)
overload重载
多个同名函数,不同形参列表,在调用时,可根据传入实参列表的不同,动态选择匹配的函数执行。
为什么?
减少函数个数,减轻调用者负担!
什么时候?
只要一件事,可能根据传入的参数不同,执行不同的逻辑时,都要用重载。
问题:
JS不支持标准的重载写法。
因为JS不允许多个同名函数同时存在!
解决:
JS中借助于arguments对象来实现重载
什么是?
每个函数内自带的。(不用创建可以直接使用)专门接受所有传入函数的实参值列表的类数组对象。
接受所有传入函数的实参值
即使没有定义形参变量,或形参变量个数少于传入的实参值个数,都没关系!
arguments可以接住所有传入函数的实参值。
这就是为什么JS中的函数,定义了几个形参和调用传入几个实参,毫无关系。
类数组对象:
像数组的对象。
像数组:1、下标
2、length
不是数组,是对象。
argument参数
params参数
function pay(){
//arguments[100,].length=1
console.log("xxxxxx")
}
只要在JS中,接收不确定个数的参数值,都用arguments
1、无论传入多个参数,都只定义一个函数。
2、在函数内直接访问arguments,根据arguments的不同,动态选择不同的逻辑执行任务。
还可以用typeof判断arguments[i]的值的类型
调用函数时的实参列表中间,不允许出现空值。
当多个参数不确定有没有,又要求对应关系时,只能用对象语法解决
什么是:
定义函数时,不被任何变量引用的函数
str.replace(/正则/g,functuon(kword){
retrun ...
})
中就是匿名函数
为什么?
1、节约内存
2、划分临时作用域!
什么时候?
1、如果一个函数只能用一次时
2、划分临时作用域时
如何:
1、回调函数—今后绝大多数回调函数都要定义为匿名函数------>节约内存!
2、匿名函数自调:定义函数后,立刻调用函数,调用后立刻释放!
(function(){
.....
})();
问题?
全局变量极易被污染,所以今后禁止使用全局变量
为什么?
解决:
今后所有JS代码,都要包裹在匿名函数自调中。
好处:
绝对不会产生全局变量,节约内存,又不影响功能的执行。
作用域(scope范围):
什么是?
用途:
作用域就是一个变量的可用范围
本质:
作用域是保存变量的一个对象。
为什么?
为了避免不同范围的变量间互相干扰!
包括:
JS中只包括2级作用域
全局变量
优点:共用,可以反复使用
缺点:容易被污染,浪费内存
全局作用域对象------window
保存仅在函数内才可以使用的变量的区域
函数作用域保存的变量是局部变量
局部变量:
优点:仅函数内可用,不会污染全局,且用完就释放,不占用内存!
缺点:无法重用!
Java是三级作用域,全局-函数-块
块级作用域:
if else…这些程序结构的{},在Java中也是一级作用域
JS中没有块级作用域。
只要函数内有,就不用全局的。
作用域链(scopes)
函数的作用域链(好友列表)
什么是?
一个函数可用的所有作用域对象的集合
普通函数的作用链,在调用时是两个成员:
优先临时创建的函数作用域对象
其次全局作用域对象window
一个函数的作用域链
闭包(closure闭合):
什么是?
即重用一个变量,又保护变量不被污染的一种编程方法
本质:
外层函数的作用域对象,被内层函数对象引用着,无法释放。这个外层函数的作用域就是闭包对象。
为什么:
全局和局部变量都有不可兼得的优缺点。
什么时候?
今后只要为一个函数保存一个专属的,可重用的,不会被外部污染的变量。
如何?
1 用外层函数包裹受保护的变量和内层函数
function parent(){
var total = 1000;
function pay(money){
total -= money;
......
}
}
2 外层函数将内层函数return到外部!
function parent(){
var total = 1000;
return function(money){
total -= money;
......
}
}
3 调用外层函数,获得内层函数的返回的函数对象,保存在变量中,反复使用。
var pay = parent();
pay(100)
pay(100)
pay(100)//700
问题:
parent的局部变量total,在parent()调用后,按理说,应该释放。
但是竟然没释放,还反复使用了。
三步:
1 外层函数包裹要保护的变量和内层函数
内层函数一定要使用了外层函数的局部变量
2 外层函数将内层函数抛出到外部
3 调用者调用外层函数,获得返回的内层函数对象,保存在变量中,并反复使用。
闭包是如何形成的?
外层函数的作用域对象,被内层函数对象引用着,无法释放。这个外层函数的作用域就是闭包对象。
闭包缺点?
1、比普通函数占用更多的内存
多占用父母的函数作用域对象
2、闭包不会自动释放,可能造成内存泄漏
解决:
使用完闭包后,如果不再使用,要手动释放
pay=null;
什么是对象?
对象是描述现实中一个具体事物的属性和功能的程序结构。
本质:
程序中集中存储一个事物的属性和功能的一块存储空间,再起一个名字
为什么?
便于大量数据的维护和使用
什么时候?
所有程序都是用面向对象方式实现的
什么是面向对象?
程序都是先将数据封装在对象中,然后按需使用对象中的成员
这样的编程方式就称为面向对象编程
如何?
三步/三大特点:封装,继承,多态
封装:
什么?
创建一个对象,集中保存一个事物的属性和功能。
为什么?
便于大量数据的维护和使用
什么时候?
只要使用面向对象方式编程,都要先将数据和功能封装在对象中,然后再按需使用。
三种:
第一种
1、用{}创建一个对象
var 对象名 = {
属性名:值
方法: function(){}
}
访问:
访问对象的成员
对象名.属性名
其实属性就是保存在对象中的变量而已
要找到属性,得先找到对象,再用.操作符,进入对象中,访问属性
访问对象的方法
对象名.方法()
其实方法就是保存在对象中的函数而已。先找到对象,然后用.操作符进入对象中,才能找到内部的方法加()调用。
{}创建一个新对象的意思。
问题:
在对象自己的方法,直接使用属性名却无法访问到自己的属性值。
报错: 属性名未定义!
原因:所有不带.的变量,默认只能在作用域链(临时函数作用域和window)中查找!
但是对象的{}又不是作用域,所以对象是不包含在作用域链中的。所以,直接使用属性名,无法找到藏在对象内的属性的。
不好的解决办法:
在属性名前加"对象."
问题:紧耦合!如果外部修改,内部被紧跟着修改!
解决:松耦合:外部改变,内部代码不用变!也能自动适应!
-----this.属性名
总结:
只要对象自己的方法,想访问自己的属性,都必须加this.
在调用函数时,临时指向正在调用函数的**.前**的对象的关键词!
函数定义在哪个对象中,this不一定指向那个对象!
正确:this与函数定义在哪无关!只与函数调用这一顺间,.前的对象有关!
看何时调用判断this
第二种
用new创建
1
先创建空对象
var obj = new Object()
强行向空对象中添加新属性:
obj.属性 = 值
obj.方法 = function(){...}
揭示:
所有js对象底层,其实都是关联数组!
对象 vs 关联数组
相同点:
都可用[“成员名”]或.成员名两种方式,访问自己的成员!(.成员名其实就是[“成员名”]的简写)也就是说.成员名 到底层会被自动翻译为[“成员名”]
随时可以给数组或对象在任何位置添加新成员,而不会报错。而是自动创建该成员。
访问数组或对象中不存在的位置不会报错,而是返回undefined
都可被for in 循环遍历
不同:
关联数组–数组
对象–Object
克隆一个对象
function clone(obj){
return newObj;
}
var xx = clone(xxx)
如果成员来自于一个变量!则不能".变量"方式访问成员。因为.变量会被翻译为[“变量”],因为变量不能放在引号里!
解决:
今后只要成员名不是写死的,而是来自于一个变量,只能用[变量]
不带引号方式访问!
var xx = xxx
用等号赋值,不是新建对象(没有新建对象),而是引用的同一个对象!
不是克隆一个新对象,而是仅仅赋值原对象的地址给新变量。两个变量用相同的地址引用着同一个对象!
任何一方修改对象,都等效于直接修改原版
要用var xxx = clone(xx)
function clone(obj){
var newObj={}
for(var key in obj){
newObj[key]=obj[key]
}
return newObj;
}
constructor
问题:只能创建一个对象,如果反复创建多个相同结构的对象。代码很冗余,不便于维护!
解决:
用构造函数------>
什么是?
专门描述一类对象统一结构的函数!
为什么:
重用对象的结构定义
什么时候?
只要程序中需要反复创建同一类型的多个对象时。
定义构造函数来描述一类对象的统一结构
function 类型名(形参...){
this.属性名=值
...=...
this.方法名= function(){
}
}
调用构造函数按照统一结构创建对象
var obj = new 类型名(实参...);
什么是继承
无需重复创建就可以使用
为什么?
代码重用,节约内存
问题:
构造函数虽然实现了代码重用,但是浪费了内存!
凡是构造函数中规定的属性和方法,最终都会添加到孩子身体里
什么时候?
只要多个孩子都需要共用的成员,都要通过继承使用!
如何?
JS实现继承都是通过继承原型对象来实现的
原型对象
专门集中保存一类子对象的共有成员的父对象
原型对象是在定义构造函数时,附赠的
只不过这个原型对象暂时是空的
如何找到原型对象?
每个构造函数,都有一个属性prototype指向这个原型对象
原型对象中有constructor指回构造函数
new让子对象的__proto__
属性指向构造函数的原型对象
凡是从__proto__属性指出的关系,都叫继承关系!
如何向原型对象中添加公有成员呢?
强行赋值
构造函数.prototype.成员名=值
例如:
Student.prototype.xxx = function(){
}
凡是构造函数Student产生的对象,都能.xxx()
来调用函数
原型对象,保存了所有子对象共同的特点!
自有属性和共有属性
自有属性:
保存在子对象中,归某个对象对象所有的属性
共有属性:
保存在原型对象中,归多个子对象共有的属性
获取属性值:二者没有任何差别!
修改属性值:
自有属性,可直接用子对象修改
共有属性:只能用原型对象修改!
内置对象的原型对象
任何一个类型,其实都是由构造函数和原型对象组成,内置对象也是这样
JS包含哪些内置类型
String Number Boolean
Array Date Math(对象) RegExp
Error
Function Object
global(对象)
所有内置类型也包含构造函数和原型对象:
构造函数: 创建该类型的子对象
原型对象: 保存该类型的所有子对象共有的方法和属性
比如:Array类型就包含2部分
数组类型构造函数
function Array(){
...
}
所以,创建数组可以使用new Array()
f Array(){[native code]}内置的
数组类型也有一个原型对象,包含所有数组对象共有的函数
为一个类型添加一个共有的自定义函数
什么适合?
只要项目中,经常对一个类型的对象做一种操作,而这个类型的原型对象中又不包含这种函数
为了避免写很多this,代码不容易理解,经常给this起别名
var arr = this
由多级父对象,逐级继承,形成的链式结构
作用:
保存了一个对象可用的所有成员
将来判断一个对象可以使用哪些成员,就看它的原型链上有什么成员
顶级object的__proto__
是null
function也有原型对象
一个函数,在不同情况下表现出不同状态
包括:
重载
重写
什么是??
在子对象中定义和父对象中新成员完全相同得成员来覆盖父对象中的成员。
继承来的成员不一定都好用
默认的toString()输出"[object 类型名]"
console.dir(now)//输出对象的存储结构
可以在对象内部写
var lilei={
sname:"Li Lei",
sage:11,
toString:function(){
return `{sname:"${this.name}",sage:${this.sage}`
}
}
自定义继承:
1、只更换一个子对象的父对象
子对象.__proto__=另一个父对象
问题:
__proto__
浏览器不推荐使用
解决:
setPrototypeOf()代替__proto__
Object.setPrototypeOf(子对象,新的父对象)
换多个子对象
在没有创建子对象之前,Student.prototype=father;
什么是?
ECMAScript标准的第五个升级版本
为什么?
JS这门语言,并不完善,有很多广受诟病的缺陷
包括哪些新规定:
什么时候?
所有JS程序都要运行在严格模式下!
严格模式新要求:
禁止给未声明的变量赋值
旧JS:强行给未声明的变量赋值
结果:会自动在全局创建
全局污染,内存泄漏
严格js中,强行赋值,结果报错!
在这段程序的顶部,写
"use strict";
避免全局污染,内存泄漏
Object.defineProperty(eric,"eid",{
writeable:false
})
将对象eric的eid属性改为只读!
strict 严格
assign:赋值
普通函数调用和匿名函数自调中的this.不再默认指向window,而是指向undefined
防止内存泄漏,和全局污染
什么是arguments.callee
是在函数运行时,专门获得正在执行的函数本身。
什么时候?
递归时使用
为什么?避免紧耦合,避免在函数内写死函数名
问题?
递归效率太低,重复计算量太大!
所以ES5严格模式,出于性能考虑,禁用了arguments.callee,暗示着不推荐使用递归算法!
绝大多数递归,都可以用循环代替
什么是?
控制对对象属性值或对象结构进行的随意的篡改!
为什么?
在旧得JS中,对象的属性和结构毫无自保能力。任何人可以在任何时候修改属性值为任何值,也可以随意添加属性和删除属性!
什么时候?
需要控制对对象的访问时,
如何?
保护对象的属性
ES5对对象的属性进行了重新的分类
如何保护数据属性:
property 属性
特指程序中的对象中的属性
在旧的JS中,数据属性其实就是一个普通的变量,毫无自保能力
ES5标准中:每个数据属性都是一个微缩的小对象。每个小对象中,保存一个属性值和三个开关!
eid:{
value:1001,//实际存储属性值
writable:true/false//是否可修改这个属性的值
enumerable:true/false
//是否可被for in遍历到
configurable:true/false
//是否可删除该属性
//是否可修改前两个开关
}
如何修改属性小对象内的开关属性来保护一个属性呢?
问题:
不能用.直接修改属性对象内部的开关属性!
解决:
必须用专门的函数
Object.defineProperty(
对象名,
"属性名",
{
开关:true/false
}
)
问题:
writable和enumerable两个开关,任何人都可以随意开关。所以,仅设置这两个属性,起不到保护的作用!
解决:
只要设置writable和enumerable,都要同时关闭configurable
目的是禁止修改前两个开关
configurable一旦改为false,不可逆!
删除属性
delete eric.ename
enumerable只能禁止for in遍历这个属性。无法·阻止用.直接访问这个属性。—只是半隐藏。防不住.直接访问!
无法解决!
问题:
Object.denfineProperty()一次只能修改对象中的一个属性。如果修改多个属性,就要重复写多遍
解决:
Object.defineProperties(
对象名,
{
属性名:{
开关:true/false
},
属性名:{
开关:true/false
}
}
)
问题:
使用保护属性,规则有些单调!(无法自定义规则保护属性)
解决:
使用访问器属性保护数据属性
自己不保存数据,专门提供对其它数据属性的保护!
何时?
只要使用灵活的自定义规则保护数据属性时,使用
如何?
先将要保护的数据属性,半隐藏!
为对象添加访问器属性,保护数据属性。
Object.defineProperties(eric,{
_eage:{
enumerable:false,
configurable:false
}
//访问器属性,不能用.添加!
//只能用defineProperty或
//defineProperties添加
eage:{//访问器属性替换原属性名
get:function(){
},
set:function(value){
}
}
})
当外部试图获取eage的属性值时,自动调用get
由get代为从受保护的属性中拿出真实值返回给外部
当外部试图修改eage的属性值时,自动调用set
将要赋的新值,先交给value
在set内部使用自定义规则验证value
保存会受保护的数据属性中
this._eage=value;
否则
throw Error("xxxx")
属性enumerable:true
需要代替受保护的数据属性
configurable:false,不能随意删除
保护对象的结构
恶意添加新属性
禁止添加新属性:
Object.preventExtensions(obj)
防扩展:
prevent阻止
extendsion扩展
阻止对obj对象添加任何扩展属性
原理:
每个对象内部都有一个隐藏的extensible的属性,默认值为true
preventExtensions(obj)其实就是将这个内部的隐藏属性改为false!
密封:
在兼具防扩展同时,进一步禁止删除现有属性
Object.seal(obj)
2件事:
其实绝大多数对象,都要密封!
强调:
虽然密封禁止修改结构,但是属性值随便改!
常见错误!
','expected 或 Unexpected identifier或者
Invalid or unexpected token
基本都是标点符号错误:
3 冻结:在兼具密封的基础上,进一步禁止修改属性的值!
Object.freeze(obj)
三件事:
Object.create()函数(没有构造函数也想创建一个子对象)
var 子对象 = Object(父对象,{
//defineProperties
属性名:{
value:属性值
writable:true/false
enumerable: true
configurable:true/false
},
......
})
必须用defineProperties中的语法!
call
call,apply,bind:替换this
什么时候?
只要函数执行时,内部的this不是我们想要的,就可以用这三个函数,将不想要的this换成任意一个想要的对象!
如何?
call,apply:
在这一次调用函数时,临时替换一次this!
calc.call(lilei,111,111,1111)
要调用的函数.call(替换this的对象,…)
强调:
实参值列表必须从第二个位置开始。因为第一个实参的位置让给了替换this的对象!
call的更大作用:可以让任何对象去调用原本没有任何关系的一个函数!
apply vs call:
apply和call用法几乎完全一样。只不过,要求所有实参值都要放在一个数组中整体传入!
执行过程:
总结:
通常情况下,要替换函数中的this,用call就够了!
只有实参值列表是放在一个数组中的时候,才需要apply,先打散数组,再传入函数。
问题:
只能一次性临时替换this.如果反复调用,反复替换,代码会很繁琐!
bind
不调用函数,而是基于原函数,创建一个新函数副本。并永久替换新函数中的this为指定的对象。
var lcalc = calc.bind(lilei,1000);
绑定新的,赋值给新变量
何时?
今后,如果一个函数需要反复调用,又反复替换其中的this时,都用bind()来创建副本,并永久绑定this
如何?
var 新函数=旧函数.bind(
替换this的对象,
要绑定的实参值,
......
)
结果:
新函数的功能和旧函数完全一样。
但是新函数中的this和开头部分形参,
已被永久替换为固定的对象和固定的参数值
今后调用新函数时,只要传入可能变化的剩余几个实参值即可!
数组函数:
查找元素:indexOf(ES5才有)
数组的indexOf和字符串的indexOf完全一样!
var i = arr.indexOf("元素",fromi)
在arr数组内,从fromi位置开始,找下一个和指定"元素"相同的元素所在的位置i
返回值:如果找到,返回i,没找到返回-1
判断:
判断数组中是否所有元素都符合要求
var bool=arr.every(function(elem,i,arr){
return 判断条件
})
原理:every会自动遍历arr中每个元素。每遍历一个元素,就自动调用一次回调函数function.每次调用function时,自动传入当前正在遍历的元素值和下标位置,以及当前数组对象。每次调用function,都会用函数内的条件检查当前元素是否符合要求,只有当前元素符合要求,才继续遍历下一个!只要碰到一个不符合要求的元素,就立刻退出遍历,返回false
判断数组中是否包含符合要求的元素
var bool = arr.some(function(value,i,arr){
return 判断条件
})
用法和every完全相同,只不过执行时,只要碰到一个元素判断为true,就不再遍历,直接返回true
绝大多数回调函数中的this都默认指向window
因为绝大多数回调函数的执行,都类似于匿名函数自调
如何判断一个对象是不是数组。共几种写法!
汇总:
对数组中的元素内容进行统计,得出最终结论。
var sum = arr.reduce{
function(prev,elem,i,arr){
//prev保存截止到当前元素的临时汇总值。
return 将当前元素elem,累加到prev中,得到新的汇总值。
},
起始值
};
ECMAScript标准的第6个升级版本
优点:在不改变原理的基础上,简化了js代码
问题:兼容性
ES6中引入了模板字符串,让我们告别了使用大量’’ 和 +来连接字符串了写法。
要创造一个模板字符串,只需要使用反引号``将字符串包起来,模板字符串中的变量用${变量名}替代即可
var a = 'o,';var b = 'd!'
console.log (`Hell${a}worl${b}`);
// Hello,world!
1、多行字符串
在模板字符串内,换行和空格是会保存并展示出来的
var a = 'o,';var b = 'd!'
console.log(`Hell${a}
worl${b}`);
/*
Hello,
world!
*/
2、嵌入表达式
var a = 1; b = 2;
console.log (`${a} + ${b} = ${a + b} `)
//1 + 2 = 3
1
2
3
3、调用函数
function string(){
return "Hello!";
}
console.log(`${string()} world!`);
//Hello! world!
包括:
为什么?
只要声明变量都用let
let
优点:
1、阻止了声明提前
2、添加了块级作用域!
原理:
let其实就是一个匿名函数自调!而且let为了双保险,其实在底层给变量改了名字。(t======>_t)
在相同作用域/块内,禁止同时let两个同名的变量。
在let a之前到当前作用域的顶部之间,不允许提前使用a变量
declare:声明
initialization:初始化
access:访问
对普通函数声明的简写?
今后绝大多数匿名函数的创建,都是用箭头函数?
3件事:
去掉function在()和{}之间加=>
如果形参列表只有一个变量,可省略()
如果函数体只有一句话,可省略{}
但是,如果只有一句return必须省略return
去掉{}时,还要注意去掉结尾的;
双刃剑:
让函数内外的this连通,保持一致!都指向函数外的this
只要回调函数中的this错了,只能用bind换!
this.friends.forEach(
function(fname){
xxx
}.bind(this)
)
交给主函数的,已经不是原函数。而是一个bind创建的副本,且 永久替换了this.
主函数可以反复使用!
结论:
如果希望函数内的this和函数外的this不一致时!就不能用箭头函数!
对象的方法不能用箭头函数简写
DOM中的事件处理函数不能用箭头函数简写
特定情况下专门简化for循环
如何?
for(var elem of 数组){}
剩余参数(rest)
专门代替arugments
箭头函数中,无法使用arguments
rest语法
arrow箭
rest剩余
function fun(形参1,形参2,...数组名)
...后的数组中会接住除之前形参外的其它所有剩余实参值。
纯正的数组类型,即使将fun改为箭头函数,依然可以使用...数组名的rest语法
打散数组(spread散播)
将一个数组或对象打散成单个的变量值
代替apply
apply问题:本职不是打散数组,而是替换this顺便打散数组!
传参时
fun(...arr)
先将arr中的元素值打散成单个值,再分别传给fun()
项目中:
拼接两个数组
var arr1 = [1,2,3],arr2=[4,5,6];
arr=[...arr1,...arr2]
arr:[1,2,3,4,5,6]
合并两个对象
var obj1 = {x:1,y:2,z:3},obj2={a:1,b:2,c:3};
var obj = {...obj1,...obj2}
什么是?
将一个大的对象或数组中的个别成员提取出来,单独使用。
如果给的是一个巨大的对象,只需要个别函数或属性。
将大的数组中的个别元素提取出来单独使用。
下标对下标
[变量1,变量2,…]=数组
结果:
变量1=数组[0]
变量2=数组[1]
可以隔几个
[变量1,变量2,…]=数组
变量2=数组[2]
destruct解构
var date = [2019,8,5];
var [y,m,d]=date;
将大的对象中的个别成员提取出来单独使用。
如何:属性名对属性名
在企业中,都是将操作一种对象的所有函数,封装在一个大的对象中。
配对 自定义
var {signin:login,signup:register} = user;
不改名:
es6写一遍就行
简写
如果新变量的名字沿用成员在原对象中的属性名,则可以简写
即用作配对,又用作变量名
var {signin,signup} = user;
signin();
在定义函数和调用函数时,采用对象结构传递参数
为什么?
多个形参值不确定有没有,而要求实参值与形参值之间必须对象。
默认值:局限:只有最后一个形参不确定时,才能用默认值。
…rest和arguments;局限
无法让形参值与实参值一一对应。
两步:
定义函数时,形参就写成对象语法!
调用函数时,所有实参必须放在一个和形参对象结构相同的对象中传入
function cook({
dan:dan,
cai:cai,
mian:mian
})
function cook({
dan,
cai,
mian
})
面向对象上的简化:
对单个对象提供了2处简化:
所有对象的方法,不再需要写
:function
var eric={
run:function(){
....
}
}
var eric={
run(){
....
}
}
强调:
对象中的方法去掉":function"就不等效于箭头函数。突出的特点就是this保持原样不变!
所以只是单纯的简写,没有任何原理的改变!
class:
什么是?
集中定义一种类型的所有对象统一属性结构和方法的程序结构。
为什么?
每种类型Array Student Date都有两部分组成:构造函数+原型对象
构造函数:
负责定义所有子对象统一的属性结构,并且负责创建子对象
原型对象:
负责保存所有子对象共有的属性值和方法
但是在ES5中,构造函数和原型对象是分开定义的。
不符合"封装"的要求
今后只要希望创建一种自定义类型时,都用class
如何?
1 用class{}包裹构造函数和原型对象方法
2 构造函数名提升为class名,所有构造函数,统一更名为constructor
3 所有放在class中的函数,不需要加类型名.prototype前缀,自动就是保存在构造函数的原型对象中。
class Student{
constructor(sname){
this.sname = sanme;
}
run(){
...
}
}
如何使用?和从前的构造函数完全一样!
原型对象的原理依然保持不变!
两种类型之间的继承
问题:两种类型之间包含部分相同的属性和方法定义
解决:
定义一个上层的父类型,集中保存两种子类型相同的部分。
class San extends Enemy{
constructor(x,y,award){
super(x,y);
this.award=award;
}
}
promise:承诺
什么是:专门保证多个异步函数,可以顺序执行的机制。
而且还防止了回调地狱问题
什么时间:
多个异步调用的函数,要求必须顺序执行!
为什么?
其实用回调函数,也可以实现多个异步函数,顺序执行。但是,使用回调函数,会有回调地狱问题!
传统解决:使用回调函数:
在定义函数时,定义一个callback形参变量在函数内部,最后一句话执行之后,自动调用callback()
function xx(callback){
...
//执行完,自动调用提前传入的callback任务
callback();
}
在调用函数时,传入一个函数,函数中包含下一步执行的操作。
提前托付。
回调地狱!!!
1 用new Promise将争端代码包裹起来
new Promise(
function(door){
...
door()
}
)
将整个new Promise对象抛出到函数外部
return new Promise(
function(door){
...
door()
}
)
调用door()后执行then里的函数
xx().then(xxx).then(xxxx)
then内写函数
then(function(){
console.log("结束")
} )
xx().then(xxx).then(xxxx).then(()=>console.log("结束"))
xx{
function(){
xxxx();
}
}
为什么必须套一层function()
不能写成xx(xxxx())
xxxx()作为参数。但是因为function xxxx(){}是一件事,没有return意味着xxxx()返回undefined
xx得到的参数是undefined,而xx要的是一个可执行的函数对象。
xx(xxxx)是对的,xxxx没有加()说明不是立刻调用执行。
而是将xxxx这个函数对象本身交给xx()作为参数。
这符合xx的要求,
但是这样做,xxxx不能加()也就无法接第三个函数
解决:
xx(
function(){
下一步要执行的js语句
xxxx();
//好处:xxxx()中可接下一个函数
}
)
前后两个函数间传参
1 上一个函数中door(参数值)
2 下一个函数定义时就要定义一个形参准备接
原理:
当上一个函数调用door(参数值)时,参数值会顺着.then()交给.then()中的下一个函数的形参变量。
下一个中就可通过自己的形参变量获得上一步传下来的参数值。
局限: door()中只能传一个变量。
如果必须传多个值则可以将多个值放在数组或者对象中整体传入。
错误处理:
任何一个支持promise的函数,如果当前异步任务执行过程中发送错误,就会从另一个方法出来。
一旦报错。后续then()都不执行
其实new Promise()除了then外,还有另一个.catch()
.catch(function(错误信息){
错误处理!
})
等待多个异步任务完成才执行:
Promise.all([
必须是new Promise对象
xx(),//new Promise对象
xxx(),
xxxx()
])
多个支持promise
Promise.all([
必须是new Promise对象
xx(),//new Promise对象
xxx(),
xxxx()
]).then(function(){
...
})
问题:
每个异步任务有返回值
解决:.then(function(arr){…})
arr数组中返回值存储的顺序和异步函数执行完成的顺序无关。
只和调用的顺序有关!
var xx =""
door(xx);相当于return xx
只有每个人都door(),说明结束了。
door()不能用retrun代替
door()不仅有返回值的作用,还有修改promise状态的作用。
三种状态:
pending正在执行还没执行完(挂起状态)
resolve(同意)执行完成then()
执行成功,会将Primise对象的状态改为resolve,改为这个状态自动执行.then()中的函数
reject错误执行catch()
之前
暴露的本质是exports
引入
packagename不能有大写和中文
引入第三方库要定义在自定义库的前面
NodeJs端使用
文档(browserify)
https://browserify.org/
npm installl browserify -g
或
npm install browserify --save-dev
在浏览器端运行时会提示 require is not defined 此时需要browserify来进行打包
// 打包文件路径 输出文件路径 bundle打包的意思
browserify js/src/app.js -o js/dist/bundle.js
定义暴露模块
define(function(){
return 模块
})
define(['module1','module2'],function(m1,m2)) {
return 模块
})
(定义AMD,暴露CommonJS)
define(function(require,exports,module){
let msg = 'module2'
function bar() {
console.log(msg);
}
module.exports = bar
})
复制代码
define(function(require,exports,module){
let msg = 'module4'
// 同步引入
let module2 = require('./module2')
module2()
// 异步引入
require.async('./module3',function(m3) {
m3.module3.fun()
})
function fun2() {
console.log(msg);
}
exports.fun2 = fun2
})
复制代码
在浏览器使用
npm install babel-cli -g
复制代码
npm install babel-preset-es2015 --save-dev
npm install babel-preset-env
{
"presets":["es2015"]
}
暴露模块方式(常规暴露)
分别暴露
统一暴露
默认暴露(可以暴露任意数据类型,暴露什么数据接收到的就是什么数据
// 默认暴露
export default ()=>{
console.log('箭头函数');
}
//引入 名字任意取
import myFun from './module3'
引入其他模块 (对象解构赋值)
使用babel将ES6编译为ES5代码(包含CommonJS语法)
babel js/src -d js/build
browserify js/build/main.js -o js/dist/bundle.js
问题:
并没有彻底消灭嵌套:
xx().then(xxx).then(xxxx).then(()=>console.log("结束")).catch(function(err){
错误处理
})
解决: ES7 关键字async await
异步 等待
可按照传统的同步指定的代码一样,编写异步代码顺序执行。
只要多个异步任务需要顺序执行
(async function(){
同步代码;
var 返回值 = await 异步函数();
await 异步函数(返回值);
同步代码;
})();
其中: await可让争端匿名函数自调暂时挂起,等待当前异步函数执行完,在执行后续代码!
强调: ES7的async和await仅仅简化的是promise函数调用的部分。
而并没有简化Promise函数的定义。
如果想用await,则异步函数必须定义为支持promise的样式!
有了await,door就相当于return
错误处理
await会认为是程序错误
应该用try{}catch(err){}来解决
强调:
只有async下的try catch才能捕获异步任务
(async function(){
try{
同步代码;
var 返回值 = await 异步函数();
await 异步函数(返回值);
同步代码;
}.catch(err){
...
}
})();
普通js基础中的try catch属于主程序,不会等待异步任务执行,就已经结束。
await可留住当前程序中的一切代码。等待执行完才结束
Promise.all([
必须是new Promise对象
xx(),//new Promise对象
xxx(),
xxxx()
])
多个支持promise
Promise.all([
必须是new Promise对象
xx(),//new Promise对象
xxx(),
xxxx()
]).then(function(){
...
})
问题:
每个异步任务有返回值
解决:.then(function(arr){…})
arr数组中返回值存储的顺序和异步函数执行完成的顺序无关。
只和调用的顺序有关!
var xx =""
door(xx);相当于return xx
只有每个人都door(),说明结束了。
door()不能用retrun代替
door()不仅有返回值的作用,还有修改promise状态的作用。
三种状态:
pending正在执行还没执行完(挂起状态)
resolve(同意)执行完成then()
执行成功,会将Primise对象的状态改为resolve,改为这个状态自动执行.then()中的函数
reject错误执行catch()
之前
[外链图片转存中…(img-KSsf8ZqD-1648213853317)]
[外链图片转存中…(img-DDJge7et-1648213853318)]
暴露的本质是exports
引入
[外链图片转存中…(img-vmKceVJG-1648213853319)]
packagename不能有大写和中文
引入第三方库要定义在自定义库的前面
NodeJs端使用
文档(browserify)
https://browserify.org/
npm installl browserify -g
或
npm install browserify --save-dev
在浏览器端运行时会提示 require is not defined 此时需要browserify来进行打包
// 打包文件路径 输出文件路径 bundle打包的意思
browserify js/src/app.js -o js/dist/bundle.js
定义暴露模块
define(function(){
return 模块
})
define(['module1','module2'],function(m1,m2)) {
return 模块
})
(定义AMD,暴露CommonJS)
define(function(require,exports,module){
let msg = 'module2'
function bar() {
console.log(msg);
}
module.exports = bar
})
复制代码
define(function(require,exports,module){
let msg = 'module4'
// 同步引入
let module2 = require('./module2')
module2()
// 异步引入
require.async('./module3',function(m3) {
m3.module3.fun()
})
function fun2() {
console.log(msg);
}
exports.fun2 = fun2
})
复制代码
在浏览器使用
npm install babel-cli -g
复制代码
npm install babel-preset-es2015 --save-dev
npm install babel-preset-env
{
"presets":["es2015"]
}
暴露模块方式(常规暴露)
分别暴露
统一暴露
默认暴露(可以暴露任意数据类型,暴露什么数据接收到的就是什么数据
// 默认暴露
export default ()=>{
console.log('箭头函数');
}
//引入 名字任意取
import myFun from './module3'
引入其他模块 (对象解构赋值)
使用babel将ES6编译为ES5代码(包含CommonJS语法)
babel js/src -d js/build
browserify js/build/main.js -o js/dist/bundle.js
问题:
并没有彻底消灭嵌套:
xx().then(xxx).then(xxxx).then(()=>console.log("结束")).catch(function(err){
错误处理
})
解决: ES7 关键字async await
异步 等待
可按照传统的同步指定的代码一样,编写异步代码顺序执行。
只要多个异步任务需要顺序执行
(async function(){
同步代码;
var 返回值 = await 异步函数();
await 异步函数(返回值);
同步代码;
})();
其中: await可让争端匿名函数自调暂时挂起,等待当前异步函数执行完,在执行后续代码!
强调: ES7的async和await仅仅简化的是promise函数调用的部分。
而并没有简化Promise函数的定义。
如果想用await,则异步函数必须定义为支持promise的样式!
有了await,door就相当于return
错误处理
await会认为是程序错误
应该用try{}catch(err){}来解决
强调:
只有async下的try catch才能捕获异步任务
(async function(){
try{
同步代码;
var 返回值 = await 异步函数();
await 异步函数(返回值);
同步代码;
}.catch(err){
...
}
})();
普通js基础中的try catch属于主程序,不会等待异步任务执行,就已经结束。
await可留住当前程序中的一切代码。等待执行完才结束