this的不同应用场景,如何取值
- 当做普通函数被调用
this=window - 使用call apply bind
this=call | apply | bing 中绑定的this - 作为对象方法调用
this=对象本身 - 在class中的方法中调用
this=当前实例 - 箭头函数
this=上一个作用域的this
手写bind函数
Function.prototype.myBind = function(context) {
//返回一个绑定this的函数,我们需要在此保存this
let self = this
// 可以支持柯里化传参,保存参数
let arg = [...arguments].slice(1)
// 返回一个函数
return function() {
//同样因为支持柯里化形式传参我们需要再次获取存储参数
let newArg = [...arguments]
console.log(newArg)
// 返回函数绑定this,传入两次保存的参数
//考虑返回函数有返回值做了return
return self.apply(context, arg.concat(newArg))
}
}
实际中闭包的应用
- 隐藏数据
- 制作一个缓存的应用
function createCache(){
const data={}//需要缓存的数据
return {
set:(key,val)=>{
data[key]=val
},
get:key=>data[key]
}
}
const c=createCache()
c.set('name','张三')//设置缓存的数据
const name=c.get('name')//获取缓存中的数据
console.log(name)
同步和异步的区别
- 基于js是单线程语言
- 同步会阻塞代码执行
- 异步不会阻塞代码执行
property和attribute的区别
property
:修改对象属性, 不会体现html结构中attribute
:修改html属性,会改变html结构- 两者都有可能引起DOM重新渲染
DOM是哪种数据结构
树(DOM结构)
DOM性能
- DOM操作非常昂贵,避免频繁操作DOM
- 对DOM查询做缓存
- 将频繁操作改为一次性操作
DOM查询做缓存
//不缓存DOM查询结果
for(let i=0;i<document.getElementByTagName('p').length;i++){
//每次循环,都会计算length,频繁进行DOM查询
}
//缓存DOM查询结果
const pList=document.getElementByTagName('p')
const pLength=pList.length
for(let i=0;i<length;i++){
//缓存length,只进行一次DOM查询
}
将频繁操作改为一次性操作
- 一次插入多个节点,考虑性能
const listNode =document.getElementById('list')
//创建一个文档片段,此时还没有插入到DOM树中
const frag=document.createDocumentFragment()
//执行插入
for(let i=0;i<10;i++){
const li=document.createElement('li')
li.innerHTML=`第${i}个li`
frag.appendChild(li)
}
//循环结束后再插入到dom树中
listNode.appendChild(flag)
编写一个通用的事件绑定函数
function bindEvent(elem,type,selector,fn){
if(fn==null){//说明不需要代理
fn=selector
selector=null
}
elem.addEventListener(type,e=>{
let target
if(selector){
//需要代理
target=e.target
//元素被指定的选择器字符串选择,Element.matches() 方法返回true; 否则返回false
if(target.mathes(selector)){
fn.call(target,e)
}else{
//不需要代理
fn(e)
}
}
})
}
描述事件冒泡
- 基于DOM树形结构
- 事件会顺着触发元素往上冒泡,一层一层往上冒
- 应用场景:事件代理
Cookie的缺点
- 存储大小,最大4KB
- http 请求时需要发送到服务端,增加请求数据量
- 只能用document.cookie=’…’ 来修改,太简陋
localStorage和sessionStorage
- html5专门为存储而设计,最大可存5M
- API简单好用
- 不会随着http请求发送出去
- localStorage数据会永久存储,除非代码或手动删除
- sessionStorage数据只存在当前会话,浏览器关闭则清空
描述cookie localStorage sessionStorage的区别
- cookie容量为4KB,localStorage和sessionStorage容量为5M
- localStorage数据会永久存储,除非代码或手动删除
- sessionStorage数据只存在当前会话,浏览器关闭则清空
- cookie会请求服务端,localStorage、sessionStorage不会请求服务端
window.onload 和 DOMContentLoaded区别
- window.onload 在页面全部资源加载完成后才能执行,包括图片视频
- DOMContentLoaded 在DOM加载完成即可,图片可能尚未加载
页面渲染过程
- 根据HTML代码生成DOM Tree
- 根据CSS代码生成CSSOM
- 将DOM Tree 和CSSOM结合形成Rander Tree
- 根据Rander Tree 渲染页面
- 遇到script则暂停渲染,优先加载并执行js代码,完成后再继续
- 直至把Rander Tree 渲染完成
编写防抖函数
防止频繁向服务器发送请求
// 防抖
debounce(fn, delay = 500) {
// timer是闭包中的
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
编写节流函数
每隔一段时间触发一次
// 节流
throttle(fn, delay = 100) {
// timer是闭包中的
let timer = null
return function () {
if (timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
let、const、var的区别
- 使用
var
声明的变量,其作用域为该语句所在的函数内
,并且存在变量提升 - 使用
let
声明的变量,其作用域为该语句所在的代码块内,不存在变量提升 - 使用
const
声明的是常量,不能修改这个常量,且需要赋初始值
typeof能判断哪些类型
- undefined 、string、number、boolean、symbol
- object(注意typeof null===‘object’)
- function
列举强制类型转换和隐式类型转换
- 强制:parseInt、parseFloat、toString等
- 隐式:if、逻辑运算、==、+拼接字符串
手写深度比较,模拟lodash.isEqual
//判断是否为对象类型
function isObject(obj){
return typeof obj==='object'&&obj!==null
}
//深度比较两个值
function isEqual(obj1,obj2){
//判断是否为对象
if(!isObject(obj1)||!isObject(obj2)){
//说明是值类型
return obj1===obj2 //直接比较
}
//说明传入的两个元素为同一个元素直接返回true
if(obj1===obj2){
return true
}
//判断两个对象属性个数是否相等
//如果不相等则返回false
const obj1Keys=Object.keys(obj1)
const obj2Keys=Object.keys(obj2)
if(obj1Keys.length!==obj2Keys.length){
return false
}
//使用递归进行深度比较,以obj1为基准作比较
for(let key in obj1){
const res=isEqual(obj1[key],obj2[key])
//判断res结果,如果为false说明不相等,直接返回false
if(!res){
return false
}
}
return true
}
split()和join()的区别
例如:
'1-2-3'.split('-')//[1,2,3]
[1,2,3].join('-')//'1-2-3'
- split是将字符串按指定字符分割成数组
- join是将数组按指定字符并结成字符串
数组的pop push shift unshift分别做什么
pop
弹出数组最后一个元素,返回结果为弹出的元素
const arr=[10,20,30,40]
const arrRes=arr.pop()
console.log(arrRes)//40
push
添加元素到数组最后一个,返回结果为数组长度
const arr=[10,20,30,40]
const arrRes=arr.push(50)
console.log(arrRes)//返回为数组长度
unshift
添加元素到数组第一个,返回结果为数组长度
const arr=[10,20,30,40]
const arrRes=arr.unshift(50)
console.log(arrRes)//返回为数组长度
shift
移除数组第一个元素,返回结果为数组长度
const arr=[10,20,30,40]
const arrRes=arr.unshift(50)
console.log(arrRes)//返回为数组长度
这些函数都会改变原数组
补充:
纯函数
- 不改变原数组(没有副作用)
- 返回一个数组
常见纯函数有
- concat()
- map()
- filter()
- slice()
非纯函数有
- push()
- pop()
- shift()
- unfhift()
- foreach()
- some()
- every()
- reduce()
数组slice()和splice区别
slice()
- 截取数组元素
参数1
:开始截取的索引位置参数2(可选)
:结束截取的索引位置- 不写参数2将截取参数1后所有元素
- 返回一个新的数组
const arr=[10,20,30,40]
const arrRes=arr.slice(1,3)
console.log(arrRes)//[20,30,40]
splice()
- 截取数组元素
参数1
:开始剪贴的索引位置参数2
:剪贴的个数- 参数n(可选):在剪贴的位置插入的元素
- 会改变原数组
const arr=[10,20,30,40]
const arrRes=arr.splice(1,2,'a','b')
console.log(arrRes)//[10,'a','b',40]
get和post的区别
- get一般用于查询操作,post一般用于用户提交操作
- get参数拼接在url上,post参数放在请求体内(数据体积更大)
- post易于防止CSRF
函数call和apply的区别
fn.call(this,p1,p2,p3)
fn.apply(this,arguments)
传参形式不同,call以依次传入方式传参,而apply以数组形式传参
闭包是什么?有何特性?有何影响?
闭包
- 闭包是指有权访问另一个函数作用域中的变量的函数
特性
- 外界无法访问闭包内部的数据
- 一般的函数,调用完毕之后,系统自动注销函数,而对于闭包来说,在外部函数被调用之后,闭包结构依然保存在
影响
- 使用闭包会占有内存资源,过多的使用闭包会导致内存泄露等。
如何阻止事件冒泡和默认行为
- event.stopPropagation() 阻止事件冒泡
- event.preventDefault() 阻止默认行为
函数声明和函数表达式的区别
- 函数声明function fn(){…}
- 函数表达式 const fn=function(){…}
- 函数声明在代码执行前会预加载,而函数表达式不会
new Object() 和 Object.create()的区别
- {} 等同于 new Object(),原型Object.prototype
- Object.create(null)没有原型
- Object.create({…})可以指定原型
判断字符串以字母开头,后面是字母数字下划线,长度6-30
- const reg=/^ [a-zA-z]\w{5,29}/
手写字符串trim(),保证浏览器兼容性
String.prototype.trim=function(){
return this.replace(/^\s+/,'').replace(/\s+$/,'')
}
什么是JSON
- JSON是一种数据格式,本质是一段字符串
- JSON格式和js对象结构一致,对js语言更友好
- window.JSON是全局对象:JSON.stringify() JSON.parse()
获取当前页面url参数
function getUrl(url) {
var urlObj={}
var arr=url.split("?")[1].split('&')
for(var i=0;i<arr.length;i++){
var newArr=arr[i].split('=')
urlObj[newArr[0]]=newArr[1]
}
return urlObj
}
手写flatern 考虑多层级
let arr=[1,2,3,[4,5,[6,7]]]
//方式一:递归
function flat(arr){
//验证arr是否存在深层嵌套
const isDeep=arr.some(item=>item instanceof Array)
if(!isDeep){//说明不存在深层嵌套
return arr
}
const res=[].concat(...arr)
return flat(res)//使用递归
}
//方式二:字符串tostring
function flat(arr){
return arr.toString().split(',').map(item=>{
return Number(item)
})
}
//方式三:json.stringify(更麻烦)
//JSON.stringify(arr):"[1,2,3,[4,5,[6,7]]]"
//replace(/\[|\]/ig,"") '"1","2","3","4","5","6""7"'
//最后使用map将字符串类型转换成数字类型
function flat(arr){
return JSON.stringify(arr).replace(/\[|\]/g,"").split(',').map(item=>{
return Number(item)
})
}
数组去重
方式一 (传统方式)
function unique(arr){
const res=[]
arr.foreach(item=>{
if(res.indexOf(item)<0){//说明不存在
res.push(item)
}
})
return res
}
方式二 (set)
function unique(arr){
const set=new Set(arr)
return [...set]
}