二、ES6新特性
let和const
模板字符串
箭头函数
解构赋值
扩展运算符
Set和Map
Promise
Async
JS
变量类型
变量类型:Number、String、Undefined、Null、Boolean、Object、Symbol
类型判断方法:typeof instanceof
值类型和引用类型:值类型按照值传递,引用类型按照指针传递
原型链和原型
原型
所有引用类型(数组、对象、函数)都具有对象特性,可以自由扩展属性
所有引用类型(数组、对象、函数)都有一个隐式原型属性
__proto__
属性,值为普通对象所有函数都有一个显式原型属性
__prototype__
属性,值为一个普通对象所有引用类型(数组、对象、函数),实例对象的隐式原型
__proto__
属性值指向它的构造函数的显式原型__prototype__
属性值 即:obj.__proto__ = Function.prototype
// 自由扩展
var obj = {}; obj.a = 100;
var arr = []; arr.b = 100;
function fn() {}; fn.c = 100
//隐式原型
log(obj.__proto__)
log(arr.__prpto__)
log(fn.__proto__)
// 显式原型
log(fn.prototype)
// 实例对象的隐式原型属性值===构造函数的显式原型属性值
log(obj.__proto__ === Object.prototype)
原型链
原型链(隐式原型链):访问一个对象的属性时,
- 先在自身属性查找,找到返回
- 未找到沿着
__proto__
这条链向上查找,找到返回- 最后还是没有找到返回undefined,尽头是:
Object.prototype.__proto__ === null
闭包
产生闭包条件:函数嵌套、内部函数使用了外部函数的数据、执行外部函数
Promise
可以解决地域回调的方法,代码更优雅
3个状态、2个过程、1个方法
3:pending等待 fulfilled实现 rejected拒绝
2:pending —>fulfilled(resolve) pending —>rejected(reject)
1:then
三、call、apply、bind区别
原理
他们三个都可以修改this的指向,但是他们的传参方式不同,call和bind参数二都是以参数列表的形式,而apply是一个数组的形式。在使用时call和apply直接调用,而bind不会立即调用。
call()方法:
Function.call(obj,param1,param2,param3)
obj:这个对象将代替Function类里的this对象
params:这个是一个参数列表
apply()方法:
Function.apply(obj,args)
obj:这个对象将代替Function类里的this对象
args:是一个数组,args:[param1,param2,param3]
bind()方法:
方法一:
Function.bind(obj,param1,param2,param3)()
obj:这个对象将代替Function类里的this对象
params:这个是一个参数列表
方法二:
Function.bind(obj)(param1,param2,param3)
obj:这个对象代替Function里的this对象
参数可以在调用的时候传递
因为bind()返回的是一个函数,需要调用才会执行
例子:
var myObj1 = {
name:'小王',
myAge:this.age,
sayName:function(add, front){
console.log(this.name + '今年' + this.age + '在'+add+'做'+front)
}
}
var heros ={
name:'艾希尼亚',
age:'20'
}
myObj1.sayName.call(heros,'上海','前端'); //艾希尼亚今年20在上海做前端
myObj1.sayName.apply(heros,['上海','前端']); //艾希尼亚今年20在上海做前端
myObj1.sayName.bind(heros,['上海','前端'])(); // 艾希尼亚今年20在上海,前端做undefined ;这里有错乱
myObj1.sayName.bind(heros,'上海','前端')(); // 艾希尼亚今年20在上海做前端
myObj1.sayName.bind(heros)('上海','前端'); // 艾希尼亚今年20在上海做前端
手写
// 手写call
Function.prototype.myCall = function(thisArg, ...args) {
const fn = Symbol('fn') // 声明一个独有的Symbol,防止覆盖
thisArg = thisArg || window // 没有传入this,默认使用window对象
thisArg[fn] = this // 将this指向调用call的对象
const result = thisArg[fn](...args)// 执行函数
delete thisArg[fn] // 删除函数
return result // 返回执行结果
}
function foo() {console.log(this.name)}
const obj = {name: '你代码质量高'}
foo.myCall(obj)
// 手写apply
Function.prototype.myApply = function(thisArg, args) {
const fn = Symbol('fn')
thisArg = thisArg || window
thisArg[fn] = this
const result = thisArg[fn](...args)
delete thisArg[fn]
return result
}
foo.myApply(obj)
// 手写bind
Function.prototype.myBind = function (thisArg, ...args) {
var self = this
var fbound = function () {
self.apply(this instanceof self ? this : thisArg, args.concat(
Array.prototype.slice.call(arguments)))
fbound.prototype = Object.create(self.prototype)
return fbound
}
}
// 测试
const obj = {name: '写代码像蔡徐坤'}
function foo() {
console.log(this.name)
console.log(argument)
}
foo.myBind(obj, 'a', 'b', 'c') // 写代码像蔡徐坤['a', 'b', 'c']
四、手写防抖
// 短时间连续触发的事件,在指定时间内事件处理函数执行一次,触发一次时间就会重置,时间内没有触发才会执行
function debounce(func, 1000) {
let timeout = null
return function() {
let _that = this
let args = arguments
if(timeout) clearTimeout(timeout)
timeout = setTimeout(() => {
func.apply(_that, args)
},1000)
}
}
// arguments 类数组对象,函数所有的参数都会存到arguments里面
五、手写节流
// 在规定时间周期内,触发执行一次事件,后续继续触发不执行,等待下一个时间周期function throttle(func,1000) { let timeout = null return function() { let _that = this let args = arguments if(!timeout) { timeout = setTimeout(() => { timeout = null func.apply(_that, args) },1000) } }}
六、数组扁平化
数组扁平化是指:将多维数组转为一维数组
const arr = [1, [1,2], [1,2,3]]
// 1、reduce 遍历数组的每一项,如果不是数组则concat,是数组则递归遍历
function flat(arr) {
return arr.reduce((result, item) => {
return result.concat(item instanceof Array ? flat(item) : item)
}, [])
}
// 2、flat 处理
arr.flat(Infinity)
// 3、扩展运算符
function flat(arr) {
while(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
return arr
}
七、深拷贝浅拷贝
深浅拷贝只针对Object和Array这样的引用数据类型。
浅拷贝:只复制指向某个对象的指针,而不是复制对象本身,新旧对象还是共享同一块内存,修改引用类型数据会一起改变,修改基本类型数据不会各自影响。
基本数据类型拷贝与原对象不会相互影响,引用数据类型拷贝与原对象会相互影响
深拷贝:会创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象,两者互不相关。
基本数据类型与引用数据类型都不会相互影响,两者完全隔离
赋值:赋的是对象的内存地址,两个对象指向的同一个存储空间,互相影响的。
基本数据类型与引用数据类型都会被影响到
// 对象赋值 var obj1 = { 'name': 'zs', 'age': 18, 'arr': [1,2,3] } var obj2 = obj1 obj2.name = 'ls' // obj1的name也会变化 obj2.arr = [2,3,4] log(obj1) // {'name': 'ls', 'age': 18, 'arr': [2,3,4]} log(obj2) // {'name': 'ls', 'age': 18, 'arr': [2,3,4]}
// 浅拷贝 浅拷贝会创建一个新对象,这个对象有着与原始对象一样的属性与属性值(拷贝),如果属性是基本类型,拷贝的就是基本类型的值,如果是引用类型,拷贝的就是内存地址,内存地址发生改变会互相影响,但是基本类型值就不会互相影响。 var obj1 = { 'name': 'zs', 'age': 18, 'arr': [1,2,3] } var obj3 = shallowCopy(obj1) obj3.name = 'ls' obj3.arr = [2,3,4] function shallowCopy(src) { var dst = {}; for (var prop in src) { if (src.hasOwnProperty(prop)) { dst[prop] = src[prop]; } } return dst; } log(obj1) // {'name': 'zs', 'age': 18, 'arr': [2,3,4]} log(obj3) // {'name': 'ls', 'age': 18, 'arr': [2,3,4]}
八、浅拷贝实现方法
-
Object.assign()
Object.assign()方法可以把任意多个源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象,拷贝的是对象的属性的引用,而不是对象本身。
var obj = {a: {a:'kola',b:30}} var initalObj = Object.assign({}, obj) initalObj.a.a='wade' console.log(obj.a.a) // wade
-
Array.prototype.concat()
let arr = [1,2,{username:'name'}] let arr2 = arr.concat() arr2[2].username='hello' console.log(arr2) // [1,2,{username:'hello'}]
-
Array.prototype.slice()
let arr = [1,2,{username: 'size'}] let arr2 = arr.slice() arr2[1] = 3 console.log(arr2) // [1,3,{username: 'size'}]
九、深拷贝实现方法
-
JSON.parse(JSON.stringify())
通过后者将对象转为字符串,再将字符串转为对象,一去一来就产生了新的对象。只能数组与对象,不能对函数
let arr = [1, 2, {name:'me'}] let arr2 = JSON.parse(JSON.stringify(arr)) arr2[1] = 3 console.log(arr2) // [1,3,{name: 'me'}]
-
手写递归方法
-
函数库lodash
十、克隆数组方法
-
slice()
let arr = [1, 2, 3, 4]let arr1 = arr.slice()
-
**[…arr]**扩展运算符
const arr = [1, 2, 3, 4]const arr2 = [...arr]
-
concat()
var arr = [1, 2, 3, 4]var arr2 = arr.concat()
-
Object.assign()
let arr = [1, 2, 3, 4] let arr2 = [] Object.assign(arr2,arr)
-
JSON.parse()+JSON.stringify()
十一、arguments
是一个类数组对象,拥有length属性。
arguments转数组方法:1.Array.prototype.slice.call(arguments) 2.[].slice.call(arguments)
十二、重排重绘
重排:某个dom节点发生变化,就需要对DOM结构重新进行计算,重新布局页面,引发回流。
引起重排的操作:页面首次渲染;浏览器窗口大小发生变化、元素大小位置变化、元素字体大小变化、删除或者添加DOM元素、激活css伪类等
重绘:页面中元素的样式改变并不会影响他在文档流中的位置。
引起重绘的操作:字体颜色、
一、正则表达式
1、单个字符
最简单的正则表达式可以由简单的数字和字母组成,没有特殊的语义,纯粹就是一一对应的关系。如想在’apple’这个单词里找到‘a’这个字符,就直接用
/a/
这个正则就可以了。 但是如果想要匹配特殊字符的话,就得请出我们第一个元字符**\
**, 它是转义字符字符,顾名思义,就是让其后续的字符失去其本来的含义。举个例子:我想匹配
*
这个符号,由于*
这个符号本身是个特殊字符,所以我要利用转义元字符\
来让它失去其本来的含义:/\*/
当然,如果转义字符后面加的没有特殊意义,而是普通的字符,那么就会被赋予特殊意义,比如下列:
特殊字符 | 正则表达式 | 记忆方式 |
---|---|---|
换行符 | \n | new line |
换页符 | \f | form feed |
回车符 | \r | return |
空白符 | \s | space |
制表符 | \t | tab |
垂直制表符 | \v | vertical tab |
回退符 | [\b] | backspace |
2、多个字符
在正则表达式里,集合的定义方式是使用中括号
[
和]
。如/[123]/
这个正则就能同时匹配1,2,3三个字符。那如果我想匹配所有的数字怎么办呢?从0写到9显然太过低效,所以元字符-
就可以用来表示区间范围,利用/[0-9]/
就能匹配所有的数字,/[a-z]/
则可以匹配所有的英文小写字母。
即便有了集合和区间的定义方式,如果要同时匹配多个字符也还是要一一列举,这是低效的。所以在正则表达式里衍生了一批用来同时匹配多个字符的简便正则表达式:
匹配区间 正则表达式 记忆方式 除了换行符之外的任何字符 . 句号-除了句子结束符 单个数字[0-9] \d digit 除了[0-9] \D no digit 包括下划线在内的单个字符[A-Za-z0-9] \w word 非单字字符 \W no Word 匹配空白字符、空格、制表符、换页符 \s space 匹配非空白字符 \S no space
3、循环与重复
3.1、0|1
元字符
?
代表了匹配一个字符或0个字符。设想一下,如果你要匹配color
和colour
这两个单词,就需要同时保证u
这个字符是否出现都能被匹配到。所以你的正则表达式应该是这样的:/colou?r/
。
3.2、>=0
元字符
*
用来表示匹配0个字符或无数个字符。通常用来过滤某些可有可无的字符串。
3.3、>=1
元字符
+
适用于要匹配同个字符出现1次或多次的情况。
3.4、特定次数
在某些情况下,我们需要匹配特定的重复次数,元字符
{
和}
用来给重复匹配设置精确的区间范围。如’a’我想匹配3次,那么我就使用/a{3}/
这个正则,或者说’a’我想匹配至少两次就是用/a{2,}/
这个正则。- {x}: x次- {min, max}: 介于min次到max次之间- {min, }: 至少min次- {0, max}: 至多max次
4、位置边界
4.1、单词边界
我想找到
cat
这个单词,但是如果只是使用/cat/
这个正则,就会同时匹配到cat
和scattered
这两处文本。这时候我们就需要使用边界正则表达式\b
,其中b是boundary的首字母。在正则引擎里它其实匹配的是能构成单词的字符(\w)和不能构成单词的字符(\W)中间的那个位置。上面的例子改写成
/\bcat\b/
这样就能匹配到cat
这个单词了。
4.2、字符串边界
匹配完单词,我们再来看一下一整个字符串的边界怎么匹配。元字符^用来匹配字符串的开头。而元字符$用来匹配字符串的末尾。注意的是在长文本里,如果要排除换行符的干扰,我们要使用多行模式。试着匹配I am scq000这个句子:
I am scq000.I am scq000.I am scq000.
/^I am scq000\.$/m
这是上面的表达式,m表示多行模式,.是特殊符号,需要转义
边界和标志 | 正则表达式 |
---|---|
单词边界 | \b |
非单词边界 | \B |
字符串开头 | ^ |
字符串结尾 | $ |
多行模式 | m |
忽略大小写 | i |
全局模式 | g |
5、子表达式
分组
所有以
(
和)
元字符所包含的正则表达式被分为一组,每一个分组都是一个子表达式,它也是构成高级正则表达式的基础。如果只是使用简单的(regex)
匹配语法本质上和不分组是一样的,如果要发挥它强大的作用,往往要结合回溯引用的方式。
回溯引用
所谓回溯引用(backreference)指的是模式的后面部分引用前面已经匹配到的子字符串。你可以把它想象成是变量,回溯引用的语法像
\1
,\2
,…,其中\1
表示引用的第一个子表达式,\2
表示引用的第二个子表达式,以此类推。而\0
则表示整个表达式。
假设现在要在下面这个文本里匹配两个连续相同的单词,你要怎么做呢?hello what what is the first first thing, and I am am scq000.
利用回溯引用,我们可以很容易地写出
/\b(\w+)\s\1/
这样的正则回溯引用在替换字符串中十分常用,语法上有些许区别,用
$1
,$2
…来引用要被替换的字符串。下面以js代码作演示:var str = 'abc abc 123';str.replace(/(ab)c/g,'$1g');// 得到结果 'abg abg 123'
前向查找
前向查找(lookahead)是用来限制后缀的。凡是以
(?=regex)
包含的子表达式在匹配过程中都会用来限制前面的表达式的匹配。例如happy happily
这两个单词,我想获得以happ
开头的副词,那么就可以使用happ(?=ily)
来匹配。如果我想过滤所有以happ
开头的副词,那么也可以采用负前向查找的正则happ(?!ily)
,就会匹配到happy
单词的happ
前缀。
后向查找
介绍完前向查找,接着我们再来介绍一下它的反向操作:后向查找(lookbehind)。后向查找(lookbehind)是通过指定一个子表达式,然后从符合这个子表达式的位置出发开始查找符合规则的字串。举个简单的例子:
apple
和people
都包含ple
这个后缀,那么如果我只想找到apple
的ple
,该怎么做呢?我们可以通过限制app
这个前缀,就能唯一确定ple
这个单词了。
/(?<=app)ple/
其中
(?<=regex)
的语法就是我们这里要介绍的后向查找。regex
指代的子表达式会作为限制项进行匹配,匹配到这个子表达式后,就会继续向后查找。 另外一种限制匹配是利用(?<!regex)
语法,这里称为负后向查找。与正前向查找不同的是,被指定的子表达式不能被匹配到。于是,在上面的例子中,如果想要查找apple
的ple
也可以这么写成/(?<!peo)ple
。需要注意的,不是每种正则实现都支持后向查找。在javascript中是不支持的,所以如果有用到后向查找的情况,有一个思路是将字符串进行翻转,然后再使用前向查找,作完处理后再翻转回来。看一个简单的例子:
// 比如我想替换apple的ple为ply
var str = 'apple people';
str.split('').reverse().join('').replace(/elp(?=pa)/, 'ylp').split('').reverse().join('');
练习正则网址http://tool.chinaz.com/regex/
作者:scq000链接:https://juejin.cn/post/6844903845227659271来源:掘金著作权归作者所有。