文章目录
- 实现延迟加载的方式
- 什么是作用域链
- 什么是同源策略
- 什么是闭包?用途及注意的地方
- call、apply、bind的区别
- 对缓存的理解
- http工作原理
- http与https的区别
- 性能优化的方法
- 对this的理解
- this指向
- new操作符具体干了什么
- 什么是css预处理器及优点
- cookies、sessionStorage、localStorage的区别
- 模块化开发的优点
- 常用的模块化的方案有哪些
- ES6和ES7的区别及H5新特性
- js继承
- propertype、_proto、constractor的关系
- ajax请求数据的流程
- get和post 区别
- 面向对象的好处
- 什么是事件流?什么是事件捕获?什么是事件冒泡
- 什么是回流和重绘
- 浅谈堆和栈的理解
- typeof的语法和返回值
- 数组去重
- 判断对象是不是数组
- 简述对promise的理解
- promise(重点说一下)
- async/await
- let和const的区别
- map和forEach的区别
- 防抖和节流
- 暂时性死区
- 常⻅的 JS 语法错误类型有哪些?
- 深浅拷贝
- 手写一个深拷贝
- 浏览器的事件循环?
- 宏任务和微任务
- 宏任务和微任务的本质区别
实现延迟加载的方式
1.直接将script节点放置在前后,这样js脚本就会子啊页面显示出来之后再加载。
2.使用script的defer和async属性,defer属性为延迟加载,是在页面渲染完成之后在进行加载的,而async属性则是和文档并行加载,这两种方案都不完美,原因在于不是所有浏览器都支持。
3.通过监听window.onload事件,动态添加script节点。
4.通过ajax下载js脚本,动态添加script节点,但ajax有一个缺点,就是无法引用使用cdn方式提供的js文件
5.使用setTimeout延迟加载
什么是作用域链
简单说就是作用域集合。当前作用域-父级作用域-全局作用域形成的作用域链条。全局作用域的变量和方法都可以进行调用。局部的变量和方法只能局部进行调用(闭包除外)。局部可以访问全局的变量和方法。
什么是同源策略
同源指的是域名、协议、端口相同。同源策略是浏览器的一个安全功能,不同源的客户端在没有授权的情况下,不能读取对象资源,为了保障数据的安全,即非同源网页不可请求。
什么是闭包?用途及注意的地方
闭包就是有权访问另一个函数作用域中的变量的函数,打破了作用域链的规则。不会被垃圾回收机制回收,容易造成内存泄漏。
应用场景:设计私有的方法和变量,用完要销毁,设置为空。匿名函数最大的用途就是创建闭包,减少全局变量的使用
特点:函数内嵌套函数,函数内部可以引用外部的参数和变量,参数和变量不会被垃圾回收机制回收
优点:可以避免全局变量污染
缺点:常驻内存,增大内存使用量,使用不当会造成内存泄漏
call、apply、bind的区别
bind:bind绑定完this的指向后会返回一个新的函数体,不会被立即调用。
call&apply:绑定完this指向会立即调用
call与apply的区别:call第一个参数是this的指向,第二个参数及后面的所用参数需要一个个进行传递。apply就地一个参数是this的指向,第二个参数是一个数组
对缓存的理解
缓存就相当于对资源的一种副本实现,不管是在客户端还是在服务端存储着,用相同的url进行请求,直接从副本中请求资源而不再访问源服务器。
优点:
提高访问速度:缓存相对服务端离用户更近,所以在请求过程中从缓存中获取内容避灾源服务器上获取内容用的时间更少,加快了用户体验。降低网络传输:副本呗重复使用,大大降低了用户的宽带使用,其实也是一种变相的省钱(如果流量需要付费的话),同时保证了宽带请求在一个低水平上,更容易维护了。
缓存种类:缓存有很多种类。浏览器缓存、cdn缓存、代理服务器缓存、网关缓存等
http工作原理
客户机与服务器简历链接后,发送一个请求给服务器,请求格式为统一资源标识符、协议版本号,服务器收到请求的信息(包括请求行、请求头、请求体)。服务器接收到请求后,给予相应的响应信息,格式为一个状态行(包括响应行、响应头、响应体)。基于http协议的用户/服务器模式的信息交换过程分为四个过程:建立连接、发送请求信息、发送响应信息、关闭连接。服务器可能同时接受多个请求,这时就睡产生多个session,每个session分别处理各自的请求
http与https的区别
http和https之间的区别在于其传输的内容是否加密和是否是开发性的内容。https协议是ssl+http协议构建的可进行加密传输、身份认证的网络协议、要比http协议安全。端口也不一样,http是80,https是443.
性能优化的方法
1.减少http请求次数:css、js源码压缩、控制图片大小、网页Gzip、cdn托管、data缓存
2.前端模板JS+数据,减少由于HTML标签导致的宽带浪费,前端用变量保存ajax请求结果,每次操作本地变量,减少请求次数
3.用innerHTML代替dom操作,减少DOM操作次数,优化JavaScript性能
4.当需要设置的样式很多时,设置className而不是直接操作style
5.减少使用全局变量、缓存DOM节点查找的结果。减少IO读取操作
6.避免使用CSS Expression又称Dynamic properties(动态属性)
7.图片预加载,将样式表放在顶部,将脚本放在底部加上时间戳
8.避免在页面的主体布局中使用table,tablke要等其中的内容完全下载之后才会显示出来,实现比div+css布局慢
对this的理解
this是js的一个关键字,随着函数使用场合不同,this的值会发生变化。但是有一个总原则,那就是this指的是调用函数的那个对象。this一般情况下是全局对象。作为方法调用,那么this就是指这个对象
this指向
JS 中如何确定 this 的值?如果是在全局作用域下,this 的值是全局对象,浏览器环境下是 window。
函数中 this 的指向取决于函数的调用形式,在一些情况下也受到严格模式的影响。
● 作为普通函数调用:严格模式下,this 的值是 undefined,非严格模式下,this 指向全局对象。
● 作为方法调用:this 指向所属对象。
● 作为构造函数调用:this 指向实例化的对象。
● 通过 call, apply, bind 调用:如果指定了第一个参数 thisArg,this 的值就是 thisArg 的值(如果是原始值,会包装为对象);如果不传 thisArg,要判断 严格模式,严格模式下 this 是 undefined,非严格模式下 this 指向全局对象。
new操作符具体干了什么
创建一个空对象,并且this变量引用该对象,同时还继承了该对象的原型。属性和方法被加入到this引用的对象中。新创建的对象由this所引用,并且最后隐式的返回this
什么是css预处理器及优点
css预处理器是一种专门的编程语言,进行web页面样式设计,然后在编译成正常的css文件,以提供项目使用。在css中使用变量、简单的逻辑程序、函数。可以让你的css更加简洁、适应性强、可读性更高、更易于代码的维护
cookies、sessionStorage、localStorage的区别
cookie大小不超过4k,每个域下cookie不能超过50个,可以设置有效期,过了有效期cookie自动删除数据,只能访问同一个域名下的cookie,只能是字符串
localStorage长期存储数据,浏览器关闭后数据不丢失。sessionStorage数据在浏览器关闭后自动删除。cookie在浏览器和服务器之间来回传递,localStorage和sessionStorage不会。sessionStorage和localStorage的存储空间更大,有更多丰富易用的接口,有各自独立的存储空间
模块化开发的优点
1.解决文件之间的依赖关系
2.避免命名冲突、解决全局变量及全局函数泛用的现象
3.解决代码的复杂性
4.按需加载
常用的模块化的方案有哪些
服务端(commonJS)例如Node.js
客户端(AMD CMD)例如require sea.js
ES6:module(export import)
ES6和ES7的区别及H5新特性
es6常用特性有解构赋值、扩展运算符、箭头函数、promise、模块化module、let、const
es7常用特性async await异步、Array.property.includes检查数组中是否存在一个值,可代替indexof
h5标签语义化:header、footer、nav、aside、section
本地存储:sessionStorage、localStorage
离线web:给根元素加manifest属性
表单新特性:placeholder、autofocus、type为email、number
js继承
原型链继承:让新实例的原型等于父类的实例,但无法向父类构造函数传参,继承单一,所有新实例都会共享父类实例的属性
构造函数继承:用call和apply将父类构造函数引入子类构造函数,是继承了父类构造函数的属性,没有继承原型的属性,无法实现构造函数的复用
组合继承:传参和复用,但是调用了两次父类构造器,耗内存,子类构造函数会代替原型上的父类构造函数
原型式继承:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就可以随意添加属性的实例或对象,类似于复制一个对象,无法实现复用
寄生式继承:就是给原型上套一个壳子,没有穿件自定义类型,没用到原型,无法复用
寄生组合式继承:在函数内返回对象然后调用
propertype、_proto、constractor的关系
propertype:每个函数都有一个propertype这个属性,而这个属性指向一个对象,这个对象就是原型对象。作用:节约内存、扩展属性和方法、可以实现类之间的继承
_proto:每个对象都有一个proto属性、proto指向创建自己的那个构造函数的原型对象、对象可以直接访问proto里的属性和方法
contractor:指向创建自己的那个构造函数
总结:当我们创建一个构造函数的时候,这个构造函数自带了一个propertype属性,而这个属性指向一个对象,就是原型对象。这个原型对象里有一个constractor构造器,它的作用是指向创建自己的构造函数。除此之外propertype还可以存放公共的属性和方法。当我们实例化一个对象的时候,这个对象自带了一个_proto属性,这个proto指向创建自己的构造函数的原型对象。可以使用这个原型对象里的属性和方法
ajax请求数据的流程
1.创建XMLHttpRequest对象,也就是创建一个异步调用对象
2.创建一个新的http请求,并制定该http请求的方法、url及验证信息
3.设置响应http请求状态变化的函数
4.获取异步调用返回的函数
5.使用JavaScript和dom实现局部刷新
get和post 区别
post:通过http post机制,用户看不到这个过程,如果想要看到数据,可以从控制台NetWork中的formData中进行查看。
get:进行数据请求时会将 传递的参数信息通过url进行传递。子啊地址的?后面按照key=value进行传递,如果需要传递多个数据是用&符进行分隔。get传递数量较小,不能大于2KB。post传递数量较大,一般被默认为不受限制。get安全性较低。post安全性较高。但执行效率比post方法好。get与post的安全性取决于http协议或者https协议
面向对象的好处
开发时间短,效率高,可靠性高,所开发的程序更强壮。由于面向兑现编程的可重用性,可以在应用程序中大量采用成熟的类库,从而缩短了开发时间。应用程序更易于维护、更新和升级。继承和封装使得应用程序带来的影响更加局部化
什么是事件流?什么是事件捕获?什么是事件冒泡
当一个HTML元素产生一个事件时,该事件会在元素节点与根节点之间的路径传播,路径所经过的节点都会收到该事件,这个传播的过程叫做DOM事件流。
冒泡事件:微软提出的,事件由子元素传递到父元素的过程叫做冒泡
捕获事件:网景提出的,事件由父元素到子元素传递的过程叫做捕获
什么是回流和重绘
当渲染树中的一部分或全部因为元素的尺寸、布局、显示隐藏等改变而需要重新构建的时候,这时候会发生回流。每个页面至少会发生一次回流,就是页面第一次加载的时候。
在回流的时候,浏览器会使渲染树中受到影响的元素部分失效,并重新绘制这个部分的渲染树,完成回流以后,浏览器会重新绘制受到影响的部分元素到屏幕中,这个过程就是重绘。
浅谈堆和栈的理解
js变量存储有栈存储和堆存储,基本数据类型的变量存储在栈中,引用数据类型的变量存储在堆中,引用数据类型的地址也存在栈中。当访问基础类型变量时,直接从栈中取值。当访问引用类型变量时,先从栈中读取地址,在根据地址到堆中取出数据
typeof的语法和返回值
语法:typeof是一个运算符,有两种方式:typeof (表达式)和typeof 变量名。
返回值:
1.‘undefined’ :–未定义的变量或值
2.‘Boolean’: --布尔类型的变量或值
3.‘string’: --字符串类型的变量或值
4.‘number’: --数字类型的变量或值
5.‘object’: --对象类型的数据或值,或者null
6.‘function’: --函数类型的变量或值
示例:
console.log(typeof a); //undefined
console.log(typeof 123); //number
console.log(typeof '123'); //string
console.log(typeof NaN); //number
console.log(typeof null); //object
var obj = new String()
console.log(typeof obj); //object
数组去重
1.双层循环,外层循环元素,内层循环时比较值,如果有相同的就跳过,没有就push进数组
2.先排序,遇到相同的就删掉
3.利用es6的set
4.扩展运算符
判断对象是不是数组
1.arr instanceof Array
2.arr.constractor === Array
3.arr.isArray()
简述对promise的理解
promise其实是一个构造函数,解决异步嵌套和回调地域,自身有all,reject,resolve几个方法,原型上有then、catch等方法。
基本使用:通过new一个promise,里面是一个回调函数,回调函数中有两个参数,reject、resolve。当异步执行成功的时候调用resolve方法,当异步失败的时候调用reject方法。因为它会直接调用,所以我们一般把它包在一个函数里面,需要用的时候再去执行,函数会return出promise对象,我们就可以直接使用 promise的.then和.catch方法。除此之外promise有一个.then方法,当成功的时候执行第一个回调函数,当失败的时候执行第二个回调函数,第二个回调函数也可以通过promise对象.catch调用。promise.all当所有异步代码都执行完毕后才会执行.then中的操作。promise.race只要有一个promise执行完毕后就会执行.then中的操作
promise(重点说一下)
理解:
解决异步和回调地狱的一种方案
从语法上说Promise
是一个构造函数
从功能上说Promise
对象用来封装一个异步操作并可以获取其结果
三种状态:
pending
:等待状态,比如正在进行网络请求,或者定时器没有到时间。
fulfill
:满足状态,当我们主动回调了resolve
时,就处于该状态,并且会回调.then()
reject
:拒绝状态,当我们主动回调了reject
时,就处于该状态,并且会回调.then()
或者.catch()
两种状态改变:
pending
变为 fulfilled
pending
变为 rejected
流程:
基本使用:
//1.创建一个新的promise对象
const p = new Promise((resolve, reject) => { //执行器函数是同步回调!
console.log('执行 executor') //立刻执行
//2.执行异步操作
setTimeout(() => {
const time = Date.now()
//3.1 成功,调用resolve(value)
if( time % 2 === 0 ){
resolve('成功的数据,time=' + time)
} else {
//3.2 失败,调用reject(reason)
reject('失败的数据,time=' + time)
}
}, 1000)
})
console.log('new Promise()之后') //先输出 '执行 exceutor'
p.then(
value => { // onfulfilled函数,自动接收成功的value
console.log('成功的回调', value)
},
reason => { // onrejected函数,自动接收失败的reason
console.log('失败的回调', reason)
}
)
API
new Promise( (resolve, reject) => {
setTimeout( () => {
resolve('成功') //resolve就像是一个传递数据的运输机
}, 1000 )
}).then(
value => {
console.log('onResolved()1', value)
}
).catch(
reason => {
console.log('onRejected()1', reason)
}
)
const p1 = new Promise((resolve, reject) => {
resolve(1)
})
const p2 = Promise.resolve(2)
const p3 = Promise.reject(3)
// p1.then( value => {console.log(value)} )
// p2.then( value => {console.log(value)} )
// p3.catch( reason => {console.log(reason)} )
//const pAll = Promise.all([p1,p2,p3])
const pAll = Promise.all([p1,p2])
pAll.then(
values => {
console.log('all onResolved()', values)
},
reason => {
console.log('all onRejected()', reason)
}
)
const pRace = Promise.race([p1,p2,p3])
pRace.then(
value => {
console.log('race onResolved()', value)
},
reason => {
console.log('race onResolved()', reason)
}
)
一个promise指定多个成功/失败回调函数, 都会调用吗?
当promise改变为对应状态时都会调用
改变promise状态和指定回调函数谁先谁后?
- 都有可能,正常情况下先指定回调在改变状态,也可以先改变状态在指定回调
- 如何先改状态再指定回调?
在执行器中直接调用resolve()/reject()
延迟更长时间才调用then()
- 什么时候才能得到数据?
如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
promise异常传透?
当使用 promise 的 then
链式调用时, 可以在最后指定失败的回调(.catch()
),前面任何操作出了异常, 都会传到最后失败的回调中处理。
中断promise链?
当使用promise的then
链式调用时, 在中间中断, 不再调用后面的回调函数
办法: 在回调函数中返回一个pending
状态的promise对象
promise和async|await的区别?
async / await
就是Promise
的语法糖promise
错误可以通过catch
来捕捉,建议尾部捕获错误, async/await既可以用.then
又可以用try-catch
捕捉
async/await
await
后面接一个会return new promise
的函数并执行它
await
只能放在async
函数里
async函数会返回一个promise
,并且Promise对象的状态值是resolved
await 命令后面的 Promise 对象,运行结果可能是 rejected
,所以最好把 await 命令放在 try...catch
代码块中。
用法:
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(m, n) {
console.log(`step2 with ${m} and ${n}`);
return takeLongTime(m + n);
}
function step3(k, m, n) {
console.log(`step3 with ${k}, ${m} and ${n}`);
return takeLongTime(k + m + n);
}
//使用async/await
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time1, time2);
const result = await step3(time1, time2, time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms
//使用promise
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => {
return step2(time1, time2)
.then(time3 => [time1, time2, time3]);
})
.then(times => {
const [time1, time2, time3] = times;
return step3(time1, time2, time3);
})
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
let和const的区别
let和const不存在变量提升,先声明后使用,不允许重复声明,块级作用域。const定义的变量不能被赋值或者被修改
map和forEach的区别
都是遍历数组中的每一项,匿名函数中的this都是指向window,forEach没有返回值,map返回一个新数组,原数组不会改变
防抖和节流
在进行窗口的resize、scroll,输入框内容校验等操作时,如果时间处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验糟糕。此时可以采用防抖和节流的方式减少调用频率,同时又不影响实际效果。
第一种是用时间戳来判断是否已到执行时间,记录上次执行的时间戳,然后每次触发事件执行回调,回调中判断当前时间
防抖实现:
● debunce 实则是个包装函数,通过传入操作函数和时间间隔,来返回㇐个新函数
● 新函数中主要是通过定时器来设置函数调用的频率
● flag 只有第㇐次触发的时候会立即执行
//handler:执行函数,ms:时间,flag:是否立即执行
function debunce(handler,ms,flag){
let timer = null
return function (...args){
clearTimeout(timer)
if(flag && !timer){
handler.apply(this,args)
}
timer = setTimeout(()=>{
handler.apply(this,args)
},ms)
}
}
节流实现
● 和防抖不同的是,防抖中是取消定时器,节流中是定时器到时间自动执行,仅仅是将 timer 变量设置为 null
● 时间戳版:第一次执行,最后一次不执行
●定时器版:第一次不执行,最后一次执行
function throttle (handler,ms){
let pre = 0;
return function(...args){
if(Date.now() - pre > ms){
pre = Date.now()
handler.apply(this,args)
}
}
}
function throttle(handler,ms){
let timer = null
return function(..args){
if(!timer){
timer = setTimeout(()=>{
timer = null
handler.apply(this,args)
},ms)
}
}
}
暂时性死区
在代码块内,使用let和const声明变量之前,变量都是不可用的,在语法上称为“暂时性死区”。
常⻅的 JS 语法错误类型有哪些?
ReferenceError:引用了不存在的变量
TypeError:类型错误,比如对数值类型调用了方法。
SyntaxError:语法错误,词法分析阶段报错。
RangeError:数值越界。
URIError,EvalError
深浅拷贝
浅拷贝:拷贝A对象里面的数据,但是不拷贝对象里面的子对象
深拷贝:会克隆出一个对象,数据相同,但引用地址不同
深拷贝用object.assign、JSON.parse(JSON.stringify())、设置deep为true
手写一个深拷贝
function deepClone(data){
//获取数据类型
let type = getType(data)
if(type === 'object'){
let result = {}
Object.keys(data).forEatch(key=>{
result[key] = deepClone(data[key])
})
return result
} else if(type === 'array'){
return data.map(item => deepClone(item))
} else if(type === 'regexp'){
return new RegExp(data.source,data.flags)
} else if(type === 'function'){
eval("(" + data.toString() + ")")
} else if(type === 'map' || type === 'set'){
return new data.constructor(data)
} else{
return data
}
}
浏览器的事件循环?
js 代码执行过程中,会创建对应的执行上下文并压入执行上下文栈中。
如果遇到异步任务就会将任务挂起,交给其他线程去处理异步任务,当异步任务处理完后,会将回调结果加入事件队列中。
当执行栈中所有任务执行完毕后,就是主线程处于闲置状态时,才会从事件队列中取出排在首位的事件回调结果,并把这个回调加入执行栈中然后执行其中的代码,如此反复,这个过程就被称为事件循环。
事件队列分为了宏任务队列和微任务队列,在当前执行栈为空时,主线程会先查看微任务队列是否有事件存在,存在则依次执行微任务队列中的事件回调,直至微任务队列为空;不存在再去宏任务队列中处理。
宏任务和微任务
常见的宏任务有setTimeout()、setInterval()、setImmediate()、I/O、用户交互操作,UI渲染
常见的微任务有promise.then()、promise.catch()、new MutationObserver、process.nextTick()
宏任务和微任务的本质区别
宏任务有明确的异步任务需要执行和回调,需要其他异步线程支持
微任务没有明确的异步任务需要执行,只有回调,不需要其他异步线程支持。
先来个简单的代码
console.log(1)
setTimeout(function() {
console.log(2)
}, 0)
const p = new Promise((resolve, reject) => {
resolve(3)
})
p.then(data => {
console.log(data)
})
console.log(4)
输出结果:
1
(同步,立即执行)
3
(微任务,先执行)
4
(同步,立即执行)
2
(宏任务,后执行)
如果明白了的话就看下面的
console.log(1)
setTimeout(function() {
console.log(2)
new Promise(function(resolve) {
console.log(3)
resolve()
}).then(function() {
console.log(4)
})
})
new Promise(function(resolve) {
console.log(5)
resolve()
}).then(function() {
console.log(6)
})
setTimeout(function() {
console.log(7)
new Promise(function(resolve) {
console.log(8)
resolve()
}).then(function() {
console.log(9)
})
})
console.log(10)
输出结果:1,5,10,6,2,3,4,7,8,9
1
同步任务, 直接执行.
setTimeout宏任务, 直接丢入宏任务队列.
promise本身不是一个微任务, then才是;
所以5
是同步任务直接执行输出, 遇到then微任务, 丢入微任务队列
setTimeout宏任务, 直接丢入宏任队列
10
同步任务, 直接执行, 输出.
主线程执行完毕, 再来执行微任务队列; 第一个进来的6
执行输出
微任务队列执行完毕, 执行宏任务队列; 第一个进来的2
执行输出, 下面3
同步任务执行输出.
遇到then, 所以4
丢入微任务队列
现在主线程是空的, 微任务队列中有4
, 宏任务队列也有任务; 微任务优先级大于宏任务, 所以先执行4
现在主线程是空的, 微任务队列也空了, 宏任务队列中还有第二个setTimeout
7
同步任务, 直接执行输出
8
同步任务, 直接执行输出
遇到then, 9
丢入微任务队列
主线程为空, 执行微任务队列, 输出9