基础js面试总结

665 阅读8分钟

基础js面试总结

本人前端新手,小白一枚,最近在看慕课的视频,花了大概两三天时间吧,总结了下一些基础js的面试的内容,第一次写文章有不足的地方,还望大家多多批评指正

一.变量类型和计算

1.变量类型

值类型:直接存储在内存,包含:number,undefined,String,boolean,symbol(es6新类型).
引用类型:堆栈模型,会在堆里申请一个内存,变量从内存中取.包含:

  1. Object-对象
  2. Array-数组
  3. null-指针指向空地址
  4. 函数--特殊引用类型,不用于数据存储,没有拷贝复制的说法

补充:堆栈模型

//引用类型
let a={age:20}
let b=a
b.age=21
console.log(a.age) //21

image.png

2.typeof运算符

识别所有值类型,识别所有函数,可以判断是否为引用类型(不可再进行细分)

3.深拷贝

手写一个深拷贝

/**
 * 深拷贝
 */

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)) {
            // 递归调用!!!
            result[key] = deepClone(obj[key])
        }
    }

    // 返回结果
    return result
}

4.计算

1.类型转换

字符串拼接+号前后会拼接成字符串

2.==与===

==会进行强制的类型转换,把两边的类型转换一致进行比较,===不会,两边的类型必须相等才成立,在开发过程中,除了==null,其他的一律使用===

3.if语句和逻辑运算

在做if逻辑运算时,判断的是两步非运算后的结果:truly变量和falsely变量
特殊的falsely变量:
0===false
NaN===false
" "===false
null===false
undefined===false
false===false

二.原型和原型链

1.class和继承

1.class-->面向对象

constructor构建:属性,方法

// 类
class Student {
    constructor(name, number) {
        this.name = name
        this.number = number
        // this.gender = 'male'
    }
    sayHi() {
        console.log(
            `姓名 ${this.name} ,学号 ${this.number}`
        )
    }
}

// 通过类 new 对象/实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()

const madongmei = new Student('马冬梅', 101)
console.log(madongmei.name)
console.log(madongmei.number)
madongmei.sayHi()

extends继承类
super->传递给父类去执行

// 父类
class People {
    constructor(name) {
        this.name = name
    }
    eat() {
        console.log(`${this.name} eat something`)
    }
}

// 子类
class Student extends People {
    constructor(name, number) {
        super(name)
        this.number = number
    }
    sayHi() {
        console.log(`姓名 ${this.name} 学号 ${this.number}`)
    }
}

// 子类
class Teacher extends People {
    constructor(name, major) {
        super(name)
        this.major = major
    }
    teach() {
        console.log(`${this.name} 教授 ${this.major}`)
    }
}

// 实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()
xialuo.eat()

// 实例
const wanglaoshi = new Teacher('王老师', '语文')
console.log(wanglaoshi.name)
console.log(wanglaoshi.major)
wanglaoshi.teach()
wanglaoshi.eat()

2.类型判断instanceof

判断变量属于哪个class或者构造函数

//借用上文的class
xialuo instanceof Student //true
xialuo instanceof People //true
xialuo instanceof Object //true
[] instanceof Array //true
[] instanceof Object //true
{} instanceof Object //true

3.原型和原型链

1.原型关系

  1. 每个class都有显示原型prototype
  2. 每个实例都有隐式原型_proto_
  3. 实例的_proto指向对应class的prototype

2.基于原型的执行规则

  1. 获取属性xialuo.name或执行xialuo.sayhi()时
  2. 先在自身属性和方法寻找
  3. 找不到自动去隐式原型_proto_中查找

3.hasOwnProperty()

验证是不是自己的属性

4.原型链

image.png

5.重要提示

  1. class是ES6语法规范,由ECMA委员会发布
  2. ECMA只规定语法规则,即我们代码的书写规范,不规定如何实现
  3. 以上实现方式都是V8引擎的实现方式,也是主流

三.作用域和闭包

1.作用域和自由变量

作用域:变量的合法使用范围

  1. 全局作用域
  2. 函数作用域
  3. 块级作用域:let和const

自由变量:变量在当前作用域没有定义但被使用了,向上级作用域,一层一层一次寻找,找到位置,如果全局作用域都没找到,会报not defined;

2.闭包

作用域应用打的特殊情况,有两种表现:

  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

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

3.this

1.应用场景

作为普通函数被调用-->window
使用call apply bind-->传入什么绑定shenme
作为对象方法被调用-->对象本身
在class方法中调用-->实例本身
箭头函数-->找上级作用域this的值

2.取值

this在函数执行的时候确认的,不是在函数定义的时候确认的

3.手写bind函数

// 模拟 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)
    }
}

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

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

4.实际开发中闭包的应用

  1. 隐藏数据
  2. 做一个简单的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') )

四.异步和单线程

1.单线程和异步

1.js语言单线程:

js是单线程语言,同时只能做一件事儿
浏览器和nodejs已支持js启动进程,如Web Worker
JS和DOM渲染共用一个线程,因为JS可修改DOM结构
遇到等待(网络请求,定时任务)不能卡住
回调基于callback形式

2.为什么使用异步

基于js是单线程语言,同步会阻塞代码执行,异步不会阻塞代码执行

3.应用场景

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

4.定时器

setTimeou和setInterval

2.promise

1.回调地狱

2.promise

resolve和reject两个参数
手写promise加载图片

function loadImg(src) {
    const p = 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
        }
    )
    return p
}
const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'

loadImg(url1).then(img1 => {
    console.log(img1.width)
    return img1 // 普通对象
}).then(img1 => {
    console.log(img1.height)
    return loadImg(url2) // promise 实例
}).then(img2 => {
    console.log(img2.width)
    return img2
}).then(img2 => {
    console.log(img2.height)
}).catch(ex => console.error(ex))

五.JS Web API

1.DOM

1.前言

vue和react框架应用广泛,封装了DOM操作
但DOM操作一直都会是前端工程师的基础,必备知识
只会vue而不懂DOM操作的前端程序员,不会长久

2.DOM的本质

DOM树:浏览器已经加载好的HTML,一棵一层一层向下的树

3.DOM节点操作

  1. 获取DOM节点
const div1 = document.getElementById('div1')//通过id获取

const divList = document.getElementsByTagName('div') // 集合,通过标签名

const containerList = document.getElementsByClassName('container') // 集合,通过类名

const pList1 = document.querySelectorAll('p')

const pList2 = document.querySelectorAll('p')
  1. attribute

setAttribute和getAttribute两个方法,通过这两个方法修改dom结构

// attribute
p1.setAttribute('data-name', 'imooc')
console.log( p1.getAttribute('data-name') )
p1.setAttribute('style', 'font-size: 50px;')
console.log( p1.getAttribute('style') )
  1. Property

以js对象属性的形式来操作,不会体现到html结构中

// property 形式
p1.style.width = '100px'
console.log( p1.style.width )
p1.className = 'red'
console.log( p1.className )
console.log(p1.nodeName)
console.log(p1.nodeType) // 1

4.DOM结构操作

// 新建节点
const newP = document.createElement('p')
newP.innerHTML = 'this is newP'
// 插入节点
div1.appendChild(newP)

// 移动节点
const p1 = document.getElementById('p1')
div2.appendChild(p1)

// 获取父元素
console.log( p1.parentNode )

// 获取子元素列表
const div1ChildNodes = div1.childNodes
console.log( div1.childNodes )
const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => {
    if (child.nodeType === 1) {
        return true
    }
    return false
})
console.log('div1ChildNodesP', div1ChildNodesP)
// 删除节点
div1.removeChild( div1ChildNodesP[0] )

5.DOM性能

DOM操作非常珍贵,避免频繁的DOM操作
对DOM查询做缓存:

//不缓存DOM查询结果
for (let i = 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查询
}

将频繁的操作改为一次性操作

const list = document.getElementById('list')

// 创建一个文档片段,此时还没有插入到 DOM 结构中
const frag = document.createDocumentFragment()

for (let i = 0; i < 20; i++) {
    const li = document.createElement('li')
    li.innerHTML = `List item ${i}`

    // 先插入文档片段中
    frag.appendChild(li)
}

// 都完成之后,再统一插入到 DOM 结构中
list.appendChild(frag)

console.log(list)

2.BOM

  1. nabigator 浏览器信息
  2. screen 屏幕信息
  3. location 地址信息
  4. history 历史记录
//nabigator
const ua = navigator.userAgent
const isChrome = ua.indexOf('Chrome')
console.log(isChrome)

//screen
console.log(screen.width)
console.log(screen.height)

//location
console.log(location.href);
console.log(location.protocol);//"http:" "https:"
console.log(location.pathname);
console.log(location.search);
console.log(location.hash);

//history
history.back()
history.forward()

3.事件绑定

1.事件绑定

通用事件绑定函数

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>事件 演示</title>
        <style>
            div {
                border: 1px solid #ccc;
                margin: 10px 0;
                padding: 0 10px;
            }
        </style>
    </head>
    <body>
        <button id="btn1">一个按钮</button>
      
        <!-- <div id="div1">
            <p id="p1">激活</p>
            <p id="p2">取消</p>
            <p id="p3">取消</p>
            <p id="p4">取消</p>
        </div>
        <div id="div2">
            <p id="p5">取消</p>
            <p id="p6">取消</p>
        </div> -->

        <div id="div3">
            <a href="#">a1</a><br>
            <a href="#">a2</a><br>
            <a href="#">a3</a><br>
            <a href="#">a4</a><br>
            <button>加载更多...</button>
        </div>

        <script src="./event.js"></script>
    </body>
</html>
// 通用的事件绑定函数

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 btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', function (event) {
    // console.log(event.target) // 获取触发的元素
    event.preventDefault() // 阻止默认行为
    alert(this.innerHTML)
})

// 代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3, 'click', 'a', function (event) {
    event.preventDefault()
    alert(this.innerHTML)
})

2.事件冒泡

基于DOM树形结构
事件会顺着触发元素往上冒泡

3.事件代理

基于事件冒泡
优点:代码简洁,减少浏览器内存使用,不要滥用

4.ajax

//get请求
const xhr = new XMLHttpRequest()
xhr.open('GET', '/data/test.json', true)
xhr.onreadystatechange = function () {
  //这里的函数异步执行,可参考之前JS基础中的异步模块
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            // console.log(
            //     JSON.parse(xhr.responseText)
            // )
            alert(xhr.responseText)
        } else if (xhr.status === 404) {
            console.log('404 not found')
        }
    }
}
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必须同源
同源:协议,域名,端口,三者必须一致

跨域

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

解决跨域

1.JSONP

script可绕过跨域限制
服务器可以任意动态拼接数据返回
可以利用script就可以获得跨域的数据,只要服务端愿意返回
2.CROS--服务器设置http header

image.png

5.存储

1.cookie

本身用于浏览器和server通讯
被"借用"到本地存储来
可用document.cookie="..."来修改

cookie的缺点
  1. 存储大小,最大4KB
  2. http请求时需要发送到服务器,增加请求数据量
  3. 只能用document.cookie="..."来修改,太过简陋

2.localStorage和sessionStorage

共同点

  1. HTML5专门为存储设计的,最大可存5M;
  2. API简单易用
  3. 不会随着http请求被发送出去

区别:

  1. localStorage数据会永久存储,除非代码或手动删除
  2. sessionStorage数据只存在于当前会话,浏览器关闭则清空
  3. 一般用localStorage会更多一些

六.开发环境问题

1.关于开发环境

  1. 面试官想通过开发环境了解候选人的实际工作情况
  2. 开发环境的工具,能体现工作效率的产出
  3. 会以聊天形式为主,不会问具体的问题

2.git

  1. 最常用的代码版本管理工具
  2. 大型项目需要多人协作开发,必须熟用git
  3. 常用命令:
git add . (添加)
git checkout XXX (恢复文件状态)
git commit -m 'XXX' (提交一行记录)
git push origin master (提交)
git pull origin master (拉取)
git branch (查看分支)
git checkout -b xxx (切换分支)
git merge xxx (合并)

3.调试工具

一般是浏览器的内部调试工具

4.抓包

  1. 移动端页,查看网络请求,需要用工具抓包
  2. windows一般用fiddler
  3. Mac OS一般用charles

抓包过程

  1. 手机和电脑连同一个局域网
  2. 将手机代理到电脑上
  3. 手机浏览网页,即可抓包

5.性能优化

1.性能优化原则

  1. 多实用内存,缓存,活其他方法
  2. 减少CPU计算量,减少网络加载耗时
  3. 空间换时间

2.从何入手

  1. 让加载更快
  2. 让渲染更快

3.让加载更快

  1. 减少资源体积:压缩代码
  2. 减少访问次数:合并代码,SSR服务器端渲染,缓存
  3. 使用更快网络:CDN

4.让渲染更快

  1. CSS放head,JS放在body最下面
  2. 尽早开始执行JS,用DOMContentLoaded触发
  3. 懒加载
  4. 对DOM查询进行缓存
  5. 频繁DOM操作,合并到一起插入DOM结构
  6. 节流throttle 防抖debounce