大钱端之面试(中)

大钱端之面试(中)

JS基础

JS基础-变量类型和计算

typeof 能判断哪儿些类型

识别出所有值类型 : string char symbol undefind blooean
识别函数 : funtion
识别引用类型 Object(到此为止,不可再细分)

何时使用 === 何时使用 ==

==运算符 会尽量转换成相同类型使之相等

除了等于null之外,一律用三等

值类型和引用类型的区别

值类型 : string char symbol undefind blooean
let a = 100
let b = a
a = 200
console.log(b)
引用类型 :  [] {} null  特殊引用类型 |函数类型 function(){} 
let a = { age : 20 }
let b = a
b.age = 30
console.log(a.age)
分析
栈  a | 100
栈  a | 100  b | 100
栈  a | 200  b | 100
存储的是内存地址
堆  内存地址1 | { age : 20 }
堆  内存地址1 | { age : 20 }
堆  内存地址1 | { age : 21 }

​ 性能及内存原因,js机制如此

手写深拷贝

注意判断值类型与引用类型 :typeof

注意判断数组还是对象 :instanceof

function deepClone(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
}

变量类型相关的面试题

  • 字符串拼接

  • == 判断

  • truely falsely 变量

    const n = 100  两部运算后等于true 则为truely变量,反之亦然
    !n
    !!n   <--  trurly变量
    
    if(truely)
    

JS基础-原型与原型链 <基于原型来集成>

知识点:

object可以认为是所有class的父类

class实际上是一个函数

>_ typeof class student{}
>_ function
student.__proto__
student.prototype
student.prototype.__proto__ === people.prototype

suduent 的属性属于自身
		方法指向到父类的__proto__

每个class都有显示原型 prototype

实例中的 _ proto _ 对应class的prototype

hasownproperty 验证是否为自身属性

hasownproperty 是object._ proto _的隐式原型中提供的方法

object._ proto _ 永远指向null

至此原型链结束

基于原型的执行规则
  • 先在自身的属性和方法寻找
  • 找不到自动去上层的 隐式原型 _ proto _中寻找

instanceof 判断引用类型

instanceof 的原理

通过隐式原型在原型链上寻找,找到这个属性方法,返回true 找不到返回false

提示

class是es6语法规范,由ECMA委员会发布

ECMA委员会发布只规定书写规范,不关心如何实现

以上原型都是通过v8引擎的实现发布,也是主流引擎

原型面试题

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

a instanceof array

class的原型本质,怎么理解

原型与原型链的图示

属性和方法的执行规则

手写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
        this.selector = selector
    }
    get(index) {
        return this[index]
    }
    each(fn) {
        for (let i = 0; i < this.length; i++) {
            const elem = this[i]
            fn(elem)
        }
    }
    on(type, fn) {
        return this.each(elem => {
            elem.addEventListener(type, fn, false)
        })
    }
    // 扩展很多 DOM API
}

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

// “造轮子”
class myJQuery extends jQuery {
    constructor(selector) {
        super(selector)
    }
    // 扩展自己的方法
    addClass(className) {

    }
    style(data) {
        
    }
}

// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))

JS基础-作用域及闭包

知识点

作用域和自由变量

作用域:

一个变量的合法使用范围

  • 全局作用域
  • 函数作用域
  • 块级作用域 {}
自由变量 :

一个变量在当前函数体被使用,但没有找到,则一层一层递增查找

所有自由变量的查到,实在函数定义的时候想上寻找,而不是在函数执行的地方向上寻找

  • 如果没有找到,则报 xx is not defind

闭包 closure

作用域应用的特殊情况,有两种情况

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

    !!! 所有自由变量的查到,实在函数定义的时候想上寻找,而不是在函数执行的地方向上寻找

this

this 有几种赋值情况
  • 作为普通函数 < 指向 window >
  • 使用 call bind apply < 指向执行体,传入什么绑定什么 >
  • 作为对象方法被调用 < 返回当前对象 >
  • 在class方法中被调用 < 返回class实例本身 >
  • 箭头函数 < 找上级作用域的this来确定 >

this取什么值,是在函数执行的时候定义的,而不是创建的时候 , 适用于以上场景

面试题:

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

​ 上面已回答

2. 手写bind函数 [改变指向的方法之一]

**bind()** 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

Function.prototype.bind1 = function (){
    把传入的参数变为数组
    .call 是把arguments赋值成为当前参数的this   复习 【指向执行体,传入什么绑定什么】
    const args = Array.prototype.slice.call(arguments)

    剔除args[0]并赋值给变量t  <也就是传入的this>
    const t = args.shift()

    // self 就是调用bind方法的函数本身
    const self = this

    // 返回一个函数
    return function(){
        return self.apply(t,args)
    }
}

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

  • 隐藏数据

  • 做一个简单的cache工具

    function createCache(){
    	const data = {}
    	return {
    		set:function(key ,value){
    			data[key,value]	
    		},
    		get:function(key){
    			return data[key]
    		}
    	}
    }
    

4. 创建10个A标签,点击弹出对应的序号

let a
for(let i = 0;i < 10 ; i++){
	a = document.createElemeat('a')
	a.innerHtml = i+'<br>'
	a.addEventListener('click',function(e){
		e.prevenDeafualt()
		e.alert(i)
	})
	document.body.appendchild(a)
}

JS基础-异步

JS三座大山 原型和原型链 作用域和闭包 以及异步和单线程

Promise 解决了 callback hell的问题

JS是一门单线程语言,只能同时干一件事

浏览器和nodejs已经支持js同时启动进程,例如web worker

js和DOM渲染同时使用同一个线程,因为JS可以修改DOM结构 <DOM渲染过程中JS必须停止,JS执行过程中也必须停止 >

遇到等待,(定时任务,网络请求),页面不能卡住

需要异步

异步是基于callback函数形式来调用的

异步 :

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

异步的应用场景:

  • 网络请求 : 如 ajax图片加载
  • 定时任务 : setTimeout setInterval
  • 解决回调地狱 promise把回调变成了管道类的形式,而不是嵌套,越陷越深.

面试题

同步和异步的区别是什么

阻塞线程的区别

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

手写promise加载一张图片

const url1 = 'xxx.jpg'

const getImage (src){
	return new Promise((resolve,reject)=>{
		const img = document.careteElment('img')
		img.onload = () => {
			resolve(img)
		}
		img.onerror = () =>{
			const err = new Error(`err info :${src}`)
			rject(err)
		}
		img.src = src
	})
}

前端异步的使用场景有哪儿些

  • 网络请求 : 如 ajax图片加载
  • 定时任务 : setTimeout setInterval

JS-异步进阶

JS异步的原理及进阶

  • event-loop

    请描述 event-loop 的机制,可画图 别名:《事件循环/事件轮询》

  • Promise进阶

    Promise有哪儿三种状态,如何变化

    Promise then与catch的连接

    Promise 和 setTimeout 的顺序

  • aysnc await

    async await 语法

  • 微任务 / 宏任务

    什么是微任务,宏任务,两者有啥子区别

什么是event-loop (事件循环/事件轮询)

  • JS 是单线程
  • 异步通过回调来实现

event loop就是异步回调的实现原理

JS是如何执行

  • 从前到后,一行一行执行 , 如果中间有一行报错,则停止执行
  • 先把同步代码执行完,再执行异步
console.log('hi')

setTimeout(function cb1(){
	console.log('cb1')
},5000)

console.log('bye')

以上JS代码执行过程

call stack (调用栈)--> web apis (处理api)--> callback queue (回调函数队列)--> Event loop (事件循环)

hi 的执行顺序:

1. call stack 放入 console.log('hi') 
2. browser console output: hi 
3. call stack (empty)

setTimeout 的执行

1. call stack 放入 setTimeout cb1
2. web apis  放入定时器 timer cb1   > 5秒钟的定时器
3. 5秒钟的定时器结束后 > cb1 放入 callback queue

bye 的执行顺序:

1. call stack 放入 console.log('bye') 
2. browser console output: bye 
3. call stack (empty)

所有同步代码执行完成后(调用栈为空),启动event loop机制

1.启动后在 callback queue轮询查找
2.在callback queue里找到cb1 推到 ,call stack里执行函数体cb1
3.执行cb1,打印 cb1  ,完成后从调用栈 清空
4.继续轮询查找 (~永动机一样~)
5.JS语句执行完毕

DOM事件和event-loop的关系

DOM事件也是基于回调来实现的
只要是基于回调,就是基于eventloop来实现的

btn的click触发
当执行到绑定click JS语句是,把click方法推到webapi存储,当用户点击时触发,webapi推送至callback queue ,eventloop轮询

Promise有哪儿三种状态

三种状态:
  • pending 过程中
  • resolved 成功 触发 then
  • rejected 失败 触发 catch

状态变化是不可逆的

状态的表现和变化

then 和 catch 改变状态

  • then 正常返回一个resolved 的Promise, 如果有报错, 则返回一个 rejected 的Promise
  • catch正常返回一个resolved 的Promise, 如果有报错, 则返回一个 rejected 的Promise

async await 语法

  • 异步回调 callback hell
  • Promise then catch 链式调用,但也是基于回调函数

async await 是同步语法去编写异步代码,彻底的消灭了回调函数

async 相当于封装了Promise 又返回一个Promise

await 相当于 Promise的then

async await和Promise的关系

  • Async Await是消灭异步回调的终极武器
  • 但是和Promise并不互斥
  • 两者是相辅相成的

执行Async函数,返回的是一个Promise , 无论返回什么,都会封装成一个Promise对象

await相当于Promise的then()

try...catch 替代了Promise的catch

异步的本质

  • Async Await是消灭异步回调的终极武器
  • JS还是单线程,该异步还要异步,还是基于event-loop来实现的
  • Async Await 只是个语法糖,但是这个糖好吃
  • Async Await 只是语法层面像同步的写法,但本质脱离不了异步
async function async1 () {
 console.log('async1 start')
 await async2() // await 后面所有的语法 都可以当成callback执行
 console.log('async1 end') *// 关键在这一步,它相当于放在 callback 中,最后执行*
}
async function async2 () {
 console.log('async2')
}

console.log('script start')
async1()
console.log('script end')

你猜猜打印的顺序~~~~ 异步的本质!

for ...of 的应用场景

  • for ... in forEach 会同时执行遍历,因为属于同步语法

  • for ... of 常用在异步的遍历

    第一次遍历有结果后才会执行第二次遍历

宏任务 macro Task,微任务micro Task(event-loop事件 异步)

什么是宏任务 macro Task,微任务micro Task

  • 宏任务 > setTimeout setInterval ajax DOM事件

  • 微任务 > Promise async await

    微任务的执行时机比宏任务要早

event-loop 和 dom渲染

为什么微任务执行时机比宏任务要早

DOM渲染时机:

  1. 第一次同步代码全部执行完,执行栈(call stack)空闲, 会尝试DOM渲染 , 然后才会执行 event-loop机制

    1. 每次轮询结束一次之后,DOM结构如果有变化的话, 会再次尝试DOM渲染
      3. 再次触发下一次的 event - loop

微任务 宏任务的区别

宏任务 : DOM渲染后触发 setTimeout

微任务 :DOM渲染前触发 Promise

为什么微任务执行时机要比宏任务早

Promise 是 ES6规范,不是W3C规范,它不会进入webapi 及 callback queue

而是等待时机推入 mircro queue [微任务队列]

所以 event-loop的执行步骤不一样

  1. call stack 清空
  2. 执行微任务
  3. 尝试DOM渲染
  4. event-loop
  5. 执行宏任务

微任务是ES6语法规定的

宏任务是由浏览器规定的

总结:

  • event loop
  • Promise 进阶
  • async await
  • 微任务 宏任务

JS-web-API

  • JS 基础知识 规定语法 ECMA 262标准
    • 变量类型计算
    • 原型和原型链
    • 作用域和闭包
  • JS Web API 网页操作的API W3C标准
    • DOM 网页节点
    • BOM 浏览器的事情(导航,url ,跳转)
    • 事件绑定
    • ajax
    • 存储
  • 前者是后者的基础,两者结合才能挣钱

DOM的本质是什么

Document Object Model

vue 和 react 框架应用广泛,已经封装了Dom操作

DOM操作是必备知识,不可不会,框架只是个框架

vue 与 react 的底层也是在操作DOM节点

DOM 的本质 : 从html语言解析出来的一棵树~

所以叫 DOM 树

DOM 节点操作

获取DOM节点

getElementById
getElementByTagName
getElementByClassName
querySelectorAll

attribute

直接修改标签的属性,会改变html结构
getAttribute('')
setAttribute('style','font-size:50px;')

property

纯操DOM 修改js变量,不会体现到html结构中
let p = document.getElementById('p')
p.style.width = '100px'
p.style.color = '100px'
p.calssName = ''
p.nodeName
p.nodeType
  • 两者都能引起DOM的重新渲染

DOM 结构操作

新增节点

let div1 = document.getElementById('div1')
let p1 = document.createElement('p')
p1.innerHtml = '<p>this is p1</p>'
div1.appendChild(p1)

移动节点

对现有div 插入已存在节点,会移动此节点

获取子元素列表

let div1 = document.getElementById('div1')
div1.childNodes

过滤获取的子元素

let div1 = document.getElementById('div1')

const divChildP = Array.prototype.slice.call(div1.childNodes).filter((child)=>{
	if(child.NodeType === 1){
		return true
	}
	return false
})

console.log(divChildP)

删除子元素

div1.removeChild( divChildP[0] )

获取父元素

p1.parentNode

DOM 性能

如何优化DOM性能

  • DOM的操作非常昂贵,要避免频繁的操作DOM

  • 对DOM查询做缓存

    • 缓存length

      let dd = document.getElementByTagName('div')
      let length = dd.length
      for(let i = 0 ; i< length ; i++){
          对length进行缓存
      }
      
  • 将多次操作改为一次操作

    创建一个文档片段
    let frg = document.createDocumentFragment()
    将需要插入的内容 appendchild 到fragment中
    fragment.appendchild(items)
    div1.appendchild(fragment)
    

面试题:

DOM是什么数据结构?

DOM操作的常用API?

attribute 和 property 的区别?

一次性插入多个DOM,考虑性能?

JS-WEB-API-BOM

Browser Object Model

  • 如何识别浏览器的类型
  • 分解拆解url的各个部分

知识点

  • navigator.userAgent ( ua ) 拿到浏览器的信息
  • screen 屏幕信息
  • location 地址信息 url的信息
    • location.herf 整个url
    • location.protocol 请求协议 http/https
    • location.host 域名
    • location.search 常用信息
    • location.hash #号后面的内容
    • location.pathname 浏览器路径(/后的内容)
  • history 前进后退信息
    • history.back 后退
    • history.forward 前进

JS-WEB-API-事件

知识点:

事件绑定
const div = document.getElementById('xx')
div.addEventListener('click',(event)=>{
	console.log('click')
})
事件冒泡
const div = document.getElementById('xx')
div.addEventListener('click',(event)=>{
	event.stopPropagation()  阻止冒泡
	console.log('冒泡')
})

const body = document.body
body.addEventListener('click',(event)=>{
	console.log('body冒泡')
})

event.prevenDefault() //阻止默认行为

event.stopPropagation() 阻止冒泡

事件代理

什么是事件代理

把事件绑定到不好绑定或者没法绑定元素的父元素上
只在父元素上绑定一个事件,处理子元素的事件
面试题
  • 编写一个通用的事件监听函数

    function bindEvent (elem , type , fn ){
    	elem.addEventListener(type, fn)
    }
    
    let dd = document.getElementById('xx')
    bindEvent(dd,'click',event => {
    	event.target 		  //获取当前被绑定对象
    	event.prevenDefault() //阻止默认行为
    	event.target.matches('a') 		  //判断是否包含a标签
    	alert('clicked')
    })
    

    进化版事件监听,增加过滤条件

    // 通用的事件绑定函数
    // function bindEvent(elem, type, fn) {
    //     elem.addEventListener(type, fn)
    // }
    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)
            }
        })
    }
    
  • 描述事件冒泡的流程

    事件会随着触发元素向上冒泡   最高body,   所以才可以实现  事件代理
    
  • 无限下拉图片列表(瀑布式),如何监听每个图片的点击 ?

    1. 事件代理
    2. target 获得触发元素
    3. matches 来判断是否为触发元素
    

JS-WEB-API-Ajax

知识点:XMLHttpRequest

GET

const xhr = new XMLHttpRequest()
//	 是否异步处理  false 否 
xhr.open('GET',"/api",true)
xhr.onreadystatechange = function(){
	if(xhr.readyState === 4){
		if(xhr.stats === 200){
			JSON.parse(xhr.responseText) //转换为json格式
			alert(xhr.responseText)
		}
	}
}
xhr.send(null)

POST

const user = {
	username : 'xxx'
	password : '123'
}
const xhr = new XMLHttpRequest()
//	false 为异步处理
xhr.open('POST',"/api",false)
xhr.onreadystatechange = function(){
	if(xhr.readyState === 4){
		if(xhr.status === 200){
			JSON.parse(xhr.responseText) //转换为json格式
			alert(xhr.responseText)
		}
	}
}
xhr.send(JSON.stringfly(user))

知识点:状态码

  • readyState
    • 0 init
    • 1 readysend
    • 2 sending
    • 3 decode
    • 4 ok
  • status
    • 2xx sccuess
    • 3xx redirect
      • 301 always redirect
      • 302 temp redirect
      • 304 use cache to reload
    • 4xx error
      • 404 errurl or has no auth to visit
      • 405
    • 5xx Server error

知识点:跨域:同源策略,跨域解决方案

什么是同源策略

!浏览器要求当前网页和Server端必须同源 

同源 : 域名,协议,端口必须一致

图片 CSS JS 无视跨域

  • 可以实现统计打点

  • 可以使用CDN,CDN一般为外域
原文地址:https://www.cnblogs.com/maozhe/p/13116400.html