js数据类型
主要有基础数据类型和引用数据类型,基础数据类型偶7种:boolean、string、number、undefined、null、bigInt(新增加的)、symbol(新增加的,创建出来的变量是唯一的,可以解决变量重名问题)
应用数据类型:object(对象数据类型,在js种万物皆可对象,function是对象,array是一个对象)
typeof的使用注意事项
null:当你用typeof null时,返回的的时object,这是为什么?因为没个数据类型底层都有一个二进制的标识符来标识这个数据的类型,而typeof就是利用这个标识符的前三位来判断这个数据是什么类型,而object类型数据的前三位是000,而null的这个标识符全是0,与object的类型判断依据相同,所以返回的是object。
函数:有一值得注意,几乎所有其他除基本数据类型为的数据结构,如Date,RegExp,array…使用typof都返回object,但是函数使用typeof后返回的是function,因为规范是这么定义的。
intanceof来判断引用数据类型
使用typof能判断number,string,undefined,boolean,function,object。当其中返回的object里面除了null外,还有许多其他的类型需要进一步的判断,这就需要instanceof了。这个关键字可以判断当前构造函数是否出现在目标对象的原型上,以此来判断是否为该类型。
例:[] instanceof Array。以此来判断字面量[]的类型是否为数组。
判断数据类型的其他方法
Object.protytype.toString方法,通过call,bind,apply等方法,都可以,改变该方法的this指向,执行该方法,会放回[object 类型]这样的字符串,可以借此来判断数据的类型。不过这也只能用来判断原始内置的数据类型,如Date,RegExp等。如果你自己写的构造函数构建的对象,是没办法用这个方法来判断具体类型的。
RegExp正则表达式
可以用字面量/asd/来声明正则表达式,也可以通过RegExp构造函数来创建这则对象。
正则相关字符:
\:反斜杠,配合特殊字符使用,但要使用特殊字符,类似\ ,/,",*,.'时候需要在这些字符前面加上\才可以正常标识他们原有的意思。
^:匹配开头
&:匹配结尾
+:匹配前面一个表达式一次或多次
* :匹配前面一个表达式零次到多次
?:匹配前面一个表达式零次到一次
.:匹配任何字符串
():称为捕获括号,通过括号可以整理你要匹配的一个字符串规则,之后你可以使用\index来在后面的匹配中复用之前的匹配规则,在replace中还可以通过$index来获取捕获到的字符串
例:/(abs)\1/等价与/(abs)(abs)/。
例:'ascad'.replace(/a(.*)a/,'$1+')可以匹配到字符串中的sc,替换成sc+
**[]**标识一个集合,标识当前位置可以是中括号里所列举的所有字符,当在内部开头加上^后,就是去反的意思
\d:匹配数字
\D:匹配非数字
\w:匹配字母
\W:匹配非字母
{n}:匹配前面的表达式n个
{n,}:匹配前面的表达式n以上个
{n,m}:匹配前面的表达式n到m个
**(?:x)**非捕获括号,也就是说仅仅起到组织表达式的作用,并不会捕获该表达式内的内容,也就是你在使用match或者exec时,返回的详细结果中不会有这个非捕获括号里面的内容
**x(?=y)**匹配x仅仅当x后面跟着y,这叫先行断言
(?<=y)x匹配前面是y的x,这叫后行断言
**x(?!)**正向否定查找
(?<!y)x反向否定查找
正则六个可选参数:
g全局搜索
i不区分大小写
m多行查找
s允许.匹配换行符
u使用unicode码的模式进行匹配
y执行“粘性 (sticky)”搜索,匹配从目标字符串的当前位置开始
正则相关的方法有,string对象上的replace match search macth macthAll split
还有正则对象上的test exec
例子:
let a=/hello/;
let str='hello world'
// search
console.log(str.search(a))
// replace
console.log('匹配到的',str.replace(a,'world'))
// match
console.log(str.match(/world/))
// matchAll 返回的是一个迭代器。通过三点结构可以查看其中的内容
console.log(...str.matchAll(/world/g));
// test
console.log(/hello/.test('hello world'))
// exec
console.log(/hello/.exec('hello world'))
this指向问题
this在函数中指向的是当前执行该方法的对象,在浏览器中,直接调用一个函数的话,其实可以看作是window对象调用了这个函数,所以此时的this指向的是window对象。当一个函数作为对象的方法的是时候,使用这个对象调用这个函数,则此时的this指向这个对象。当声明的是一个构造函数的时候,则this指向的是实例对象。可以运行一下代码看看结果
function Test(){
console.log(this);
}
let obj={
name:'selfObj',
func:Test
}
let test=new Test();
obj.func();
Test()
Promise是什么
Promise提供了异步解决方案,是一个构造函数,用new方法,构建一个promise实例,promise实例有三个状态,pending(等待状态)、reject(失败状态)、resolve(成功状态),状态只能由pending状态向成功状态或者失败状态转换,且状态变化后是不可逆的。
在创建promise实例时,床褥一个方法,这个方法就是要执行的包含异步行为的方法,这个方法有两个参数,第一个参数是成功回调方法和第二个参数是失败回调方法,通常命名为resove和reject,但成功执行完成异步方法后,可以调用reoslve方法,失败了调用rejetc方法。promise状态就会改变,接着执行注册的成功回调行数或者失败回调函数。
promise实例以链式的方式进行成功和失败回调的注册,在promise实例后面,用then的方法注册成功回调,用catch方法捕获失败。
值得注意的是,then方法返回的还是一个promise实例,也就是说你可以通过链式调用的方法,继续在后面通过then方法,在进行下一步的操作,解决了原先回调地狱的问题。
Promise钩爪函数上还有几个静态方法,
Promise.resolve:返回一个成功的promise实例对象
Promise.reject:放回一个失败的promise实例对象
Promise.all:并行执行数组中的所有Promise实例,返回promise实例。当所有实例都成功了,该方法放回的实例也是成功,有一个失败就直接走catch注册的方法
Promise.race:并行执行监听所有Promise实例,但只要有一个转为成功状态,该方法返回的实例就成功。
下方例子运行一边,观察结果,有助于理解
// 第一部分
let p=new Promise((resolve,reject)=>{
console.log('这里执行异步方法')
resolve(1)
}).then((result)=>{
console.log('执行了成功回调,结果为',result)
return 2
}).then((result)=>{
console.log('可以通过链式处理数据',result)
return Promise.reject('err')
}).catch(err=>{
console.log('捕获err',err)
}).then((result)=>{
console.log('也可以通过throw方法提交错误来触发错误捕获');
throw new Error('this is a throw error')
}).catch((err)=>{
console.log('err',err)
})
// 第二部分
Promise.all([Promise.resolve(1),Promise.resolve(2),Promise.resolve(3),Promise.resolve(4)]).then((result)=>{
console.log('全部成功才成功',result)
})
Promise.all([Promise.resolve(1),Promise.resolve(2),Promise.resolve(3),Promise.reject('i am error')]).catch(err=>{
console.log('只要有一个失败就算失败',err)
})
Promise.race([Promise.resolve(1),Promise.resolve(2),Promise.resolve(3)]).then((result)=>{
console.log('数组里有一个成功,谁快,返回的结果就是谁的',result)
})
Promise.race([Promise.reject('i am error'),Promise.resolve(1),Promise.resolve(2),Promise.resolve(3),Promise.reject('i am error')]).catch(err=>{
console.log('如果第一个执行完的promise实例是失败的,那race方法返回的就是失败的promise实例',err)
})
手写Promise
根据上面对promise的理解,手写promise。
const PENDING=0,RESOLVE=1,REJECT=2;
function iPromise(func){
this.state=PENDING;
this.result=null;
this.err=null;
this.successCallbacks=[];
this.failCallbacks=[];
let reject=(err)=>{
if(this.state==PENDING){
this.state=REJECT;
this.err=err;
this.failCallbacks.forEach(func=>{
func(err);
})
}
}
let resolve=(result)=>{
if(this.state==PENDING){
try{
this.state=RESOLVE;
this.result=result;
this.successCallbacks.forEach(func=>{
func(result)
})
}catch(e){
reject(e)
}
}
}
try{
func(resolve,reject)
}catch(e){
reject(e)
}
}
iPromise.prototype.then=function(successCallback=result=>result,failcallback=err=>{throw err}){
return new iPromise((resolve,reject)=>{
try{
if(this.state==RESOLVE){
let result=successCallback(this.result);
resolve(result)
}
if(this.state==REJECT){
let errResult=failcallback(this.err);
reject(errResult)
}
if(this.state==PENDING){
this.successCallbacks.push((result)=>{
resolve(successCallback(result))
})
this.failCallbacks.push((err)=>{
reject(failcallback(err))
})
}
}catch(e){
reject(e)
}
})
}
iPromise.prototype.catch=function(func){
return this.then(null,func);
}
iPromise.reject=function(err){
return new iPromise((resolve,reject)=>{
reject(err)
})
}
iPromise.resolve=function(result){
return new iPromise((resolve,reject)=>{
resolve(result);
})
}
iPromise.all=function(arr){
return new iPromise((resolve,reject)=>{
let curIndex=0;
let results=new Array(arr.length).fill(null);
const processResult=(result,key)=>{
results[key]=result;
if(++curIndex==arr.length){
resolve(results)
}
}
arr.forEach((item,index)=>{
if(item instanceof iPromise){
item.then((result)=>{
processResult(result,index);
}).catch((err)=>{
resolve(err)
})
}else{
processResult(item,index)
}
})
})
}
iPromise.race=function(arr){
return new iPromise((resolve,reject)=>{
for(let item of arr){
if(item instanceof iPromise){
item.then(resolve,reject)
}else{
resolve(item)
}
}
})
}
let p=new iPromise((resolve,reject)=>{
resolve('i am wrong')
}).then((result)=>{
console.log('first '+result);
return 'end of first'
}).then((result)=>{
console.log('second',result)
throw new Error('i am wrong')
}).catch((err)=>{
console.log('error',err)
})
iPromise.all([1,2,3,iPromise.resolve(4)]).then((results)=>{
console.log(results)
})
iPromise.race([5,6,7,iPromise.resolve(4)]).then((result)=>{
console.log(result)
})
垃圾回收
javascript有自动垃圾回收机制,定期进行清理,将不再使用的变量清除,释放其内存空间。垃圾回收机制有两种,一种是标记清除法,一种是引用计数法。
标记清除法:垃圾回收机制在运行的时候给所有变量都做了标记,然后除去那些在执行环境或者被执行环境变量引用的变量的标记,那些标记的变量就是不会在被使用的的变量,所以清除这些变量,然后释放收回其内存
引用计数法:通过资源的引用次数来判断是否需要释放内存。但赋值一个变量的时候,这个值的引用计数就加一,当这个变量指向其他资源的时候,原先的值的引用计数就会减一,但减为零的时候后,就可以定为不会被用到的资源,垃圾回收在下次执行的时候就会释放该部分内存。
迭代器
迭代器可以说是一个对象,这个对象里有一个next方法,这个方法的返回值是是一个对象,有两个属性,一个是value,当前值,一个是done,是否迭代完成。以下generateIterator方法用于生成一个迭代器,iterator就是生成的迭代器;
function generateIterator(){
let arr=[1,2,3,4,5];
let index=0;
return {
next(){
return index+1<arr.length?{
value:index++,
done:false
}:{
value:index,
done:true
}
}
}
}
let iterator=generateIterator();
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
生成器函数
虽然自定义的迭代器是个很好的工具,但它需要显示的维护状态,而生成器函数允许你包含自有迭代算法的函数,通过function*进行生成器函数的定义,内部通过yeild返回每次迭代的value。
function* makeRangeIterator() {
for (let i = 0; i < 3; i += 1) {
yield i;
}
}
生成器和迭代器的应用
在js中有很多数据类型有默认的迭代行为,如数组、Set、Map,这些数据类型可以通过for…of 遍历,可以通过…三点展开符展开,这是因为其内部实现了Symbol.iterator方法。这个方法规定了如何进行迭代,当你需要特殊的迭代行为的时候,就可以通过修改Symbol.iterator方法来实现特殊的迭代行为,而生成器函数和自己写的生成迭代器的函数赋值给这个属性,就可以实现自己的迭代行为。
模块化
以前由于js逻辑少,所以没有模块化这个概念,随着js逻辑不断复杂化,功能越来越多,此时就需要有模块化开发来组织脚本。现在主要的有的模块化主要有commonjs和esmodule。
commonjs:主要应用与node环境,通过module.exports导出模块,通过require导入模块。require导入的模块是拷贝型,也就是说与原来文件里的模块没有联系,两边修改互不联系,不过值得注意的是,这种拷贝是浅拷贝,如果导出的模块里有引用类型数据,那拷贝过来的只是其指针,而指针指向的是同一个引用类型数据。require是运行时加载的,也就是说其可以写在代码中动态加载,且其是同步加载,所以一般用于node环境下的后端开发,因为文件都在本地,同步获取模块也快。
esmodule:这是es提出的模块规范,通过export 导出模块,import导入模块。import是在编译时执行的,也就是说import导入模块只能写在文件的开头,这种导入方式是异步的,并且其导入的是模块的引用,并且只读,不可更改。主要是前端网页的模块化规范。在网页内使用模块,需要在脚本标签上加上type=‘module’,在node环境下使用要package.json里加上type:‘module’配置。
Proxy
proxy代理,可以挟持目标对象,拦截对目标对象的操作行为,对该操作进行修改,执行额外行为等。以下代码挟持了空对象,返回了proxy实例p,拦截了对p的赋值和取值操作。还有可以挟持许多其他操作
has:挟持in操作
deleteProperty:挟持删除操作
defineProperty:挟持修改属性操作。
…
let p=new Proxy({},{
get(target,key){
return target[key]
},
set(target,key,value){
console.log(key)
console.log(value)
target[key]=value
}
})
p.name='linkai';
console.log(p.name)
手写promise
const PENDING='pending';;
const FULFILLED='fullfilled';
const REJECT='reject';
class iPromise{
constructor(executor){
this.status=PENDING;
this.value=undefined;
this.reason=undefined;
this.onResolveCallbacks=[];
this.onRejectCallbacks=[];
let resolve=(value)=>{
if(this.status==PENDING){
this.status=FULFILLED
this.value=value;
this.onResolveCallbacks.forEach((fn)=>{
fn(value)
})
}
}
let reject=(reason)=>{
if(this.status===PENDING){
this.reason=reason;
this.status=REJECT
this.onRejectCallbacks.forEach((fn)=>{
fn(reason);
})
}
}
try{
executor(resolve,reject)
}catch(e){
reject(e)
}
}
#resolvePromise(promise2,x,resolve,reject){
if(x===promise2){
resolve({})
}else{
if(x instanceof iPromise){
x.then((value)=>{
resolve(value)
},reason=>{
reject(reason)
})
}else{
resolve(x)
}
}
}
then(onResolve,onReject){
onResolve=typeof onResolve ==='function'?onResolve:(val)=>val;
onReject=typeof onReject ==='function'?onReject:(err)=>{return new Error(err)}
let promise2=new iPromise((resolve,reject)=>{
if(this.status===FULFILLED){
setTimeout(() => {
try{
let x=onResolve(this.value);
this.#resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
}, 0);
}
if(this.status===REJECT){
setTimeout(() => {
try{
let x=onReject(this.reason);
this.#resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
}, 0);
}
if(this.status===PENDING){
this.onResolveCallbacks.push((value)=>{
setTimeout(()=>{
try{
let x=onResolve(value);
this.#resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
})
this.onRejectCallbacks.push((reason)=>{
setTimeout(() => {
try{
let x=onReject(reason);
this.#resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e)
}
}, 0);
})
}
})
return promise2
}
static resolve(value){
return value instanceof iPromise?value:new iPromise((resolve)=>resolve(value))
}
static reject(reason){
return new iPromise((resolve,reject)=>reject(reason))
}
static all(promises){
return new iPromise((resolve,reject)=>{
let count=promises.length,ans=[];
if(count===0){
resolve([])
}
promises.forEach((promise,index)=>{
promise.then((value)=>{
ans[index]=value;
count--;
if(count===0){
resolve(ans)
}
},(reason)=>{
reject(reason);
})
})
})
}
static race(promises){
return new iPromise((resolve,reject)=>{
promises.forEach((promise)=>{
promise.then((value)=>{
resolve(value)
},(reason)=>{
reject(reason)
})
})
})
}
static allSettled(promises){
return new iPromise((resolve,reject)=>{
let count=promises.length,ans=[];
if(count===0){
resolve([])
}
promises.forEach((promise,index)=>{
promise.then((value)=>{
count--
ans[index]={
status:FULFILLED,
value
}
if(count===0){
resolve(ans);
}
},reason=>{
count--
ans[index]={
status:REJECT,
reason
}
if(count===0){
resolve(ans);
}
})
})
})
}
}
new iPromise((resolve,reject)=>{
resolve(1)
}).then((value)=>{
return value+1
}).then((value)=>{
console.log(value)
return new iPromise((resolve)=>{
resolve(10)
})
}).then((value)=>{
console.log(value)
})
iPromise.resolve('resolve').then((value)=>{console.log(value)})
iPromise.resolve('reject').then((value)=>{console.log(value)},(reason)=>{console.log(reason)});
iPromise.all([iPromise.resolve('resolve1'),iPromise.resolve('resolve2'),iPromise.resolve('resolve3')]).then((value)=>{console.log(value)})
iPromise.race([iPromise.resolve('race1'),iPromise.resolve('race2')]).then((value)=>{console.log(value)})
iPromise.allSettled([iPromise.resolve('settledResolve'),iPromise.resolve('settledReject')]).then((value)=>{console.log(value)})
扁平数据与树的转化
let arr=[
{ id: 1, pid: null, name: 'M1部门' },
{ id: 11, pid: 1, name: '张三' },
{ id: 12, pid: 1, name: '李四' },
{ id: 13, pid: 1, name: '王五' },
{ id: 2, pid: null, name: 'M2部门' },
{ id: 21, pid: 2, name: '赵六' },
{ id: 22, pid: 2, name: '周七' },
{ id: 23, pid: 2, name: '吴八' }
]
function arrtoTree(arr,pid){
return arr.filter((item)=>{
return item.pid==pid
}).map((item)=>{
return {
...item,
children:arrtoTree(arr,item.id)
}
})
}
//利用队列
function flatTree(tree){
let queue=tree,ans=[],item=undefined;
while(item=queue.shift()){
let {children,...data}=item;
ans.push(data);
if(children?.length){
queue.push(...children)
}
}
return ans;
}
let tree=arrtoTree(arr,null)
console.log(flatTree(tree))