前端知识
什么是js
- js是一个客户端的脚本语言,无需编译可以直接被浏览器解析执行
js的组成
- ECMAScrip BOM DOM
js数据类型
- 基本数据类型
- string boolean undefined null number symbol bigint
- 引用数据类型
- object
- RegExp
- Date
- Array
- Function
- Error
- object
js内置对象、本地对象、宿主对象是哪些,有什么去呗
- 内置对象:array function object number string boolean
- 本地对象: JOSN Date promise Math
- 宿主对象
- 浏览器: window document
- node: fs http
存储方式
- 基本数据类型
- 存储在栈内存中
- 引用数据类型
- 存储在栈内存中是堆内存的地址 实际的值存储在堆内存中
- 堆栈的区别
- 栈
- 寻址速度要快于堆
- 有结构按照一定次序存放
- 当变量消失时系统会自动回收内存
- 特点后进先出
- 生命周期确定 比较固定
- 堆
- 由开发人员自主释放 若不主动释放,程序结束时由浏览器回收,用于存储引用类型数据
- 无序的键值对存储
- 具有优先级 优先级高的队列先执行
- 生命周期不确定 比较灵活
- 栈
数据类型检测
- typeof
- 用与基本数据类型的检测 除null 引用类型检测均返回object
console.log(typeof 12) // number
console.log(typeof true) // boolean
console.log(typeof undefined) //undefined
console.log(typeof 'abc') // string
console.log(typeof null) // object
console.log(typeof [1,2,3]) // object
console.log(typeof {name: 'abc'}) // object
- instanceof
- 用与引用数据类型的检测不能检测基本数据类型
console.log( 12 instanceof Number) // false
console.log( true instanceof Boolean) // false
console.log( 'abc' instanceof String) // false
console.log( [1,2,3] instanceof Array) // true
console.log( {name: 'abc'} instanceof Object) // true
- 手写instanceof
function my_instanceof (data, target) {
if(typeof data !== 'object' || data === null) {
return false
}
let proto = Object.getPrototypeOf(data)
while (proto) {
if(proto === target.prototype){
return true
}
proto = Object.getPrototypeOf(proto)
}
return false
}
- Object.prototype.toString.call()
console.log(Object.prototype.toString.call(12)) // [object Number]
console.log(Object.prototype.toString.call(true)) // [object Boolean]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
console.log(Object.prototype.toString.call('abc')) // [object String]
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call([1,2,3])) // [object Array]
console.log(Object.prototype.toString.call({name: 'abc'})) // [object Object]
类型转换
- 强制类型转换
- Number() parseInt() toString() String() Boolean() 等
- 隐式类型转换
- 逻辑运算符 && || ! 条件运算符 if/while 关系运算符 > >= <= < 相等运算符== 运算符 + - * / %
- null 和 undefined有什么区别
- null转成数字是0 undefined转数字是NaN
- 在条件运算符中都表示false
- 数据类型不同 typeof null是object | typeof undefined 是 undefined
- null表示空 undefined代表被声明了 但是未赋值
深浅拷贝
- 浅拷贝
- 如果声明一个变量 并且这个变量等于另一个变量 其中如果另一个变量是基本数据类型那么赋值的是变量的值 如果是引用数据类型那么赋值的是这个变量在栈内存中的地址
- 问题 如果是引用数据类型 改变了声明的变量 另一个变量也会改变
- 如何实现浅拷贝 扩展运算符 … concat object.assign()
- 如果声明一个变量 并且这个变量等于另一个变量 其中如果另一个变量是基本数据类型那么赋值的是变量的值 如果是引用数据类型那么赋值的是这个变量在栈内存中的地址
- 深拷贝
- 如果是基本数据类型和浅拷贝一致 如果是引用数据类型 那么将在堆内存中重新开辟一个存储空间将需要拷贝的变量在堆内存中的值重新复制一遍到新的存储空间 并把新的地址在栈内存中存储
- 如何实现深拷贝
- JSON.parse(JSON.stringify())
- 通过循环递归的形式
- 如何实现深拷贝
- 如果是基本数据类型和浅拷贝一致 如果是引用数据类型 那么将在堆内存中重新开辟一个存储空间将需要拷贝的变量在堆内存中的值重新复制一遍到新的存储空间 并把新的地址在栈内存中存储
function deepclone(target) {
// 如果源数据不是基本数据类型则循环递归处理
if(typeof target ==='object'&& target !== null) {
let cloneTarget = Array.isArray(target) ? [] : {}
// 遍历对象 或者 数组的属性
for(prop in target){
if (typeof target[prop]==='object'&& target[prop]!== null){
// 如果还是对象继续递归拷贝
cloneTarget[prop] = deepclone(target[prop])
} else{
// 否则赋值返回
cloneTarget[prop]= target[prop]
return cloneTarget
}
}
}
return target
}
数组类型 相关
数组的方法
改变自身
pop末尾删除 shift头部删除 push末尾添加 unshift头部添加
splice 数组添加 删除 reverse 数组反转 sort数组排序
不改变自身
find 数组查找 some某个条件满足返回true every每个条件满足返回true
slice数组截取 join 转字符 concat 数组合并
reduce是什么 做什么用
reduce数组叠加 上一个值作为下一次循环的参数
类数组转数组的方法
array.form
[...]
数组去重
[...new Set(arr)]
遍历去重
function qc (arr) {
let setArr = []
arr.forEach((item)=>{
if(!setArr.includes(item)){
setArr.push(item)
}
})
return setArr
}
数组排序
sort 根据unicode排序
选择排序 -- 选择数组中的最小值或者最大值(通过双向遍历每个数据和数组中的每一项相比较)依次放入数组的开头
function selectionSort (arr) {
let len = arr.length
for(let i =0;i<len-1;i++){
let minIndex = i
for(let j = i+1; j<len;j++) {
if(arr[j] < arr[minIndex]) {
minIndex = j
}
}
if (minIndex !== i) {
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
}
}
return arr
}
冒泡排序 -- 通过重复遍历数组,比较相邻2个元素并交换
function bubbleSort (arr) {
for(let i=0;i<arr.length-1;i++) {
for(let ii=0; ii<(arr.length - 1)-i;ii++){
if(arr[ii] > arr[ii+1]) {
[arr[ii],arr[ii+1]] = [arr[ii+1],arr[ii]]
}
}
}
return arr
}
快速排序 -- 找到一个基准值,比基准值大的放右边 小的放左边,最终合并
function quickSort (arr) {
let len = arr.length
if(len <= 1) {
return arr
}
let middleIndex = Math.floor(len/2)
let left = []
let right = []
for(let i =0; i < len ; i++) {
if(arr[i] < arr[middleIndex]) {
left.push(arr[i])
} else if(arr[i] > arr[middleIndex]) {
right.push(arr[i])
}
}
return quickSort(left).concat([arr[middleIndex]], quickSort(right))
}
插入排序 -- 从第二个数开始与前面每个数据相比如果比当前数大往后排,依次类推
function insertionSort (arr) {
let len = arr.length
for(let i = 1; i < len; i++) {
let temp = arr[i]
let j = i
while( j > 0 ) {
if (arr[j - 1] > temp) {
arr[j] = arr[j-1]
}else{
break
}
j--
}
arr[j] = temp
}
return arr
}
遍历比较
-
for
可以通过关键字break跳出循环, 通过continue可以跳出本次循环执行下一次循环
通过for(;;)可以执行死循环 -
for in
可以循环数组 也可以循环对象,可以正确响应break continue
循环数组时变量是索引 循环对象时变量是key -
for of
只可以循环数组, 可以正确响应break continue
循环时变量是数组的每一项值 -
forEach
数组的遍历方法
第一项是每一项的值,第二项是数组的索引,第三项是数组本身
可以通过return 实现continue 关键字 本身不支持continue break
如果想要跳出循环体
1.可以通过return + 修改数组本身长度 让循环不继续进行
2.可以通过报错+try catch 跳出循环体 -
while
通过break可以跳出循环 continue可以结束本次循环 执行下一个循环 -
do while
相比于while肯定会执行一次
作用域
- 什么是作用域:一个变量能访问的区域 称为这个变量的作用域。ES5有全局作用域 和函数作用域 ES6新增块级作用域
- 什么是作用域链: 当访问一个变量如果在当前作用域没有找到会去父级作用域找直到找到该变量或者找到全局作用域,这样的查找方式称为作用域链
闭包
* 什么是闭包: 可以访问其他函数内部变量的函数
* 闭包的特点: 函数嵌套函数
* 闭包的作用: 避免被垃圾回收机制回收,防止变量被外部污染
* 如果让闭包产生的变量被垃圾回收机制回收,给产生闭包的这个函数设置成null
* 闭包的应用:
- 封装私有变量,防止命名污染
- 函数的柯里化
+ 什么是函数的柯里化 将一个函数多个参数的调用方式,转换成一个参数多次调用的形式
- dom操作中需要拿到每个事件源时可以使用闭包解决每次拿到的dom节点都不一样
- 防抖节流
+ 什么是防抖
防抖:一段时间内多次触发事件只执行最后一次
+ 什么是节流
节流: 每隔一段时间只执行一次
// 手写防抖节流
// 防抖
function debounce (fn, time) {
// 边缘检测 如果 fn不是函数 报错提示
if (Object.prototype.toString.call(fn) !== '[object Function]') {
throw new Error(`${fn} is not a function`)
}
let timer = null
return function (...arg) {
if (timer) {
clearTimeout(timer)
timer = null
}
let _this = this
timer = setTimeout(()=>{
fn.call(_this, ...arg)
}, time)
}
}
// 节流
function throttle (fn, time) {
// 边缘检测 如果 fn不是函数 报错提示
if (Object.prototype.toString.call(fn) !== '[object Function]') {
throw new Error(`${fn} is not a function`)
}
let startTime = 0
return function (...arg) {
let nowTime = +new Date()
let _this = this
if(nowTime - startTime > time) {
fn.call(_this, ...arg)
startTime = nowTime
}
}
}
this
指执行时动态 读取上下文决定的 而不是创建时决定的
如何改变this指向
- call bind apply
- 三者的区别是什么
- call 和 apply 入参形式不同 call bind是以逗号分开的形式 apply是数组的形式
- bind和call的区别是 bind不会立即执行 call会立即执行
- 改变this指向的原理 是给需要借用方法的对象绑定这个方法 执行后删除该方法
- 手写apply
Function.prototype.myCall = function (obj, ...arg) {
let context = this
obj = obj || window
let key = Symbol('key')
obj[key] = context
let result = obj[key](...arg)
delete obj[key]
return result
}
new 操作符
- new 操作符做了什么
- 1.创建一个新对象
- 2.新对象__proto__ 指向构造函数的prototype
- 3.执行构造函数 将新对象作为构造函数的上下文
- 4.返回一个新对象 要么是实例对象 要么是return指定的对象
- 手写new操作符
function _new (constructor, ...arg) {
// 边界判断
if(Object.prototype.toString.call(constructor) !== '[Object Function]') {
throw new Error(`${constructor} is not a function`)
}
// 1.创建一个新对象
let newObj = {}
// 2.新对象__proto__ 指向构造函数的prototype
newObj.__prtot__ = constructor.prototype
// 3.执行构造函数 将新对象作为构造函数的上下文
let result = constructor.call(newObj, ...arg)
// 4.返回一个新对象 要么是实例对象 要么是return指定的对象
return typeof result !== 'object' ? newObj : result
}
原型与原型链
- 原型 氛围显示原型 prototype 隐式原型 proto
- 原型链 查找对象的属性和方法 如果找不到就沿着__proto__往上查找,我们把__proto__形成的链条关系称为原型链
继承
* 原型继承
//缺点 子类修改父类属性 会相互影响
function Parent (name) {
this.name = name
this.like = ['red','pink']
this.say = function () {
console.log(`my name is ${this.name}`)
}
}
function Child (name, age) {
this.name = name
this.age = age || 18
}
Child.prototype = new Parent()
let c = new Child('xiaomin', 12)
c.say() //my name is xiaomin
console.log(c.like) //['red', 'pink']
// 如果这时候有另一个实例
let c1 = new Child('xiaomhong')
// 修改父元素的属性 第一个实例也会受到影响
c1.like.push('blue')
console.log(c.like) //['red', 'pink', 'blue']
* 构造函数继承
// 无法继承父类原型上的属性和方法
function Parent (name) {
this.name = name
this.like = ['red','pink']
this.say = function () {
console.log(`my name is ${this.name}`)
}
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this)
this.name = name
this.age = age || 18
}
let c = new Child('xiaomin', 12)
c.say() //my name is xiaomin
c.getName() // c.getName is not a function
- 组合式继承
// 缺点 父类会被调用2次
function Parent (name) {
console.log('我被调用了')
this.name = name
this.like = ['red','pink']
this.say = function () {
console.log(`my name is ${this.name}`)
}
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this)
this.name = name
this.age = age || 18
}
Child.prototype = new Parent()
let c = new Child('xiaomin', 12)
c.say() //my name is xiaomin
c.getName() // xiaomin
let c1 = new Child('xiaohong')
c1.like.push('blue')
console.log(c.like) //['red', 'pink']
- 组合寄生式继承
// 相对完美的继承
function Parent (name) {
console.log('我被调用了')
this.name = name
this.like = ['red','pink']
this.say = function () {
console.log(`my name is ${this.name}`)
}
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this)
this.name = name
this.age = age || 18
}
Child.prototype = Object.create(Parent.prototype)
Child.constructor = Child
let c = new Child('xiaomin', 12)
c.say() //my name is xiaomin
c.getName() // xiaomin
let c1 = new Child('xiaohong')
c1.like.push('blue')
console.log(c.like) //['red', 'pink']
- class继承
// 通过babel将calss继承转es5时 应用的是组合寄生式继承
class Parent {
constructor(name){
this.name = name
this.like = ['red','pink']
}
say () {
console.log(`my name is ${this.name}`)
}
}
class Child extends Parent {
constructor (name, age) {
super(name)
this.name = name
this.age = age || 18
}
}
let c = new Child('xiaomin', 12)
c.say() //my name is xiaomin
let c1 = new Child('xiaohong')
c1.like.push('blue')
console.log(c.like) //['red', 'pink']
- 组合寄生式继承中 Object.create 做了什么 手写一个
// Object.create 创建一个新对象 新对象的原型设置成入参对象 最后返回这个对象
Object.prototype._create = function (obj) {
if(Object.prototype.toString.call(obj) !== '[Object Object]') {
throw new Error(`${obj} is not object`)
}
let o = {}
o.__porto__ = obj
return o
}
js的执行机制
- js是一门单线程的语言,也就表示同一个时间只能做一件事,这样会导致的问题是:如果js执行时间过长会导致页面渲染不连贯,页面渲染加载阻塞
- 为了解决上述问题,利用多核CPU的计算能力,HTML5提出了Web Worker标准,允许js脚本创建多个线程,于是js出现了同步异步
- js执行–先执行栈中的同步任务,异步任务存放在任务队列当中,当同步任务执行完,系统依次取出任务队列中的任务执行
- 同步
在主线程上排队执行 前一个任务执行完执行下一个任务,程序的执行顺序与任务的排列顺序是一致的, - 异步
不进入主线程,进入到任务队列中,当主线程的任务执行完后任务队列通知主线程某个异步任务能执行了 主线程再把任务放到主线程执行 - 异步任务的类型:1. 定时器 setTimeout setInterval 2. 事件触发 click onload 3. promise 4. axaj请求 等
- 宏任务 微任务
setTimeout setInterval setImmwediate 宏任务
promise await async process.nextTick 微任务
微任务的优先级比宏任务高 宏任务执行前先看有没有需要执行的微任务 如果有微任务先执行微任务
- 同步
异步编程
- 处理异步的一些方法 回调函数 promise generator await/async
- promise的出现是为了解决回调地狱问题,await的出现是让异步写法更加像同步 await最终返回的是一个promise
promise相关
- 状态 promise 有三中状态 pending fulfilled rejected 状态不可逆 pending 只能 变更为 fulfilled | rejected
- then 返回的是一个promise支持链式调用
- promise 的一些方法
- promise.allSettled 同时处理多个异步函数 无论成功和失败都会返回
- promise.race 同时处理多个异步函数 当有一个状态发生变更就返回该状态 无论成功和失败
- promise.any 同时处理多个异步函数 当有一个成功 就返回成功 如果都失败就返回失败
- promise.all 同时处理多个promise请求 如果有一个promise失败 则不会继续进行 直接返回失败
- 如何让promise.all 失败的情况下全部返回 通过map的方式将每个promise retrun出去
let p1 = new Promise((resolve,reject)=>{
resolve('1')
})
let p2 = new Promise((resolve,reject)=>{
resolve('1')
})
let p3 = new Promise((resolve,reject)=>{
reject('error')
})
let p4 = new Promise((resolve,reject)=>{
resolve('1')
})
Promise.any([p1,p2,p3,p4]).then((res)=>{
console.log(res) // 打印错误
}).catch(err=>{
console.log(err)
})
// 可以将所有的结果返回
Promise.any([p1,p2,p3,p4].map((p) => {
return p.then((res) => {
return res
}, (err) => {
return err
})
})).then((res)=>{
console.log(res) // [1,1,error,1]
}).catch(err=>{
console.log(err)
})
- 手写promise
// 基础写法 缺少了 then返回值是一个新的promise的状态和结果的判定
// 该对象的状态和返回值由回调函数的返回值决定
// 返回值是一个promise对象 返回值是成功 新的promise就是成功 反之失败
// 注意::返回值是一个promise对象 接着又返回一个promise对象的情况下 需要用到递归判断 来判定到底成功还是失败
// 如果返回值不是一个promise对象 新的promise就是成功 值就是返回值
class MyPromsie {
constructor(fn){
// 定义状态默认为pending
this.status = 'PENDING'
// 定义成功的值
this.value = null
// 定义失败原因
this.reason = null
// 创建成功任务队列
this.onFulfilledCallbacks = []
// 创建失败任务队列
this.onRejectedCallbacks = []
// 立即执行fn
try {
fn(this.reslove.bind(this), this.reject.bind(this))
} catch (err) {
this.reject(err)
}
}
// 成功回调
reslove (value) {
// 如果状态待定 将状态修改为成功
setTimeout(()=>{
if (this.status = 'PENDING') {
this.status = 'FULFILLED'
this.value = value
onFulfilledCallbacks.forEach((item) => item(value))
}
})
}
// 失败回调
reject (reason) {
// 如果状态待定 将状态修改为失败
setTimeout(()=>{
if (this.status = 'PENDING') {
this.status = 'REJECTED'
this.reason = reason
onRejectedCallbacks.forEach((item) => item(reason))
}
})
}
// then 回调
then (onFulfilled, onRejected) {
onRejected = typeof onRejected === 'function' ? onRejected : () => {}
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {}
return new MyPromsie((reslove, reject)=>{
if (this.status === 'FULFILLED') {
setTimeout(()=>{
onFulfilled(this.value)
})
}
if (this.status === 'REJECTED') {
setTimeout(()=>{
onRejected(this.reason)
})
}
if (this.status === 'PENDING') {
this.onRejectedCallbacks.push(onRejected)
this.onRejectedCallbacks.push(onFulfilled)
}
})
}
catch (onRejected) {
return this.then(undefined, onRejected)
}
}