JS面试一 - jkiodgsfiohasdf

看到题目,想到考点

JS知识体系面试知识点

变量类型和计算

javascript允许直接对变量赋值而无需事先声明,但声明变量是一个良好的习惯。

命名
1. 类:以大写字母开头
2. 方法:以小写字母开头
3. 常量名全部大写
4. 通常运算符 ( = + - * / ) 前后需要添加空格

JS中分号的使用

一行开头是括号或者方括号的时候加上分号就可以了,其他时候全部不需要

var let const的区别?
1.使用var声明的变量,函数作用域,且存在变量提升现象;在全局作用域中声明会成为window对象的属性。
2.使用let声明的变量,块作用域(函数作用域的子集),不存在变量提升;在全局作用域中声明不会成为window对象的属性。
3.使用const声明基本和let相同,声明变量时必须初始化变量,修改变量时会报错。若const引用对象,则此对象内部属性可以修改。
var o1={b:1}实现了在堆内存中创建了一个对象{b:1},o1则存储了该对象在堆内存中的地址,即o1是一个指针,指向{b:1};

箭头函数=>

在箭头函数内使用this,是在使用它的上级作用域的this

值类型和引用类型

值类型存储直接保存在栈内存中,值与值之间是独立存在,修改一个变量不会影响其他的变量。

1.数值(number):整数和小数(比如1和3.14)
2.字符串(string):文本(比如Hello World),JavaScript里用 \(反斜杠)对字符进行转义。
3.布尔值(boolean):表示真伪的两个特殊值,即true(真)和false(假)
4.undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值
5.null:表示空值,即此处的值为空。
6.sumbol:表示符号,即此处的值为空。
7.Object:表示符号,即此处的值为空。

引用类型存储是保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间。变量保存的是对象的内存地址(对象的引用)。

  • 对象(object):各种值组成的集合。(如果两个变量保存的是同一个对象引用,当一个通过一个变量修改属性时,另一个也会受到影响),JavaScript中所有变量都是某种类型的对象。
//值类型
let a=1
let b=a
b=100
console.log(a)//1

//引用类型
let a={age:25}//{}是一个对象
let b=a
b.age=100
console.log(a.age)//100

手写深拷贝

Object.assign不是深拷贝!!!

/**
 * 深拷贝
 */
const obj1 = {
    age: 20,
    name: 'xxx',
    address: {
        city: 'beijing'
    },
    arr: ['a', 'b', 'c']
}

const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])

/**
 * 深拷贝
 * @param {Object} obj 要拷贝的对象
 */
function deepClone(obj = {}) {
    if (typeof obj !== 'object' || obj == null) {
        // obj 是 null ,或者不是对象和数组,直接返回
        return obj
    }

    // 初始化返回结果
    let result
    if (obj instanceof Array) {
        result = []
    } else {
        result = {}
    }

    for (let key in obj) {
        // 保证 key 不是原型的属性
        if (obj.hasOwnProperty(key)) {//hasOwnProperty:拥有自身的属性
            // 递归调用!!!
            result[key] = deepClone(obj[key])
        }
    }

    // 返回结果
    return result
}
类型判断

typeof 能判断哪些类型?

考点:JS的变量类型
(1)typeof能识别所有值类型
(2)识别函数:function
(3)判断是否是引用类型:object

字符串拼接

const a = 100 + 10 //110
const 100 + parseInt('10')//110
const b = 100 + '10'//'10010'
const c = true + '10'//'true10 '

==何时使用 = = = 何时使用 = ===
考点:强制类型转换

//只有判断==null之外,其余一律用===
100 == '100'//true
0 == ''//true
0 == false //true
false == ''//true
null == undefined //true 
逻辑运算

if语句和逻辑运算

const n = 100;
!n//false
!!n//true

原型和原型链

原型和原型链

1.每个class都有显示原型属性:prototype:构造函数有一个prototype属性,指向实例对象的原型对象
2.每个实例都有隐式原型:__ proto__, :指向该实例对象对应的原型对象
3.原型对象有一个constructor属性: 指向该原型对象对应的构造函数
4.获取属性或者执行方法时,先在自身属性和方法中寻找,如果找不到则自动去proto__中寻找,最终都继承于Object对象,Object对象的没有原型,如果在Object原型中依然没有找到,则返回 null

class的原型本质,怎么理解?

instanceof

isPrototypeOf() 方法用于测试一个对象是否存在于另一个对象的原型链上。
instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

如何准确判断一个变量是不是数组

a instanceof Array
class和继承
//父类:People
class People{
    constructor(name){
        this.name=name 
    };
    eat(){
        console.log(`${this.name}要吃东西`)
    };
};

//子类:Student
class Student extends People {
    constructor(name,number){
        super(name)
        this.number=number
    };
     sayHi(){
        //建议这么写输出
        console.log(
        `姓名 ${this.name}上课要说你好`
        );
        //还可以用字符串拼接的方式
         console.log(
             '姓名' + this.name +',学号'+this.number
        );
    };
};

//子类:Teacher
class Teacher extends People{
    constructor(name,major){
        super(name)
        this.major = major
    };
    teacher(){
        console.log(`${this.name}教授${this.major}`)
    }
}
//通过类声明对象/实例
const luojing = new Student('罗京',21907010041)
console.log(luojing.name)
console.log(luojing.number)
console.log(luojing.sayHi())
luojing.eat()
//通过类声明对象/实例
const zhangsan = new Teacher('zhangsan','数学')
console.log(zhangsan.name)
console.log(zhangsan.number)
console.log(zhangsan.teacher())
zhangsan.eat()

//隐式原型和显示原型
console.log(luojing.__proto__)//__proto__:隐式原型
console.log(Student.prototype )// prototype:显示原型
console.log(luojing.__proto__===Student.prototype)//true

手写一个简易的jQuery,考虑插件和扩展性

class jQuery{
    constructor(selector){
        const result = document,querySelectorAll(selector)
        const length=result.length
        for(let i=0;i<length;i++){
            this[i]=result[i]
        };
        this.length=length
    };
    get(index){
        return this[index]
    };
    each(fn){
        for(let i=0;i<this.length;i++){
            const ele =this[i]
            fn(elem)
        };
    };
    on(type,fn){
        return this.each(elem =>{
            elem.addEventListener(type,fn,false)
        });
    };     
};

//插件
jQuery.prototype.dialog=function(){
    alert(info)
}

//复写(造轮子)
class myJQuery extends jQuery{
    constructor(selector){
        super(selector)
    }
    //扩展自己的方法
    addClass(className){
        
    };
    style(data){
        
    }
}

作用域和闭包

this

this取什么值是在函数执行的时候确认的,不是在定义的地方确认的

this的不同应用场景,如何取值

使用场景:
1.作为普通函数被调用
2.使用call apply bind
3.作为对象方法被调用
4.在class方法中被调用
5.箭头函数

//场景1
function fn1(){
    console.log(this)
}
fn1()//windows
fn1.call({x:100})//x:100
const fn2 = fn1.bind({x:200})
fn2()//{x:200}

//场景2
const zhangsan={
    name:'张三'
    sayHi(){
        //this即是当前zhangsan对象
        console.log(this)
    };
wait(){
    setTimeout(function(){
        //this===window
        console.log(this)
    })
}
}

//场景3
const zhangsan={
    name:'张三'
    sayHi(){
        //this即是当前对象
        console.log(this)
    };
waitAgain(){
    setTimeout(()=>{
        //this即当前对象
        console.log(this)
    })
}
}

手写bind函数

function fn1(a,b){
    console.log('this',this)
    console.log(a,b,c)
    return 'this is fn1'
}

const fn2 =fn1.bind({x:100},10,20,30)
const res=fn2()
console.log(res)

//模拟bind 
Function.prototype.bind1=function(){
    //将参数拆解为数组
    const args=Array.prototype.slice.call(arguments)
    
    //获取this(数组第一项)
    const t = args.shift()
    //fn1.bind(...)中的fn1
    const self =this()
    
    //返回一个函数
    return function(){
        return self.apply(t,args)
    }
    
}

实际开发中闭包的应用场景,举例说明

//创建10个'<a>'标签,点击时弹出对应的序号
let a
for(let i=0;i<10;i++){
    a=document.createElement('a')
    a.innerHTML=i+'<br>'
    a.addEventListener('click',function (e){
        e.preventDefault()
        alert(i)
    })
    document.body.appendChild(a)
}
作用域和自由变量

一个变量在当前作用域没有定义,但被使用了,向上级作用域,一层一层依次寻找,直至找到为止。如果到了全局作用域都没有找到,则报错xx is not defined

let a=0
function fn1(){
    let a1 = 100
    
    function fn2(){
        let a2 = 200
        
        function fn3(){
            let a3 = 300
            return a + a1 + a2 + a3
        }
        fn3()
    }
    fn2()
}
fn1()
闭包

作用域应用的特殊情况,有两种表现:
1.函数作为参数被传递。
2.函数作为返回值被返回

//函数作为返回值
function create(){
    let a=100
    return function(){
        console.log(a)
    }
}
let fn=create()
let a = 200
fn()//100


//函数作为参数
function print(fn){
    let a =200
    fn()
}
let a=100
function fn(){
    console.log(a)
}
print(fn)//100

//闭包:自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方!

开发中闭包的应用
隐藏数据
做一个简单的cache工具

//闭包隐藏数据,只提供API
function createCache(){
    const data= {}//闭包中的数据,被隐藏,不被外界访问
    return{
        set:function(key,val){
            data[key]=val
        },
        get:function(key){
            return data[key]
        }
    }
}

const c =createCache()
c.set('a',100)
console.log(c.get('a'))

异步和单线程

同步和异步的区别?

JS是单线程语言,只能同时做一件事,异步是解决单线程的等待问题。每个异步都需要加一个callback函数。异步不会阻塞代码执行。

手写Promise加载一张图片

//promise图片加载
console.log('start')
function loadImg(src){
    return new Promise(
    (resolve,reject)=>{
        const img=document.createElement('img')
        img.onload=()=>{
            resolve(img)
        }
        img.onerror=()=>{
            const err=new Error(`图片加载失败'${src}`)
            reject(err)
        }
        img.src=src
    }
    )
}

const url='https://baike.baidu.com/pic/%E5%88%98%E4%BA%A6%E8%8F%B2/136156/21228350/f703738da977391281207810f6198618367ae2b9'//图片地址

loadImg(url).then(img=>{
    console.log(img.width)
    return img
}).then(img=>{
    console.log(img.heught)
}).catch(ex=>console.error(ex))

前端使用异步的场景

//setTimeout笔试题,输出顺序13452
console.log(1)
setTimeout(function(){
    console.log(2)
},1000)
console.log(3)
setTimeout(function(){
    cnsole.log(4)
},0)
console.log(5)
应用场景

网络请求,如ajax图片加载
定时任务,如setTimeout

//ajax
console.log('start')
$.get('./data1.json',function(data1){
    console.log(data1)
})
console.log('end')

//图片加载
console.log('start')
let img=document.createElement('img')
img.onload=function(){
    console.log('loaded')
}
img.src = '/xxx.png'
console.log('end')

//定时器setTimeout,(一次性)
console.log(1)
setTimeout(function(){
    console.log(2)
},1000)
console.log(3)

//定时器setInterval,(循环)
console.log(1)
setInterval(function(){
    console.log(2)
},1000)
console.log(3)
callback和Promise
//callback hell,嵌套,有很多层
//获取第一份数据
$.get(url1,(data1)=>{
    console.log(data1)
    
    //获取第二份数据
    $.get(url2,(data2)=>{
        console.log(data2)
        //获取第三份数据
        $.get(url3,(data3)=>{
            console.log(data3)
            //还可以获取更多数据
        })
    })
})
// Promise,通过then就变成按顺序执行
function getData(url){
    return new Promise((resolve,reject)=>{
        $.ajax({
            url,
            success(data){
                resolve(data)
            },
            error(err){
                reject(err)
            }
        })
    })
}

const url1='/data1.json'
const url2='/data2.json'
const url3='/data3.json'
getData(url1).then(data1=>{
    console.log(data1)
    return getData(url2)
}).then(data2=>{
    sonsole.log(data2)
    return getData(url3)
}).then(data3=>{
    console.log(data3)
}).catch(err=>console.error(err))

JS-Web-API

DOM是哪种数据结构
树形结构

DOM操作的常用API
DOM节点操作,DOM结构操作

Vue和React框架应用广泛,封装了DOM操作。api:应用程序编程接口(预先定义好的函数)

DOM

DOM的本质

DOM是一棵树型结构。

DOM的节点操作

获取DOM节点

//获取DOM节点
//方式一:通过 id 获取一个元素节点
var div1 = document.getElementById("box1"); 

//方式二:通过 标签名 获取 元素对象的集合 以数组形式存储,所以有s
var arr1 = document.getElementsByTagName("div"); 

//方式三:通过 类名 获取 元素节点数组,所以有s
var arr2 = document.getElementsByClassName("hehe");

//方式四:返回指定选择器的所有元素对象。".box"说明"box"是一个类;"#box"说明"box"是一个id   
var randomName=document.querySelectorAll('.box');

//返回指定选择器的第一个元素对象。".box"说明"box"是一个类;"#box"说明"box"是一个id   h5以上才支持
var randomName=document.querySelector('.box');

//获取html元素
var htmlEle = document.documentElement;                                   

//获取body元素
var bodyEle = document.body;
属性

attr和property的区别

property修改对象属性,不会体现在html结构中(尽量使用)。attribute修改html属性,会改变html结构。两者都有可能引起DOM重新渲染

property

通过修改js对象的属性来改变页面样式的一种形式,不会体现到html结构中(尽量使用)

const pList =document.querySelectorAll('p')
const p= pList[0]
//property形式
console.log(p.style.width)//获取样式
p.style.width = '100px'//修改样式
console.log(p.className)//获取class
p.className='p1'//修改class

//获取nodeName和nodeType
console.log(p.nodeName)
console.log(p.nodeType)

attribute

直接修改html标签的属性,会改变html结构(尽量不要用)

//attribute形式
const pList = document.querySelectorAll('p')
const p=plist[0]
p.getAttribute('data-name')
p.setAttribute('data-name','imooc')
p.getAttribute('style')
p.setAttribute('style','font-size:30px')
DOM的结构操作

新增/ 插入节点

const div1 = document,getElementById('div1')

//添加新节点
//新建节点
const p1 = document.createElement('p')
//赋值内容
p1.innerHTML='this is p1'
//插入节点
div1.appendChild(p1)
//移动节点,注意是移动!!!
const p2 = document.getElementById('p2')
div1.appendChild(p2)

获取子元素列表,获取父元素

//获取子元素列表
const div1 =document.getElementById('div1')
const child = div1.ChildNodes

//获取父元素
const div1=document,getElementById('div1')
const parent=div1.parentNode

删除子节点

const div1 =document.getElementById('div1')
const child= div1.childNodes
div1.removeChild(child[0])
DOM性能

DOM操作非常“昂贵”,避免频繁的DOM操作

DOM查询做缓存

//不缓存DOM查询结果
for(let =0;i<document.getElementsByTagName('p').length;i++){
    //每次循环,都会计算length,频繁进行DOM查询
}

//缓存DOM查询结果
const pList=document.getElementsByTagName('p')
const length=pList.length
for(let i=0;i<length;i++){
    //缓存length,只进行一次DOM查询
}

对DOM查询做缓存

一次性插入多个DOM节点,考虑性能
将频繁错做改为一次性操作

const listNode=document.getElementById('list')
//创建一个文档片段,此时还没有插入DOM树中
const frag=document.createDocumentFragment()

//执行插入
for(let x=0;x<10;x++){
    const li=document.createElement("li")
    li.innerHTML="List item"+x
    //先插入文档片段中
    frag.appendChild(li)
}
//都完成之后,再插入到DOM树中
listNode.appendChild(frag)

BOM

问题:

如何识别浏览器的类型?

如何拆解url各个部分?

//navigator
const ua =navigator.userAgent
const isChrome =ua.indexOf('Chrome')
console.log(isChrome)
 
//screen
console.log(screem.width)
sonsole.log(screen.height)

//location
console.log(location.href)
console.log(location.protocol)
console.log(location.pathname)
console.log(location.search)
console.log(location.hash)
  
//history
history.back()
history.forward()
navigator

浏览器信息

screen

屏幕信息

location

地址信息

history

前进后退

事件

题目:
编写一个通用的事件监听函数?
描述事件冒泡的流程?

事件冒泡是基于DOM树形结构,事件会顺着触发元素往上冒泡,应用场景就是事件代理

无限下拉的图片列表,如何监听 每个图片的点击?

使用事件代理,用e.target获取触发元素。用matches来判断是否是触发元素

事件绑定
const btn = document.getElementById('btn1')
btn.addEventListener('click',event=>{
    console.log('clicked')
})

//通用的绑定函数
function bindEvent(elem,type,fn){
    elem.addEventListener(type,fn)
}
//通过id来绑定
const a= document.getElementById('link1')
bindEvent(a,'click',event =>{
    event.preventDefault()//阻止默认行为
    alert('clicked')
})
事件冒泡

当一个元素接收到事件的时候 会把他接收到的事件传给自己的父级,一直到window

const p1 =document.getElementById('p1')
const body = document.body
bindEvent(p1,'click',e=>{
    e.stopPropagation()//阻止事件冒泡
    alert('激活')
})
bindEvent(body,'click',e=>{//在body处监听事件
    alert('取消')
})
事件代理

基于事件冒泡

<div id="div1">
    <a herf="#">a1</a>
    <a herf="#">a2</a>
    <a herf="#">a3</a>
    <a herf="#">a4</a>
</div>
<button>
    点击增加一个a标签
</button>
function bindEvent(elem,type,selector,fn){
    if(fn==null){
        fn=selector
        selector=null
    }
}
elem.addEventListener(type,event=>{
    const target = event.target
    if(selector){
        //代理绑定
        if(target.matches(selector)){
            fn.call(target,event)
        }
    }else{
        //普通绑定
        fn.call(target,event)
    }
})
//普通绑定
const a= document.getElementById('link1')
bindEvent(a,'click',event =>{
    event.preventDefault()//阻止默认行为
    alert('clicked')
})
//代理绑定
const div1 = document.getElementById('div1')
bindEvent(div1,'click','a',event=>{
    event.preventDefault()//阻止默认行为
    alert(target.innerHTML)
})

ajax

手写一个简易的ajax-

function ajax(url,successFn){
    const xhr = new XMLHttpRequest()
    xhr.open("GET",url,true)
	xhr.onreadystatechange = function(){
        if(xhr.readState === 4){
            if(xhr.status === 200){
                alert(xhr.responseText)
            }
        }
    }
    xhr.send(null)
}
XMLHttpRequsetAPI
//get请求
const xhr =new XMLHttpRequest()
xhr.open("GET","/api",true)// true代表异步请求;
xhr.onreadystatechange = function(){
    //这里的函数是异步执行,可参考之前JS基础中的异步模块
    if(xhr.readState === 4){
        if(xhr.status === 200){
            alert(xhr.responseText)
        }
    }
}
xhr.send(null)//post请求
const xhr =new XMLHttpRequest()
xhr.open("POST","/login",true)// true代表异步请求;
xhr.onready·tatechange = function(){
    //这里的函数是异步执行,可参考之前JS基础中的异步模块
    if(xhr.readState === 4){
        if(xhr.status === 200){
            alert(xhr.responseText)
        }
    }
}
xhr.send(null)
状态码

xhr.readyState
0:(未初始化)还没有调用send()方法
1:(载入)已调用send()方法,正在发送请求
2:(载入完成)send()方法执行完成,已经接收到全部响应内容
3:(交互)正在解析响应内容
4:(完成)响应内容解析完成,可以在客户端调用

xhr.status
2xx:表示成功处理请求,如200
3xx:需要重定向,浏览器直接跳转,如301 302 304
4xx:客户端请求错误,如404 403
5xx:服务器端错误

跨域

同源策略,跨域解决方案
ajax请求时,浏览器要求当前网页和server必须同源(安全)

同源:协议,域名,端口。三者必须一致。加载图片css js可无视同源策略。

//可以无视同源策略
<img src=跨域的图片地址,可用于统计打点,可使用第三方统计服务/>	
<link href=跨域的css地址,可使用CDN(CDN一般都是外域)/>	
<script src=跨域的js地址,可实现JSONP></script> 

跨域:所有的跨域,都必须经过server端允许和配合,未经server端允许就实现跨域,说明浏览器有漏洞

跨域的常用的实现方式

JSONP原理,CORS

JSONP
/*
<script>可绕过跨域限制
服务器可以任意动态拼接数据返回,所以,<script>就可以获得跨域的数据,只要服务端愿意返回
*/
<script>
    window.callback = function(data){
    console.log(data)
}
<script src="http://localhost:8002/jsonp.js?username=xxx"><script>
callback(
	{name:'xxx'}
)
CORS

服务器设置 http header

//第二个参数填写允许跨域的域名称,不建议直接写“*”
response.setHeader("Access-Control-Allow-Origin","http://localhost:8011");
response.setHeader("Access-Control-Allow-Headers","X-Request-With");
response.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");

//接收跨域的cookie
response.setHeader("Access-Control-Allow-Credentials","true");
常用的ajax插件

JQuery Fetch Axios(推荐)

存储

描述cookie localStorage sessionStorage区别

cookie

本身用于浏览器和server通讯,被“借用”到本地存储来,只能用document.cookie = '...'来修改,最大存储4kb,http请求时需要发送到服务端,增加请求数据量。

lacalStorage和sessionStorage

HTML5专门为存储而设计,最大可存5M,API简单易用setItem getItem。不会随着http请求被发送出去。

localStorage数据永久存储,除非代码或手动删除(使用较多)。sessionStorage数据只存在于当前会话,浏览器关闭则清空。

开发环境

git

git add .//添加
get checkout xxx//还原
git commit -m "xxx"//提交
git push origin master//推送
git pull origin master//下载
git branch//开发分支
git checkout -b xxx/git checkout xxx// 切换分支
git merge xxx //合并分支

chrome调试工具

Elements:DOM结构 Console debugger Network:资源加载 Application:存储

抓包

fiddler(windows) charles(macos)

webpack和babel

babel是一个JS编译器,用来转换最新的JS语法,比如把ES6, ES7等语法转化成ES5语法,从而能够在大部分浏览器中运行。

webpack是一个打包工具,打包js文件,css文件,图片,html等等,它可以分析整个项目的文件结构,确认文件之间的依赖,比如一个js文件引入了另一个js文件。在这个过程中可以合成js,压缩,加入hash等,最终生成项目文件。

linux命令

ssh 用户名@IP地址	//本地登录线上
ls	//查看文件夹
ls -a	//查看隐藏文件
mkdir	//创建文件夹
rm -rf 文件夹	//删除文件夹
mv	//修改文件名
cp	//拷贝
touch	//新建文件

运行环境

页面加载过程

从输入url到渲染出页面的整个过程

  1. 加载资源的形式

    html代码 媒体文件 js和css

  2. 加载资源的过程

    (1) DNS解析:域名->ip地址

    (2) 浏览器根据ip地址向服务器发起http请求

    (3) 服务器处理http请求,并返回给浏览器

  3. 渲染页面的过程

    (1) 根据HTML代码生成DOM Tree

    (2) 根据CSS代码生成CSSOM (建议把css放在head中就是为了避免重复渲染)

    (3) 将DOMTree和CSSOM整合形成Render Tree

    (4) 根据Render Tree渲染页面

    (5) 遇到

window.onload和DOMContentLoaded的区别

window.onload资源全部加载完才能执行,包括图片

DOMContentLoaded渲染完即可,图片可能尚未下载

性能优化

加载资源优化

多使用内存,缓存或其他方法。减少cpu计算量,减少网络加载耗时。适用于所有编程的性能优化 - 空间换时间。加载更快:(1)减少资源体积:压缩代码。(2)减少访问次数:合并代码,ssr服务器端渲染,缓存。(3)使用更快的网络:CDN

渲染优化

渲染更快:(1)CSS放在head,JS放在body最下面。(2)尽早开始执行JS,用DOMContentLoaded触发。(3)懒加载(图片懒加载,上滑加载更多)(4)对DOM查询进行缓存。(5)频繁DOM操作,合并到一起插入DOM结构。(6)节流throttle防抖debounce

缓存:(1)静态资源加hash后缀,根据文件内容计算hash。(2)文件内容不变,则hash不变,则url不变。(3)url和文件不变,则回自动触发http缓存机制,返回304.
CDN:
SSR:服务器端渲染:将网页和数据一起加载,一起渲染。非SSR(前后端分离):先加载网页,在加载数据,在渲染数据。
懒加载:先加载预览图,当图片出现在屏幕时再加载图片
缓存DOM查询:DOM查询慢,JS执行很快

手写防抖(定时器)

//监听一个而输入框,文字变化后触发change事件
//直接用keyup事件,则会频繁触发change事件
//防抖:用户输入结束或暂时时,才会触发change事件(定时器)
function debounce(fn,delay = 500){
    //timer是闭包中的
    let timer =null
    return function(){
        if(timer){
            clearTimeout(timer)
        }
        timer=setTimeout(()=>{
            fn.apply(this,arguments)
            timer = null
        },delay)
    }
}


手写节流(定时器)

//拖拽一个元素时,要随时拿到该元素被拖拽的位置
//直接用drag事件,则会频繁触发,很容易导致卡顿
//节流:无论拖拽速度多块,都会每间隔100ms触发一次
function throttle(fn,delay =100){
    let timer=null
    return function(){
        if(timer){
            return
        }
        timer = setTimeout(()=>{
            fn.apply(this,arguments)
            timer = null
        },delay)
    }
}

安全

常见的web前端攻击方式有哪些?

xss跨站请求攻击:嵌入

xsrf跨站请求伪造:使用post接口,增加验证

题目汇总:

typeof能判断那些类型?
何时使用===何时使用==?
值类型和引用类型的区别?
手写深拷贝?
如何准确判断一个变量是不是数组?
手写一个简易的额jQuery,考虑插件和扩展性
class的原型本质,怎么理解?
this的不同应用场景,如何取值?
手写bind函数
实际开发中闭包的应用场景,举例说明
同步和异步的区别是什么?
手写用promise加载一张图片
前端使用一部的场景有哪些?
DOM是哪种数据结构?
DOM操作的常用api
attr和property的区别
 一次性插入多个DOM节点,考虑性能
 如何识别浏览器的类型
 分析拆解url各个部分
 编写一个通用的事件监听函数
 描述事件冒泡的流程
 无限下拉图片列表,如何监听每个图片的点击
 手写一个简易的ajax
 跨域的常见实现方式
 描述cookie localStorage sessionStorage区别
 从输入url到渲染出页面的整个过程
 window.onload和DOMContentLoaded的区别?
 前端常见的性能优化方案
 手写节流和防抖
 Web前端常见的安全攻击方式和预防

简历:简洁明了,突出个人技能和项目经验。个人把博客,开源作品放在简历中。简历不要造假,保证能力上的真实性(不要用精通)

  1. 如何看待加班:像借钱,救急不救穷。
  2. 千万不要挑战面试官,不要反考面试官。
  3. 学会给面试官惊喜,证明你能想到更多,做得更好,但不要太多。
  4. 遇到不会的问题,说出你知道的部分即可,但别岔开话题
  5. 谈谈你的缺点:说一下最近在学什么。

面试真题

typeof返回哪些类型

基本类型:undefined string number boolean symbol
引用类型:object (null===‘object’)
函数:function

列举强制类型转换的隐式类型转换

强制:parseInt parseFloat toString
隐式:if 逻辑运算 == +拼接字符串

手写深度比较,模拟lodash isEqual

/*实现如下效果:
const obj1={a:10,b:{x:100,y:200}}
const obj2={a:10,b:{x:100,y:200}}
isEqual(obj1,obj2)===true
*/
//判断是否为对象或数组
function isObject(obj){
    return typeof obj === 'ojject' && obj !==null
}
function isEqual(obj1,obj2){
    //为值类型时(参与equal的一般不是函数)
    if (!isObject(obj1) || !isObject(obj2)){
         return obj1 === obj2
    }
    //为同一对象时
    if(obj1 === obj2){
        return true
    }
    //当两个为不同对象时(包含数组)
    //先取出obj1和obj2的keys,比较个数
    const obj1Keys = Object.keys(obj1)
    const obj2Keys = Object.keys(obj2)
    if (obj1Keys.length !== obj2Keys.length){
        return false
    }
    //以obj1为基准,和obj2递归比较
    for(let key in obj1){
        //比较当前key的val (递归)
        const res - isEqual(obj1[key],obj2[key])
        if(!res){
            return false     
        }
        //全相等
        return turn
    }
}

split()和join()的区别

‘1-2-3’.split(’-’) // [1,2,3]

[1,2,3].join(’-’) // ‘1-2-3’

数组的pop push unshift shift分别做什么

功能是什么?返回值是什么?是否会对原数组造成影响?

pop抛除数组最后一位,并返回最后一位
shift在数组前面删除一维数组,并返回抛出的那位
push往后追加一个数组,返回数组长度
unshift在数组最前面插入一个 ,返回数组长度

纯函数:不改变源数组,返回一个新数组
concat map filter slice
非纯函数:push pop shift unshift forEach some every reduce

slice和splice的区别

功能:slice-切片 splice-剪接
参数和返回值:
是否纯函数:slice是纯函数,splice是非纯函数

[10,20,30].map(parseInt)返回结果是什么?

map的参数和返回值
parseInt参数和返回值

ajax请求get和post的区别?

get一般用于查询操作,post一般用于用户提交操作
get参数拼接在url上,post放在请求体内(数据体积可更大)
安全性:post易于防止CSRF

函数call和apply的区别

fn.call(this,p1,p2,p3)
fn.apply(this,arguments)

事件代理(委托)是什么?

const p1 = document.getElementById('P1')
const body = document.body
bindEvent(p1,'click',e =>{
    e.stopPropagation()//注释掉这一行,来体会事件冒泡
    alert('激活')
})
bindEvent(body,'click',e =>{
    alert('取消')
})

闭包是什么?有什么特性?有什么负面影响?

回顾作用域和自由变量
回顾闭包应用场景:作为参数被传入,作为返回值被返回
回顾:自由变量的查找,要在函数定义的地方(而非执行的地方)
影响:变量回常驻内存,得不到释放。闭包不要乱用。(没法判断未来是否会用到)

如何阻止事件冒泡和默认行为?

event,stopPropagation()
event.preventDefault()

查找,添加,删除,移动DOM节点的方法

如何减少DOM操作?

缓存DOM查询结果
多次DOM操作,合并到一次插入

解释jsonp的原理,为何它不是真正的ajax?

浏览器的同源策略(服务端没有同源策略)和跨域
哪些html标签能绕过跨域?
jsonp的原理

document load和ready的区别

load:页面的全部资源加载完才会执行,包括图片,视频等
ready:DOM渲染完即可执行,此时图片,视频还可能没有加载完

函数声明和函数表达式的区别?

函数声明function fn(){ } 函数声明会在代码执行前预加载,而函数表达式不会

函数表达式const fn = function(){ }

new Object()和Object.create()的区别

{ }等同于new Object( ) 原型都是Object.prototype
Object.create(null)没有原型,Object.create({ … })可指定原型

关于this的场景题

关于作用域和自由变量的场景题

//问题
let i
for(i =1;i<=3;i+=){
    setTimeout(function(){
        console.log(i)
    },0)
}

判断字符串以字母开头,后面字母数字下划线,长度6-30

const reg = /1\w{5,29}$/

学习正则表达式规则
手写常见的正则表达式

关于作用域和自由变量的场景题-2

//看代码的时候先跳过函数内部,等到执行的时候再看函数内部
let a = 100
function test(){
    alert(a)
    a=10
    alert(a)
}
test()
alert(a)

手写字符串trim方法,保证浏览器兼容性

String.prototype.trim=function(){
    return this.replace(/^\s+/,'').replace(/\s+$/,'')
}
//原型,this,正则表达式

如何获取多个数字中的最大值

function max(){
    const nums = Array.prototype.slice.call(arguments)//变为数组
    let max = 0
    nums.forEach(n=>{
        if(n>max){
            max=n
        }
    })
    return max
}

如何用JS实现继承

class继承
prototype继承

如何捕获JS程序中的异常?

//手动捕获异常
try{
    //todo
}catch(ex){
    console.error(ex)//手动捕获catch
}finally{
    //todo
}

//自动捕获异常
window.onerror = function (message,source,lineNom,colNom,error){
    //第一,对跨域的js,如CDN的,不会有详细的报错信息
    //第二,对于压缩的js,还要配合sourceMap反查到未压缩代码的行,列
}

什么是JSON?

json是一种数据格式,本质是一段字符串。

{
    "name":"张三",
        "info":{
            "single":true,
            "age":30,
            "city":"北京"
        },
    "like":["篮球","音乐"]
}

json格式和js对象结构一致,对js语言更友好
window.JSON是一个全局对象: JSON.stringify JSON.parse

获取当前页面url参数

传统方式,查找location.search
新API.URLSearchParams

将url参数解析为JS对象

//传统方式,分析search
function queryToObj(){
    const res= {}
    const search = location.search.substr(1)//去掉前面的'?'
    search.split('&').forEach(paramStr =>{
        const arr = paramStr.split('=')
        const key = arr[0]
        const val = arr[1]
        res[key] = val
    })
    return res
}

//使用URLSearchParams
function queryToObj(){
    const res = {}
    const pList = new URLSearchParams(location.search)
    pList.forEach((val,key)=>{
        res[key] = val
    })
	return res
}

手写数组flatern,考虑多层级

function flat(arr){
    //验证arr中,还有没有深层数组[1,2,[3,4]]
    const isDeep = arr.some(item => item instanceof Array)
    if(!isDeep){
        return arr//已经是flatern[1,2,3,4]
    }
    
    const res = Array.prototype.concat.apply([],arr)
    return flat(res)//递归
}
const res = flat ([1,2,[3,4,[10,20,[100,200]]],5])
console.log(res)

数组去重

  1. 传统方式: 遍历元素挨个比较,去重
  2. 使用set,考虑计算效率
//传统方式: 遍历元素挨个比较,去重
function unique(arr){
    const res = []
    arr.forEach(item=>{
        if(res.indexOf(item)<0){
            res.push(item)
        }
    })
    return res
}
const res = unique([30,10,10,20,40,30])
console.log(res)

//使用set方式,,set时无序结构,不能重复
function unique(arr){
    const set = new Set(arr)
    return [...set]//...是解构
}
const res = unique([30,10,10,20,40,30])
console.log(res)

介绍一下RAF requestAnimateFrame

要想动画流畅,更新频率要达到60帧/s,即16.67ms更新一次视图
setTimeout要手动控制频率,而RAF浏览器会自动控制
后台标签或隐藏iframe中,RAF会暂停,而setTimeout依然执行

前端性能如何优化?一般从哪几个方面考虑?

原则:多使用内存,缓存,减少计算,减少网络请求
方向:加载页面,页面渲染,也买你操作流畅度


  1. a-zA-Z ↩︎

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值