以下是本人面试后总结出来的面试知识点:
\1. ES6
\2. Commonjs
\1. Webpack核心思想:模块化打包
\2. Webpack基本配置: entry, output, module&rules, plugins, devServer, devtool, resolve
\3. 打包各种资源: JS / CSS/img/html/…
\4. 加分项: 自定义webpack打包环境, Webpack性能优化
\1. 原生ajax请求 xhr/fetch
\2. ajax请求跨域问题
\3. ajax请求库axios的理解和使用
\4. axios的二次封装
\5. 加分项: axios的核心实现源码分析
\1. jQuery
(1) 整体理解
(2) jQuery核心函数
(3) jQuery核心对象
\2. Vue
(1) Vue基本核心语法
(2) Vue-router
(3) Vuex
(4) MVVM实现原理
\3. React
(1) React基本核心语法
(2) React-router
(3) Redux
\1. JS语言的执行环境是"单线程", 也就是我们写的所有js代码都是在一个线程(主线程)上执行
\2. 理解单线程:
(1) 就是指一次只能完成一件任务。
(2) 如果有多个任务,就必须排队,前一个任务完成,再执行后面一个任务,以此类推
\3. js执行任务的2种模式
(1) 同步(Synchronous)
① 后一个任务等待前一个任务结束,然后再执行,
② 程序的执行顺序与任务的排列顺序是一致的、同步的
(2) 异步(Asynchronous)
① 每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,
② 后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
\4. 为什么需要异步JS?
(1) 浏览器端:
① 耗时很长的操作都应该异步执行,避免浏览器失去响应
② 给时机让浏览器能去更新界面, 响应用户操作
(2) Node端:
① "异步模式"是唯一的模式,执行环境是单线程的
② 处理请求的回调函数/数据库操作/文件读写操作都必须是异步处理, 否则处理多个请求时很快就会失去响应
\5. JS常用的异步编码方式
(1) 回调函数
(2) 事件机制
(3) 消息订阅与发布 / 全局事件总线
(4) Promise
(5) async & await
\1. 异步编程最基本的方法, 所有模式都是在此基础上进行封装扩展而来
\2. 回调函数的执行模式:
(1) 异步执行
(2) 同步执行
\3. 异步回调的缺点:
(1) 不利于代码的阅读和维护,各个部分之间高度耦合。
(2) 容易导致回调地狱问题
\1. 事件驱动模式:
(1) 任务的执行不取决于代码定义的顺序,而取决于某个事件是否发生
(2) 事件监听函数定义时不会执行, 只有当事件发生(分发事件)后才执行
\2. 分类:
(1) 原生DOM事件
(2) 自定义事件
\3. 操作:
(1) 在某个元素/组件对象上绑定特定事件监听
(2) 在某个元素/组件对象上分发事件
\4. 特点:
(1) 针对某个DOM元素绑定监听和分发事件
(2) 针对某个组件对象绑定监听和分发事件
\1. 消息订阅/发布:
订阅全局消息
发布全局消息
\2. 全局事件总线:
绑定事件监听
分发事件
\3. 特点:
分发事件后, 所有同名的事件监听回调都会调用
\1. 实现异步编程新的通用解决方案
\2. 相对于纯回调的优势
(1) 指定异步回调函数的方式更灵活(可以在启动异步任务后,甚至可以在任务完成后)
(2) 通过then的链式调用解决回调地狱的问题
\3. 不足: 还需要指定回调函数
\1. 基于promise的语法糖, 简化了promise对象的使用(不再使用回调函数编码)
\2. 以同步编码方式实现的异步流程
\3. 是js异步编程的终极解决方案(基本上可以这样说)
\1. js是单线程运行的
\2. js的回调函数可以异步执行, 也可以同步执行
\3. js通过event-loop机制实现了js的单线程异步执行
(1) JS引擎解析执行js代码总是在主线程执行(WebWorks除外)
(2) 浏览器有在分线程执行的对应管理模块(浏览器是多线程执行的)
① 定时器
② DOM事件监听
③ ajax请求
④ Promise
⑤ MutationObserver
(3) JS引擎有专门的回调队列, 缓存待执行的回调函数
① 宏队列
② 微队列
\1. 如何改变promise的状态?
\2. 一个promise指定多个成功/失败回调函数, 都会调用吗?
\3. promise.then()返回的新promise的结果状态由什么决定?
(1) 返回一个非promise值 resolved
(2) 抛出异常 rejected
(3) 返回一个promise
① 成功了 resolved
② 失败了 rejected
③ pending pending
\4. 改变promise状态和指定回调函数谁先谁后?
\5. promise如何串连多个操作任务?
\6. promise异常传(穿)透?
\7. 中断promise链
\1. 定义整体结构
\2. Promise构造函数的实现
\3. promise.then()/catch()的实现
\4. Promise.resolve()/reject()的实现
\5. Promise.all/race()的实现
\6. Promise.resolveDelay()/rejectDelay()的实现
\7. ES6 class版本
\1. 前后台交互接口: 请求地址 / 请求方式 / 请求参数格式 / 响应数据格式
\2. 测试接口: 使用postman
\3. 模拟(mock)接口: 使用mockjs / json-server / webpack / node&express
\1. 区别ajax请求与一般的HTTP请求
\2. 原生ajax请求: XHR与fetch
\3. XHR的基本编码流程
\1. JSONP: json with padding(垫子)
\2. CORS: 服务器端设置响应头: Access-Control-Allow-Origin: www.taobao.com
\3. 代理
(1) 基于xhr/http包 + promise的异步ajax请求库
(2) 浏览器端/node端都可以使用
(3) 支持请求/响应拦截器
(4) 支持请求取消
(5) 请求/响应数据转换
(6) 批量发送多个请求
axios(config): 通用/最本质的发任意类型请求的方式
axios(url[, config]): 可以只指定url发get请求
axios.request(config): 等同于axios(config)
axios.get(url[, config]): 发get请求
axios.delete(url[, config]): 发delete请求
axios.post(url[, data, config]): 发post请求
axios.put(url[, data, config]): 发put请求
axios.create([config]): 创建一个新的axios(它没有下面的功能)
axios.defaults.xxx: 请求的默认全局配置
axios.interceptors.request.use(): 添加请求拦截器
axios.interceptors.response.use(): 添加响应拦截器
axios.Cancel(): 用于创建取消请求的错误对象
axios.CancelToken(): 用于创建取消请求的token对象
axios.isCancel(): 是否是一个取消请求的错误
axios.all(promises): 用于批量执行多个异步请求
axios.spread(): 用来指定接收所有成功数据的回调函数的方法
\1. 请求loading
\2. token处理: 通过请求头携带token数据, 对token进行校验处理
\3. 异步请求成功的数据不是response, 而是response.data
\4. 统一处理请求异常
\5. 对请求体参数进行urlencode处理, 而不使用默认的json方式(后台接口不支持)
\1. 相同:
(1) 都是一个能发任意请求的函数: request(config)
(2) 都有发特定请求的各种方法: get()/post()/put()/delete()
(3) 都有默认配置和拦截器的属性: defaults/interceptors
\2. 不同:
(1) 默认配置不一样, 且相互之间是独立的
(2) instance没有axios后面添加的一些方法: create()/CancelToken()/all()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WPu30d97-1647229293205)(file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JEvGrOvJ-1647229293207)(file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg)]
\1. 请求拦截器:
在真正发送请求前执行的回调函数
可以对请求进行检查或配置进行特定处理
成功的回调函数, 传递的默认是config(也必须是)
失败的回调函数, 传递的默认是error
\2. 响应拦截器
在请求得到响应后执行的回调函数
可以对响应数据进行特定处理
成功的回调函数, 传递的默认是response
失败的回调函数, 传递的默认是error
\1. 请求转换器: 对请求头和请求体数据进行特定处理的函数
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, ‘application/json;charset=utf-8’);
return JSON.stringify(data);
}
\2. 响应转换器: 将响应体json字符串解析为js对象或数组的函数
response.data = JSON.parse(response.data)
\1. 本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包工具
\2. 当 webpack 处理应用程序时,它会在内部构建一个 依赖图(dependency graph),此依赖图会映射项目所需的每个模块,并生成一个或多个 bundle。
\1. 入口**(entry)**: 打包依赖图的入口js
\2. 输出**(output)**: 打包生成的bundle
\3. loader: webpack 只能理解 JS 和 JSON 文件, loader打包其它类型模块
\4. 插件**(plugin)**: 用来处理各种各样的任务(webpack和loader处理不了)
\5. 模式**(mode)**: 用于启用 webpack 内置的一系列配置
\1. entry
\2. output
\3. module & rules
\4. plugins
\5. mode
\6. devServer
\7. devtool
\8. resolve
\9. optimization
\1. babel-loader
\2. css-loader/style-loader/less-loader/stylus-loader/ sass-loader/postcss-loader
\3. file-loader / url-loader
\4. eslint-loader
\5. vue-loader/vue-style-loader
\1. html-webpack-plugin
\2. copy-webpack-plugin
\3. clean-webpack-plugin
\4. optimize-css-assets-webpack-plugin
\5. webpack-bundle-analyzer
\6. webpack.HotModuleReplacementPlugin
\1. loader: 用于加载特定类型的资源文件, webpack本身只能打包js, 如果打包css就需要css-loader/style-loader, 如果打包图片就需要file-loader/url-loader
\2. plugin: 用来扩展webpack其它方面的功能, 如页面引入打包文件需要html-webpack-plugin, 删除文件需要clean-webpack-plugin
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6KvGfNsW-1647229293208)(file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image006.jpg)]
\1. module: 我们手写下一个一个的文件
\2. webpack开始打包后, 会根据文件引用关系生成内存中的几个 chunk 文件
\3. 保存到本地的是bundle文件, 一般一个chunk对应一个bundle, 但也可能一个chunk对应多个bundle(拆分样式打包)
\4. 拆分js是在生成chunk前, 所以拆分出的js都是一个单独的chunk
\5. 拆分css是在生成chunk后, 所以拆分出的css与js共用一个chunk
webpack4添加的魔法注释功能, 可以使用preload-webpack-plugin@next进行处理
\1. webpackPreload:
(1) 同时并行请求加载, 针对当前就需要的bundle文件
(2)
\2. webpackPrefetch:
(1) 空闲时才请求加载, 针对其它路由需要的bundle文件
(2)
\1. 理解
(1) hash整体模块文件内容的md5值
(2) chunkhash当前chunk内容的md5值
(3) contenthash当前bundle内容的md5值
\2. 使用
(1) js/css/img使用contenthash
(2) img/audio/video使用hash时本质还是用的contenthash
3. 注意
用hash****有什么问题?
用chunkhash****有什么问题?
\1. Inline
(1) 生成全部的source map后整体内联到打包文件中
(2) 更新代码时完全重新生成source map
\2. eval
(1) 以模块为单位生成source map内联到打包文件中
(2) 更新代码时只会重新生成相应的source map
(3) 主要是更新打包速度快于inline
\3. cheap
(1) 只记录代码行号, 不记录列号
(2) 只映射自定义模块
\4. module
(1) 只记录代码行号, 不记录列号
(2) 只映射第三方模块
最佳实践***
开发环境**------devtool: ‘cheap-module-eval-source-map’**
测试生产环境**-----devtool: ‘cheap-module-source-map’**
上线生产环境**-----devtool: ‘none’**
\1. 使用@babel/core和@babel/preset-env
(1) 只能编译ES6的新语法(转换为ES5相应的语法)
(2) 问题: 不能处理ES6的新API, 在相对低版本浏览器中不能运行
\2. 使用@babel/polyfill
(1) 内部通过core-js提供了新API的实现
(2) 问题: 默认是打包整体包, 导致打包文件太大
\3. 实现polyfill的按需要引入打包
(1) useBuiltIns: ‘usage’
\1. 目标
兼容性 / 减小打包文件/ 懒加载 / 预加载 / 首屏加载优化
\2. 常用技巧
(1) 兼容低版本浏览器
(2) 拆分打包 & 压缩
(3) 异步/懒加载
(4) 预取/预加载
(5) 打包文件hash化(利用浏览缓存)
(6) Tree Shaking
(7) Scope Hoisting(作用域提升)
(8) 服务器(nginx)开启gzip
(9) 打包文件分析
\3. 目标
加快打包 / 提升开发调试体验
\4. 常用技巧
(1) loader增加include匹配特定条件
(2) 合理配置extensions扩展名
(3) 配置resolve.alias字段, 指定常用的路径别名
(4) dll第三方模块进行预打包==> 使HardSourceWebpackPlugin更简洁更快
(5) eslint代码规范检查
(6) sourcemap 源码映射
(7) live-reload / hot-reload
(8) 加快loader处理: 多线程/多进程loader
变量:用来存放数据,保存的数据可以修改
常量:用来存放数据,保存的数据不可修改
变量和常量的本质,无论是变量还是常量,其本身都是数据,也需要在内存中占用内存空间,保存在内存的栈结构分区中
ECMAScript标准定义了8种数据类型
1. 7种原始数据类型:String,Numbr,Boolean,Undefined,null,Symbol,BigInt
Object,Function,Array,Date,RegExp
typeof 运算符(判断数据的类型)
作用:用来获取当前变量中存储的数据的类型
typeof的返回值有多少个
instanceOf 运算符(原生JS判断实例的类型)
作用:用来判断当前实例对象是不是某种数据类型
基本数据类型和引用数据类型在内存中,内存空间是如何存储数据的
除 Object 以外的所有类型都是不可变的(值本身无法被改变),JavaScript 中字符串是不可变的(译注:如,JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。我们称这些类型的值为“原始值”。
基本数据类型的值在栈空间中存储,如果修改了数据,则是把原来的值直接干掉,重新存放新的值
引用数据类型的对象在堆空间中存储,该空间的地址在栈空间中存储,如果修改栈空间存储的地址,则指向发生变化,也叫引用发生了变化,此时是在堆空间中重新指向了一个新的内存空间(存储了一个新的对象)
基本类型之间的值如何传递? 传递的是数值
引用类型之间的值如何传递? 传递的是引用(地址)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X2j2mJVW-1647229293210)(/images/引用类型的传递.jpg)]
内存:用于暂时存放CPU中的运算数据以及与硬盘等外部存储器交换的数据
计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来,内存的运行也决定了计算机的稳定运行。
内存通电后产生的存储空间(临时的)
产生和死亡:内存条(集成电路板)—>通电—>产生一定的容量存储空间—>存储各种数据—>断电—>内存空间全部消失
内存的空间是临时的,而硬盘的空间是持久的
内存包含2个数据:内存存储的数据(一般数据/地址数据)和内存地址值数据
内存分类:
具有一定的属性或者方法,特指的某个事物
看得见或者是摸得到,特指的某个东西
识别对象,抽象出对象有什么特征和行为
抽象出当前的对象属于什么类别
以上都是抽象出对象和类别的过程,抽象性
抽象的过程要转变成代码的方式,写代码:先有类别(构造函数),创建对象并设置对象的属性及方法的初始值,对象的初始化过程,最终对象调用相关方法,操作相关属性及某些行为
特征就是属性,行为就是方法,类别就是构造函数,创建对象就是实例化的过程(初始化属性及方法的中)
工厂模式:自定义函数实现对象的创建
通过构造函数实现对象的创建
字面量的方式创建对象
// 1.通过字面量创建对象
var obj ={}
// 2.通过工厂模式创建对象
function createObject(name,age){
var obj = new Object()
obj.name = name
obj.age = age
return obj
}
var obj1 =createObject('小明',10)
var obj2 =createObject('小红',20)
console.log(obj1,obj2)
// 3.构造函数的方式创建对象
function Person(name,gender){
this.name = name
this.gender = gender
this.sayHi=function(){
console.log('您好,我是:'+this.name)
}
}
var per = new Person('小明','男')
per.sayHi()
console.log(per)
通过ES6中的class来创建
// 类的方式
class Student{
constructor(name,age,gender){
this.name = name
this.age =age
this.gender = gender
}
// 在原型上
sayHi(){
console.log(`您好,我是${this.name},几年${this.age}岁了,是${this.gender}生`)
}
// 在实例上
eat=()=>{
console.log('吃东西啊')
}
}
单例模式
function createObj() {
var instance = null
return function (name) {
if (!instance) {
instance = new Object()
instance.name =name
}
return instance
}
}
var getObj = createObj()
var obj1 = getObj('小明')
var obj2 = getObj('小红')
console.log(obj1,obj2)
console.log(obj1===obj2)
1. 对象.属性名字
2. 对象['属性名字']
3. 什么时候使用对象[属性名字]的写法
- 不确定属性名字是什么(属性名字是变量)
- 属性名字不太规范的时候
js是弱类型语言,声明变量都用var
js是脚本语言 直接执行
js是解释性语言 直接解释
js是动态类型语言 变量在执行的时候才知道具体的类型,对象没有这个属性,点了,就有了
js是单线程语言 执行的时候安装一定的顺序,之前的代码执行完毕后,后面才执行
js是基于对象的语言,最终所有的对象都指向了object
原型就是对象,JS中原型一共有两个,一个是prototype,一个是**__proto__
属性**
- prototype: 浏览器的标准属性,程序员使用的,显示原型,存在于函数中
- __proto__: 浏览器的非标准属性,浏览器使用的,隐式原型,存在于实例对象中
- 函数中有prototype,实例对象中有__proto__
- 实例对象也是对象,里面就有__proto__
- 实例的__proto__与对应函数的prototype都指向原型对象
- 无论是构造函数还是普通函数,或者是方法,只要是函数,内部就有prototype
2. 原型的作用之一:共享数据,节省内存空间
1.实例对象一般都是通过构造函数进行创建的,实例化对象的时候做的四件事:
- var per = new Person('卡卡西',20)
1) 申请一块空闲的空间,用来存储当前的实例对象
2) 设置this为当前的实例对象(修改this的指向)
3) 初始化实例对象中的属性和方法的值
4) 把this作为当前对象进行返回
2. 在构造函数中定义的属性及方法,仅仅是编写代码进行定义而已,而实际上里面定义的属性及方法是属于每个实例对象的,所以,创建多个对象,就会开辟多个空间,每个空间中的每个对象都有自己的属性及方法,大量创建对象,对象的方法都不是同一个方法(方法也是函数,函数代码也占用空间),为了节省内存空间,那么可以使用原型的方式,实现数据共享,节省内存空间
3. 原型的作用之二:实现JS中的继承
1). 通过改变原型指向实现继承
2). 借用构造函数显示继承
3). 组合继承
4). 拷贝继承:浅拷贝和深拷贝(递归后再说)
原型对象上有一个constructor
属性指向对应的构造函数
function Fn () {}
const fn = new Fn()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pCIKpW1v-1647229293212)(/images/显示原型与隐式原型.png)]
: 区别执行函数定义与执行函数
执行函数定义: 也就是去创建函数对象, 只是有可能被JS引擎提升预处理执行
执行函数: 执行函数体中所有语句
先有函数定义的执行,才有执行函数
常见的回调
说说函数对象上的prototype属性?(prototype什么时候出现,执行函数定义的时候)
执行函数定义定义(有可能被提升执行)创建函数对象
给函数对象添加prototype属性, 属性值为空的Object实例对象, 也就是原型对象
给原型对象添加constructor属性, 值为函数
伪代码:
// 给函数对象添加prototype属性, 属性值为空的Object实例对象, 也就是原型对象
function Fn(){}
console.dir(Fn)
var obj ={}
console.log(obj.__proto__===Object.prototype)
console.log(Fn.prototype.__proto__===Object.prototype)
// 伪代码
this.prototype = {} // this就是函数对象
this.prototype.constructor = Fn
说说实例对象上的**__proto__
属性**?(_proto-什么时候出现,实例化对象的时候)
JS引擎在创建实例对象时内部自动执行时, 会自动给实例对象添加__proto__
属性, 值为构造函数的 prototype属性的值
this.__proto__ = Fn.prototype // this是实例对象
原型链(实际上是隐式原型链,言外之意就是和显示原型没毛关系,显示原型产生实例的一瞬间起的作用)
从对象的__proto__
开始, 连接的所有对象, 就是我们常说的原型链, 也可称为隐式原型链
查找对象属性简单说: 先在自身上查找, 找不到就沿着原型链查找,如果还找不到返回undefined
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TkDoBl5q-1647229293212)(/images/image-20201113231139677.png)]
查找对象上属性的基本流程
先在对象自身上查找, 如果有, 直接返回
如果没有, 根据__proto__
在原型对象上查找, 如果有, 直接返回
如果没有根据原型对象的__proto__
在原型对象的原型对象上查找, 一直查找到Object原型对象为止
如果找到了返回, 如果查找不到由于它的__proto__
为null, 只能返回undefined
查找对象属性时候,会不会读函数的prototype?(会还是不会)
// 简单的原型链
function F1(){}
F1.prototype.number =100
function F2(){}
F2.prototype=new F1()
F2.prototype.number =200
function F3(){}
F3.prototype=new F2()
F3.prototype.number =300
var f3 = new F3()
console.log(f3.number)
console.dir(f3)
表达式a.b的解析流程
instanceOf
作用: 判断一个任意类型对象的具体类型
如何判断?
原型与原型链结构图
function Foo() { }
const f1 = new Foo()
const f2 = new Foo()
const o1 = new Object()
const o2 = {}
// 下面的结果
console.log(Foo instanceof Object)
console.log(Foo instanceof Function)
console.log(Object instanceof Object)
console.log(Function instanceof Function)
console.log(Function instanceof Object)
console.log(Object instanceof Foo)
console.log(f1 instanceof Function)
console.log(f1 instanceof Object)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lVX1N1gv-1647229293213)(/images/原型与原型链结构图.png)]
1. 对象中有__proto__,函数中有prototype
- 实例对象中__proto__指向的是当前实例对象对应的构造函数中的prototype
- 而每个prototype都是一个对象,所以,内部必然有__proto__,普通函数中的prototype的__proto__指向的是Object的prototype
- 每个函数是Function的实例对象,所以,只要是函数,那么函数对象中__proto__指向的都是Function的prototype,那么这个prototype中的__proto__指向的仍然是Object的prototype
- 但是,Object这个构造函数也是函数,所以,Object的__proto__指向的是Function的prototype
- Function这个构造函数也是对象,所以里面的__proto__指向的是Function的prototype
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K2KOwD1U-1647229293215)(D:/BaiduNetdiskDownload/11-面试精讲/11-面试精讲/精讲笔记/原型.jpg)]
继承(改变原型指向/借用构造函数/组合/拷贝)
// 通过原型实现继承
function Person(name,age,gender){
this.name = name
this.age =age
this.gender = gender
}
Person.prototype.sayHi=function(){
console.log('您好')
}
Person.prototype.eat=function(){
console.log('好吃')
}
function Student(name,age,gender,score){
// 借用构造函数实现属性的继承
Person.call(this,name,age,gender)
this.score = score
}
// 原型实现方法的继承
Student.prototype=new Person()
Student.prototype.constructor = Student
// 重写方法
Student.prototype.eat=function(){
console.log('学生吃')
}
var stu = new Student('小明',20,'男',100)
stu.sayHi()
stu.eat()
console.log(stu)
// ES6的方式实现继承
class Person{
constructor(name,age){
this.name = name
this.age = age
}
// 原型上的方法
sayHi(){
console.log('您好')
}
// 赋值的写法就是给实例添加属性或者方法--------
// 实例属性
sex = '男'
// 实例上的方法 run =function(){}
eat=()=>{
console.log('好吃啊')
}
// 静态属性
static gender='男'
}
var per = new Person('小明明',30)
console.log(per)
console.dir(Person)
class Student extends Person{
constructor(name,age,gender){
super(name,age)
this.gender = gender
}
// 重写父类中的方法
sayHi(){
console.log('我很好')
}
}
var stu = new Student('小明',20,'男')
console.log(stu)
js引擎在js代码正式执行之前会做一些预解析的操作
先找关键字var,function
找到var以后将var后面的变量提前声明,但是不赋值
找到function以后将function后面的函数提前声明,但是不赋值,也就是说函数在解析之前已经定义完毕了
变量的提升
- 浏览器在解析js代码之前,先把变量的声明提升
6. 函数的提升
- 浏览器在解析js代码之前,先把函数的声明提升
- 函数提升 ==> 变量提升, 同名的变量忽略
```js
// 先提升的谁?
a()
var a = 100
function a(){
console.log('函数')
}
console.log(a)
```
7. 注意:f2() var f2=function(){}; 报错:因为f2是undefined
8. 预解析:全局预解析和局部预解析
执行上下文(动态的):就是一个代码的执行环境(全局执行上下文和函数执行上下文,eval函数执行上下文)
1. 执行上下文概念:代表了代码执行的环境,包含:执行环境,变量对象,this,作用域链
2. 流程:
- js引擎在js代码正式执行前会先创建一个执行环境
- 进入该环境以后会创建一个变量对象,该对象用于收集:变量,函数,函数的参数,this
- 找关键字var,function
- 确认this
- 创建作用域链
3. 在全局代码执行前,js引擎就会创建一个栈来存储管理所有的执行上下文
4. 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
5. 在函数执行上下文创建后,将其添加到栈中(压栈)
6. 在当前函数执行完毕后,将栈顶的对象移除(出栈)
7. 当所有的代码执行完毕后,栈中只剩下window
8. 重点:执行上下文是动态创建的,尤其是针对函数,每调用一次函数都会创建一次执行上下文
总结执行上下文: 当代码要执行,但是没有执行,或者将要执行,在预解析之后,此时出现了全局执行上下文环境(全局执行上下文),创建了一个变量对象,用来收集var , function ,函数参数,确定this的指向,默认全局执行上下文是确定了this是window,这个变量对象会被压入到栈中(全局执行上下文的变量对象在栈中的最下面),如果出现了函数调用,此时出现了局部执行上下文环境(局部执行上下文),再次创建一个变量对象,用来收集函数参数,var ,function,改变this的指向,这个变量对象会被再次压入栈中,在全局执行上下文的变量对象的上面,如果当前函数调用完毕,此时出栈(把局部上下文的变量对象干掉),依次弹出变量对象,就结束了
概念:变量的使用范围,静态的(编写代码的时候就已经确定了)
全局作用域和局部作用域
全局作用域:函数外部变量的使用范围
局部作用域:函数内变量的使用范围(一个函数就是一个作用域)
块级作用域(ES6新增): const / let
作用:隔离变量,不同的作用域下同名的变量不会冲突
变量分为:全局变量(非函数内部定义的变量)和局部变量(函数内部定义的变量)
多个嵌套的作用域形成的由内向外的结构, 用于查找变量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TUtbOkIQ-1647229293216)(/images/作用域链.jpg)]
作用域于执行上下文
1. 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时已经确定了,而不是函数调用时
2. 全局执行上下文环境在全局作用域确定之后,js代码马上执行之前创建
3. 函数执行上下文环境是在调用函数时,函数体代码执行之前创建
4. 作用域是静态的,只要函数定义好了就一直存在,且不会再变化
5. 执行上下文是动态的,调用函数时创建,函数调用结束时上下文环境就会释放
6. 上下文环境(对象)是从属于所在的作用域
7. 全局上下文环境-->全局作用域
8. 作用域链
1. 用来决定代码执行的范围,变量的作用范围
2. 作用域是代码定义的时候决定的
3. 作用域链是如何产生的
- 函数在定义的时候自动添加一个属性'[[Scopes]]'该属性保存的是其上级作用域链
- 当函数执行的时候,进入执行上下文环境,将创建的变量对象添加到'[[Scopes]]'数组的第一个位置,形成新的数组
4. 查找变量的规则
- 先在当前作用域的变量对象中查找,找到则使用
- 如果没有则沿着作用域链的数组去上级作用域中的变量对象中查找
- 找到就返回对应的值,如果没有继续向上查找,知道找到最后一个变量对象(全局的变量对象),如果没有则报错
变量的查找:
代码编写的时候确定了当前全局作用域及局部作用域
在代码马上执行,还没执行,执行上下文环境就出现了,函数调用完毕后,局部执行上下文没了,整个代码结束,全局的执行上下文环境也没了
全局作用域---->预解析--->全局执行上下文环境--->全局的变量对象{var ,function ,函数参数,this的指向}----->函数定义---->局部作用域---->出现了函数调用----->局部局解析--->局部的执行上下文环境---->局部的变量对象{var ,function ,函数参数,this的指向}
- 理解:
- 当嵌套的内部函数引用了外部函数的变量时就产生了闭包(执行外部函数,不一定就会产生闭包)
- 什么时候产生的闭包? 执行内部函数定义(创建内部函数对象)后
- 通过chrome工具得知: 闭包本质是内部函数中的一个对象(非js的容器), 这个容器中包含引用的变量
1. 闭包内部包含了被引用变量(函数)的对象
2. 说白了,闭包其实就是一种引用关系,引用关系存在于内部函数中,引用的是外部函数的变量的对象(深入理解)
2. 函数内部本身是个局部作用域,如果出现闭包,延长了局部作用域的生命周期
3. 闭包延长局部变量的生命周期后,如果不及时释放会出现内存泄漏
4. 闭包作用:
- 闭包的作用:延长外部函数变量对象的生命周期
- 让函数外部可以操作(读写)函数内部的数据(变量/函数)
5. 闭包什么产生的?
- 闭包在嵌套内部函数定义执行完成时就产生了(不是调用)
6. 闭包什么时候挂的?
- 在嵌套的内部函数成为垃圾对象的时候
7. 闭包的优点/缺点及如何清除闭包
- 优点/缺点: 延长外部函数变量对象的生命周期(不及时清除容易造成内存溢出、泄漏)
- 释放闭包: 让内部函数对象成为垃圾对象, 断开指向它的所有引用
注意问题:函数中定义函数,内部函数没有调用,则不会出现在局部执行上下文的变量对象中
function fn1() {
var a = 2;
function fn2() {
a++;
console.log(a);
}
return fn2;
}
// 此时fn2释放了没有呢?
var f = fn1();
// 产生一个闭包
f();
// 产生一个闭包
f();
var f2 = f
f = null
function showDedelay (time, msg) {
setTimeout(() => {
alert(msg)
}, time)
}
showDelay(1000)
闭包什么时候产生的? 执行函数定义产生引用变量的的时候产生闭包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5gydRY5e-1647229293217)(/images/产生闭包.jpg)]
下面的没有产生闭包(没有产生内部函数引用变量,函数引用变量是执行函数定义才能有该应用变量,fn2就是内部引用变量)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wx0HKVHU-1647229293218)(/images/没有产生闭包.jpg)]
闭包的应用:
举删除删除列表中的的某个商品的例子(带确定框)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qtr8gGIB-1647229293218)(D:/BaiduNetdiskDownload/11-面试精讲/11-面试精讲/精讲笔记/images/image-20201114144206567.png)]
内存溢出和内存泄漏
1. 内存泄露 :是指程序在申请内存后,无法释放已申请的内存空间就造成了内存泄漏,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
2. 内存溢出: 指程序申请内存时,没有足够的内存供申请者使用,或者说,一个杯子里你非要装一桶的水,那么结果就是内存不够用,即所谓的内存溢出,简单来说就是自己所需要使用的空间比我们拥有的内存大内存不够使用所造成的内存溢出。
this在不同场景下的取值?
this学习的2个目标:
常规情况下, 函数中的this取决于执行函数的方式
特殊情况:
如何控制函数的this?
利用函数的bind()
利用箭头函数
也可以用外部保存了this的变量
var m = 1
function f1(){
console.log(this.m)
return 3
}
var obj = {m:2}
f1.bind(obj)
// 结果?
f1()
// 结果??
f1.bind(obj)()
// 结果???
console.log(f1.bind(obj)())
// 自己实现bind
Function.prototype.bind = function (obj) {
console.log('myBind')
return () => {
return this.call(obj)
}
}
var obj2= {
test(){
const f1=()=>{
console.log('+++',this) // 谁?
}
f1()
const f2 = function(){
console.log('----',this) // 谁?
}
f2()
}
}
obj2.test()
进程: 程序的一次执行,它占有一片独有的内存空间
线程: CPU的基本调度单位,是程序执行的一个完整流程
1. 一个进程中一般至少有一个运行的线程:主线程
2. 一个进程中也可以同时运行多个线程,我们会说程序是多线程的
3. 一个进程中的数据可以供其多个线程直接共享
4. 多个进行质检的数据是不能直接共享的
浏览器运行是单进程还是多进程
1. 有的是单进程的
- firefox
- 老版本IE
2. 有的是多进程
- chrome
- 新版IE
- 新版火狐
3. 如何查看浏览器是否是多进程运行的
- 任务管理器----->进程
- 都是多线程运行的
浏览器内核
1. 支持浏览器运行的核心的程序
2. 不同的浏览器内核不太一样
- IE浏览器内核:Trident内核,也是俗称的IE内核;
- Chrome浏览器内核:统称为Chromium内核或Chrome内核,以前是Webkit内核,现在是Blink内核;
- Firefox浏览器内核:Gecko内核,俗称Firefox内核;
- Safari浏览器内核:Webkit内核;
- Opera浏览器内核:最初是自己的Presto内核,后来加入谷歌大军,从Webkit又到了Blink内核;
- 360浏览器、猎豹浏览器内核:IE+Chrome双内核;
- 搜狗、遨游、QQ浏览器内核:Trident(兼容模式)+Webkit(高速模式);
- 百度浏览器、世界之窗内核:IE内核;
- 2345浏览器内核:好像以前是IE内核,现在也是IE+Chrome双内核了;
- UC浏览器内核:这个众口不一,UC说是他们自己研发的U3内核,但好像还是基于Webkit和Trident,还有说是基于火狐内核。。
js是单线程的
1. 如何证明JS执行是单线程的
- setImteout()的回调函数是在主线程执行的
- 定时器回调函数只有在运行栈中的代码全部执行完毕后才有可能执行
2. 为什么JS要用单线程模式,而不是多线程模式
- JS的单线程与它的用途
- 作为浏览器脚本语言,JS的主要用途是与用户互动,以及操作DOM
- 这决定了它只能是单线程,否则会带来很复杂的同步问题
- 同步会阻塞代码执行
- 异步不会阻塞代码执行
JS是单线程编程语言, 只能同时做一件事(普通人->单线程,影分身–>多线程)
// 同步回调
[1, 2, 3].forEach(item => {
console.log(item)
})
console.log('forEach()之后')
new Promise((resolve, reject) => { // excutor 执行器函数,作用:执行异步代码
console.log('执行excutor')
// 执行异步任务
})
console.log('new Promise()之后')
// 异步回调,宏任务
setTimeout(() => {
console.log('执行timout回调')
}, 0);
console.log('setTimeout()之后')
// 微任务
Promise.resolve(1).then(() => {
console.log('promise成功的回调')
})
console.log('.then之后')
js引擎是在一个线程(可以称为JS线程)上解析执行js代码的(web worker除外), 无论是同步代码还是异步代码
界面第一次渲染: 初始化同步代码 > 所有的微任务> 渲染界面==> 执行第一个宏任务
> 所有的微任务> 渲染界面==> 执行第一个宏任务
浏览器在另一个线程(GUI渲染线程)进行页面渲染操作,
GUI渲染线程与js线程是互斥(不会同时执行), 因为 JS 可以修改 DOM 结构
遇到需要等待 (网络请求, 定时任务) 不能卡住,需要异步
回调callback函数
浏览器多线程演示
<ul> <li>aaali> <li>bbbli> <li>cccli> ul> <button id="test">testbutton> <div id="content"> aaaaaaa div> <script> Promise.resolve().then(() => { // 微任务 alert('promise1') // 页面渲染了吗? }) Promise.resolve().then(() => { // 微任务 alert('promise2') // 页面渲染了吗? }) setTimeout(() => {// 宏任务 alert(document.getElementById('content').innerHTML) // 页面渲染了吗? }, 0) document.getElementById('test').onclick = () => {// 宏任务 document.getElementById('content').innerHTML = 'xxxx' // dom渲染 setTimeout(() => {// 宏任务 alert(document.getElementById('content').innerHTML) // 页面更新渲染了吗? }, 0); Promise.resolve().then(() => { // 微任务 alert('promise3') //页面更新渲染了吗? }) } alert('1111') // 页面渲染了吗? script>
使用Promise解决回调地狱问题(可阅读性很差,仍然要用回调,但是没有嵌套了)
Promise相对纯回调形式, 指定回调函数的时机更灵活(可以在发送请求后或请求结束后)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ehDGXgcZ-1647229293219)(D:/BaiduNetdiskDownload/11-面试精讲/11-面试精讲/精讲笔记/images/回调地狱.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PCkSLdfa-1647229293221)(D:/BaiduNetdiskDownload/11-面试精讲/11-面试精讲/精讲笔记/images/Promise解决回调地狱1.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CMAhdxgy-1647229293223)(D:/BaiduNetdiskDownload/11-面试精讲/11-面试精讲/精讲笔记/images/Promise解决回调地狱2.jpg)]
##10. 事件轮询机制event loop1(异步实现的原理统称:事件循环(轮询机制))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LdAIdViK-1647229293223)(.\images\event loop(只有宏任务)].png)
事件循环机制的2个重要部分
在分线程执行的管理模块: 定时器/ajax/dom事件
保存待执行的回调函数的事件队列(Event queue)/任务队列(Task queue)
事件轮询的执行过程:首先执行初始化代码,就是先执行同步代码,执行同步代码的时候,有可能启动定时器,有可能发送ajax请求,有可能绑定事件监听,执行这些代码的时候,会把回调函数交给对应的管理模块进行管理,而对应的管理模块在分线程执行,不会影响js执行,js会继续向下执行,比如启动一个setTimeout定时器(有个定时器的管理模块),假设1秒后执行,就会在1秒后把回调放在待执行的回调队列里,此时js有可能还在执行初始化代码,只有初始化代码全部的执行完毕后,一个一个,依次的取出执行
宏任务与微任务(任务就是回调,任务的本质就是回调)
宏队列与微队列(队列本质就是数组)
event loop2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zdlvZZlI-1647229293224)(D:/BaiduNetdiskDownload/11-面试精讲/11-面试精讲/精讲笔记/images/宏任务与微任务执行流程图.png)]
定时器注意:1. 千万不要在定时器后面放大量的代码块(会阻塞定时器,导致定时器不准确的)2. 定时器模块何时开始计时: - 开定时器的时候交给定时器管理模块就执行 H5规范提供了js分线程的实现,取名:Web Workers(了解)1. Worker:构造函数,加载分线程执行的js文件2. Worker.prototype.onmessage:用于接收另一个线程的回调函数3. Worker.prototype.postMessage:向另一个线程发送消息4. 不足 - Worker 内代码不能操作DOM(更新UI) - 不能跨域加载JS - 不是每个浏览器都支持这个新特性
ES6推出的新的更好的异步编程解决方案(相对于纯回调的方式)
promise对象有3种状态
promise状态的2种变化
4…promise的then()的理解
then()总是返回一个新的promise
新promise的结果状态由then指定的回调函数执行的结果决定
/* .then()返回的promise的结果状态由什么决定? 简单说: 由.then()指定并执行的回调函数的执行结果决定 详细说: 什么情况导致promise是失败的? 执行出错(抛异常了), 失败的reason就是抛出的错误 返回一个失败的promise, 失败的reason就是返回的promise的reason 什么情况导致promise是成功的? 返回一个成功的promise, 成功的value就是返回promise的value 返回一个非promise任务值, 也可以不返回(返回undefined), 成功的value就是返回值 */new Promise((resolve, reject) => { // 成功 // resolve(1) // 失败 reject(2) }).then( value => { console.log('onResolved1', value) }, // onResolved reason => { console.log('onRejected1', reason) // 抛出错误 // throw 100 // 返回一个失败的promise // return Promise.reject(200) // 返回一个成功的promise // return Promise.resolve(300) // 返回一个其他的值 // return 1000 Promise.resolve(2000) } ).then(value=>{ console.log('onResolved1', value) },reason=>{ console.log('onRejected2', reason) })
5.Promise.all([promise1, promise2, promise3]) - 批量/一次性发送多个异步请求 - 当都成功时, 返回的promise才成功 - 一旦有一个失败的, 返回的promise就失败了 问题: 发3请求成功后再4个请求 ```js function ajax(url) { return axios.get(url) } const p1 = ajax(url1) const p2 = ajax(url2) const p3 = ajax(url3) Promise.all([p1, p2, p3])// values和数组中数据的顺序有关系 .then(values => { return ajax(url4) }) .then(value => { console.log(value) // 就是第4个请求成功的value }) .catch(error => { })
6.async/await与promise的关系
async/await是消灭异步回调的终极武器
作用: 简化promise对象的使用, 不用再使用then/catch来指定回调函数
但和Promise并不互斥
反而, 两者相辅相成
执行async函数, 返回promise对象
await相当于promise的then
try…catch可捕获异常, 相当于promise的catch
// function ajax(url) { // return axios.get(url) // } // async function getProduct() { // try { // const response = await ajax('/product2.json') // return response.data // } catch (error) { // console.log('请求出错', error.message) // // throw error // return Promise.reject(error) // } // } // // 等同于上面async&await的函数 // function getProduct() { // return ajax('/product.json') // .then(response => { // return response.data // }) // .catch(error => { // console.log('请求出错', error.message) // // throw error // return Promise.reject(error) // }) // } // function test() { // getProduct().then(value => { // console.log(value) // }).catch(error => { // console.log('error', error.message) // }) // } // test()
##12.ES6
ECMA组织:欧洲计算机制造商协会,制定和发布的脚本语言规范
ECMAscript是基于Netscape javaScript的一种标准脚本语言。
JavaScript包含3个部分:
1)ECMAScript核心(JS标准语法)
2)浏览器端的扩展
BOM(浏览器对象模型)
DOM(文档对象模型)
3)服务器端的扩展
Node.js
ES的几个重要版本
ES5:09年发布
ES6:15年发布,也叫ECMA2015
ES7:16年发布,也叫ECMA2016
ES5给Object扩展了一些静态方法,常用的2个:
Object.create(prototype,[descriptors]) 创建对象并继承
作用: 以指定对象为原型创建新的对象
为新的对象指定新的属性,并对属性进行描述
value: 指定的值
weitable:标识当前属性值是否是可修改的,默认为false
configurable:标识当前属性是否可以被删除,默认是false
enumberable: 标识当前属性是否能用for in 枚举,默认为false
for-in(性能问题)不仅枚举自身属性,也可以枚举原型对象上的属性,一般配合对象.hasOwnProperty()方法
var person = {name: '小明',sayHi: function () { console.log('您好') }}// 新创建的stu对象和person对象是继承关系var stu = Object.create(person) // stu.__proto__---->personconsole.log(stu.name)stu.sayHi()
Objectt.defineProperties(object, descriptors)
作用:为指定对象定义扩展多个属性
get: 用来获取当前属性值的回调函数
set: 修改当前属性值的触发的回调函数,并且实参为修改后的值
存储器属性: settter,getter一个用来存值,一个用来取值
Array的方法扩展
1. Array.prototype.indexOf(value):得到数组中的某个数据的第一个下标,用来找数据的2. Array.prototype.lastIndexOf(value):得到数组中某个数据的最后一个小标3. Array.prototype.forEach(function(item,index){}):遍历数组4. Array.prototype.map(function(item,index){}):遍历数组,返回新数组5. Array.prototype.filter(function(item,index){}):遍历数组,过滤后的数组
ES6+
箭头函数
7.三点运算符: 拆包和打包
8.形参默认值: 简化函数的形参语法
9.Symbol:
1. ES5中对象的属性名都是字符串,容易造承重名,污染环境2. 概念:ES6中的添加了一种原始数据类型Symbol(已有的原始数据类型:String,Number,Boolean,null,undefined,对象)3. 特点: - Symbol 属性对应的值是唯一的,解决命名冲突问题 - Symbol 值不能与其他数据进行计算,包括同字符串拼串 - for in , for of 遍历时不会遍历symbol 属性 4. 使用: - 调用Symbol 函数得到symbol 值 - let symbol =Symbol() - let obj ={} - obj[symbol]='hello';5. 传参标识 - let symbol = Symbol('one') - let symbol2 = Symbol('two') - console.log(symbol) // Symbol('one') - console.log(symbol2) // Symbol('two')6. 定义常量标识 - 可以定义常量,就是标识 - const person_key = Symbol('person_key') - console.log(person_key)7. 内置Symbol值 - 除了定义自己使用的Symbol值以外,ES6还提供了11个内置的Symbol值,指向语言内部使用的方法 - Symbol.iterator - 对象的Symbol.iterator属性,指向该对象的默认遍历器方法(很快就讲了)
10.iterator 是一种接口机制,为各种不同的数据结构提供统一的访问机制
1. 作用: - 为各种数据结构,提供一个统一的,简便的访问接口 - 使得数据结构的成员能够按某种次序排列 - ES6创造了一种新的遍历命令,for..of循环,Iterator接口主要提供for...of消费2. 工作原理 - 创建一个指针对象(遍历器对象),指向数据结构的起始位置 - 第一次调用next方法,指针自动指向数据结构的第一个成员 - 接下来不断调用next方法,指针会一直往后移动,知道指向最后一个成员 - 每调用next方法返回的是一个包含value和done的对象,{value:当前成员的值,done:布尔值} - value表示当前成员的值,done对应的布尔值表示当前的数据的结构是否遍历结束 - 当遍历结束的时候返回的value值是undefine,done值为false - 原生具备iterator接口的数据(可用for of遍历) - 扩展理解: - 当数据结构上部署了Symbol.iterator接口,该数据可以用for-of遍历 - 当使用for of去遍历目标数据的时候,该数据会自动去找Symbol.iterator - Symbol.iterator属性指向对象的默认遍历器方法(iterator接口) - Array - arguments - set容器 - map容器 - String ....
// 模拟遍历器对象(指针对象) function myIterator(arr) { // iterator接口 let nextIndex = 0 // 默认第一次记录指针的位置 return { // 遍历器对象 next: function () { return nextIndex < arr.length ? { value: arr[nextIndex++], done: false } : { value: undefined, done: true } } } } let arr = [1, 2, 3, 4, 5] let interatorObj = myIterator(arr) console.log(interatorObj.next()) console.log(interatorObj.next()) console.log(interatorObj.next()) console.log(interatorObj.next()) console.log(interatorObj.next()) console.log(interatorObj.next())
11.Generator函数
1. 概念: - ES6提供的解决异步编程的方案之一 - Generator函数是一个状态,内部封装了不同状态数据 - 用来生成遍历对象 - 可暂停函数(惰性求值),yield可暂停,next方法可启动,每次返回的是yield后的表达式结果2. 特点: - function 与函数名之间有一个星号 - 内部用yield表达式来定义不同的状态 - 例如: function * generatorExample(){ let result = yield 'hello'; // 状态值为hello yield 'generator'; // 状态值为generator } - generator函数返回的是指针对象(接iterator),而不会执行函数内部逻辑 - 调用next方法函数内部逻辑开始执行,遇到yield表达式停止,返回{value:yield后的表达式结果/undefined,done:true} - 再次调用next方法会从上一次停止时的yield处开始,直到最后 - yield语句返回结果通常为undefine,当调用next方法时传参内容会作为启动时yield语句的返回值
// 需求,先做第一件事,然后做第二件事,最后最第三件事 function* generator(){ setTimeout(function(){ console.log('第一件是做完了') iterator.next('aaaa') },1000); let result=yield; console.log(result) setTimeout(function(){ console.log('第二件是做完了') iterator.next('bbbb') },2000); let result2=yield; console.log(result2) setTimeout(function(){ console.log('第三件是做完了') },3000); } let iterator = generator() iterator.next()
12.async 函数是generator函数的语法糖
1. 概念: 真正意义上去解决异步回调问题,同步流程表达异步操作2. 本质: Generator的语法糖3. 语法: - async function foo(){ await 异步操作; await 异步操作; }4. 特点: - 不需要像Generator去调用next方法,遇到await等待,当恰你的异步操作完成就往下执行 - 返回的总是Promise对象,可以用then方法进行下一步操作 - async 取代Generator函数的星号*,await 取代Generator的yield - 语义上更为明确,使用简单,经临床验证,暂时没有副作用及不良反应1. await 是暂停,但是必须跟着promise对象才会暂停,其他的都不暂停
// async 函数 async function f1() { console.log('函数开始执行') await setTimeout(function(){ console.log('定时器') },2000); console.log('函数执行中') await 456; console.log('函数执行结束') } f1() // 只有在await后面有promise才会暂停,如果要继续执行,修改promise对象的状态(暂停到promise对象状态为成功的情况,如果是失败状态,则直接报错) // resolve(value) value就会作为await的返回值返回 // async 函数调用返回的是Promise对象 // 默认是peding状态 // 当async 函数所有代码全部执行完毕,并且没有出错,此时就会变成成功状态 // 当async 函数代码出错了,就会变成失败状态
12.字符串扩展
1. includes(str) 判断是否包含指定的字符串2. startsWith(str) 判断是否以指定字符串开头3. endsWith(str) 判断是否以指定字符串结尾4. repeat(count) 重复指定的次数
13.二进制与八进制数值表示法:二进制用0b,八进制用0o
1. Number.isFinite(i) 判断是否是有限大的数2. Number.isNaN(i) 判断是否是NaN3. Number.isInteger(i) 判断是否是整数4. Number.parseInt(str) 将字符串转换为对应的数值5. Math.trunc(i) 直接去除小数部分
14.数组扩展
1. Array.form(伪数组) 伪数组转真数组2. Array.of(val1,val2,val3)将一些列数值转换为数组3. find(回调) 找出第一个满足条件的元素4. findIndex(回调) 找出第一个满足条件的元素的下标
15.对象扩展
1. Object.is(v1,v2)2. Object.assign(target,obj1,obj2) 将对象的属性复制到目标对象上3. 直接操作__proto__属性 - let obj2={} - obj2.__proto__=obj1
16.拷贝数据
1. 基本数据类型 - 拷贝后悔生成一份新的数据,修改拷贝以后的数据不会影响原数据2. 对象/数组 - 拷贝后不会生成新的数据,而是拷贝的是引用,修改拷贝以后的数据会影响原来的数据3. 拷贝数据的方法: - 直接赋值给一个变量 浅拷贝 - Object.assgin() 浅拷贝 - Array.prototype.concat() 浅拷贝 - Array.pototype.slice() 浅拷贝 - JSON.parse(JSON.stringify()) 深拷贝(深度克隆),拷贝的数据里不能有函数,处理不了4. 浅拷贝(对象/数组) - 特点: 拷贝引用,修改拷贝以后的数据会影响原数据5. 深拷贝(深度克隆) - 特点: 拷贝的时候生成新数据,修改拷贝以后的数据不会影响原数据 - 深度克隆会进行深度的遍历(会用到递归) - 需要进行类型的检测 : typeof 返回数据类型:String,Number,Boolean,Undefined,Object,Function - Object.prototype.toString.call(obj) 返回的是该对象到底是什么类型 - console.log(Object.prototype.toString.call(result)) - 截取获取的真正的数据类型 - console.log(Object.prototype.toString.call(result).slice(8, -1)) - for-in循环 对象(属性名) 数组(下标)
17.Set容器:无序不可重复的多个值的集合体
1. Set()2. Set(array)3. add(value)4. delete(value)5. has(value)6. clear()7. size
18.Map容器: 无序的key不重复的多个key-value的集合体
1. Map()2. Map(array)3. set(key,value) 添加4. get(key)5. delete(key)6. has(key)7. clear()8. size
19.for-of循环可以遍历下面内容:
1. 遍历数组2. 遍历Set3. 遍历Map4. 遍历字符串5. 遍历伪数组
20.ES7
Array.prototype.includes() 判断数组中是否包含指定的value 指数运算符(幂): **
21.ES8
Object.values(对象) 获取对象中所有的属性的值 Object.entries(对象) 把对象转数组
22.ES9
Promise.finally let promise = new Promise((resolve,reject)=>{ console.log('开始执行') resolve('111') console.log('结束执行') }) promise.then((data)=>{ console.log('成功:'+data) }).catch((errorMsg)=>{ console.log('报错啦:'+errorMsg) }).finally(()=>{ console.log('成功失败都会执行的') })
23.总结ES6+面试题
ES6+常用语法列出整体ES6+新语法列表说明: 跟面试官交流ES6, 先快速说出这套列表, 再选择几个常用的/有些难度的/有说头的说, 或者看面试官关注哪方面的1. const与let2. 解构赋值==================1. 字符串的扩展2. 数值的扩展3. 函数的扩展4. 数组的扩展5. 对象的扩展====================6. 类语法7. 模块化语法8. 异步语法9. 新容器语法10. 代理(Proxy)与反射(Reflect)语法===================================const与let- const定义常量, let定义变量- 相对于var - 有块作用域 - 没有变量提升 - 不会添加到window上 - 不能重复声明==============================解构赋值- 解构对象: const {id, name} = this.product- 解构数组: const [count, setCount] = useState() - 形参解构: add ({ id, title }) {}- 引入模块解构: import { getProductList } from '@/api'===================================字符串的扩展- 模板字符串: 我是${name}, 今年${age}- 方法: includes() / startsWith() / endswith()数值的扩展- 完善二进制(0b)与八进制(0o)表示- 给Math添加方法: parseInt()与parseFloat() (原本window上有)- 指数计算: **函数的扩展- 箭头函数 - 没有自己的this, 使用外部作用域中的this, 不能通过bind来绑定this - 不能通过new来创建实例对象 - 内部没有arguments, 可以通过rest参数来代替- 形参默认值: fn (a=2, b={}) {}- rest参数: fn (a, ...args) {} / fn (a, ...args) {} fn(1, 2, 3, 4)数组的扩展- 扩展运算符 - 浅拷贝数组: const arr2 = [...arr] - 合并多个数组: const arr3 = [...arr1, ...arr2]- 静态方法 - Array.from(): 将类数组对象和可遍历对象转为真数组 - Array.from(new Set(arr)) - [...new Set(arr)] - Array.of(1, 2, 3): 将一组值,转换为数组- 实例方法 - find() / findIndex(): 查找匹配的元素或下标 - arr.flat(): 将多维数组转为一维数组(也称为: 数组扁平化)对象的扩展- 扩展运算符 - 浅拷贝对象: const obj2 = {...obj1} - 合并多个对象: const obj3 = {...obj1, ...obj2}- 属性/方法的简洁写法: {name, getName () {}}- 遍历内部属性 - for..of: 遍历对象及其原型链上所有属性 - Object.keys(obj): 得到对象自身可遍历的所有属性名的数组- 静态方法: - Object.is(value1, value2): 判断2个值是否完全一样 - Object.assign(target, ...sources): 将后面任意多个对象合并到target对象上 类语法- class- extends- constructor- super() / super.xxx()- static模块化语法- export - export default value- import: 静态导入, 合并一起打包- import(): 动态导入, 拆分打包, 用于懒加载 const Home = () => import('./views/Home.vue') import('./views/Home.vue').then((module) => { // 使用module块 module.default module.xxx })异步语法- Promise- async 函数- await 表达式新容器语法- Map- Set代理(Proxy)与反射(Reflect)语法- Proxy- Reflect面试可说的: ES6常用语法- const与let- 箭头函数- 解构赋值- 形参默认值- rest/剩余参数- 类语法: class / extends / constructor / static /super- 扩展运算符: ...- 模板字符串- 异步语法: promise / async & await- 对象的属性与方法简写- set / map- 模块化语法: export / default / import / import()
/*
绑定事件监听的通用函数(不带委托)
*/
function bindEvent1 (ele, type, fn) {
ele.addEventListener(type, fn)
}
/*
绑定事件监听的通用函数(带委托)
*/
function bindEvent2(ele, type, fn, selector) {
ele.addEventListener(type, event => {
// 得到发生事件的目标
const target = event.target
if (selector) {
// 如果元素被指定的选择器字符串选择, 返回true; 否则返回false。
if (target.matches(selector)) {
// 委托绑定调用
fn.call(target, event)
}
} else {
// 普通绑定调用
fn.call(ele, event)
// fn(event) // this不对
}
})
}
-
-
bindEvent2(ul, 'click', (event) => {}, 'li')
bindEvent2(ul, 'click', (event) => {})
/*
xhr + promise 封装一个异步ajax请求的通用函数 简洁版
*/
function ajax(url) {
return new Promise((resolve, reject) => {
// 创建一个XHR对象
const xhr = new XMLHttpRequest()
// 初始化一个异步请求(还没发请求)
xhr.open('GET', url, true)
xhr.onreadystatechange = function () {
/*
ajax引擎得到响应数据后
将xhr的readyState属性指定为4
将响应数据保存在response / responseText属性上
调用此回调函数
*/
// 如果状态值不为4, 直接结束(请求还没有结束)
if (xhr.readyState !== 4) {
return
}
// 如果响应码在200~~299之间, 说明请求都是成功的
if (xhr.status>=200 && xhr.status<300) {
// 指定promise成功及结果值
resolve(JSON.parse(xhr.responseText))
} else { // 请求失败了
// 指定promise失败及结果值
reject(new Error('request error staus '+ request.status))
}
}
xhr.send(null)
})
}
/*
xhr + promise 封装一个异步ajax请求的通用函数 加强版
返回值: promise
参数为配置对象
url: 请求地址
params: 包含所有query请求参数的对象
data: 包含所有请求体参数数据的对象
method: 为请求方式
*/
function axios({url, params={}, data={}, method='GET'}) {
// 返回一个promise对象
return new Promise((resolve, reject) => {
// 创建一个XHR对象
const request = new XMLHttpRequest()
// 根据params拼接query参数
let queryStr = Object.keys(params).reduce((pre, key) => {
pre += `&${key}=${params[key]}`
return pre
}, '')
if (queryStr.length>0) {
queryStr = queryStr.substring(1)
url += '?' + queryStr
}
// 请求方式转换为大写
method = method.toUpperCase()
// 初始化一个异步请求(还没发请求)
request.open(method, url, true)
// 绑定请求状态改变的监听
request.onreadystatechange = function () {
// 如果状态值不为4, 直接结束(请求还没有结束)
if (request.readyState !== 4) {
return
}
// 如果响应码在200~~299之间, 说明请求都是成功的
if (request.status>=200 && request.status<300) {
// 准备响应数据对象
const responseData = {
data: JSON.parse(request.response),
status: request.status,
statusText: request.statusText
}
// 指定promise成功及结果值
resolve(responseData)
} else { // 请求失败了
// 指定promise失败及结果值
const error = new Error('request error staus '+ request.status)
reject(error)
}
}
// 如果是post/put请求
if (method==='POST' || method==='PUT' || method==='DELETE') {
// 设置请求头: 使请求体参数以json形式传递
request.setRequestHeader('Content-Type', 'application/json;charset=utf-8')
// 包含所有请求参数的对象转换为json格式
const dataJson = JSON.stringify(data)
// 发送请求, 指定请求体数据
request.send(dataJson)
} else {// GET请求
// 发送请求
request.send(null)
}
})
}
前台:
// 使用cors, 允许跨域, 且允许携带跨域cookie
app.use(function (req, res, next) {
// console.log('----')
// 允许跨域的地址
res.header('Access-Control-Allow-Origin', 'http://localhost:5500') // 不要是*
// 允许携带凭证(也就是cookie)
res.header('Access-Control-Allow-Credentials', 'true')
// 允许跨域的请求头
res.set("Access-Control-Allow-Headers", "Content-Type")
// 放行
next()
})
axios.defaults.withCredentials = true // 允许携带cookie
xhr.withCredentials = true
axios(url)
axios({
method: '',
url: '', // 如果有params参数必须拼接在url中
params: {}, // query参数
data: {} // 请求体参数
})
axios.get(url, {配置})
axios.post(url, data, {配置})
axios.put(url, data, {配置})
axios.delete(url, {配置})
const service = axios.create({
baseURL: '',
timeout: 20000,
})
service.interceptors.request.use((config) => {
// 添加请求头
config.headers['token'] = token值
return config // 必须返回config
})
xhr.send()
service.interceptors.response.use(
response => {
// return response
return response.data
},
error => {
}
)
service({}).then(data => {
})
Promise.resolve(config)
.then((config) => { // 请求拦截器的回调
return config
})
.then((config) => { // 用来发ajax的回调
return new Promise((resolve, reject) => {
// 根据config使用xhr发请求
resolve(response)
})
})
.then( // 响应拦截器
(response) => {
return response.data
},
(error) => {
}
)
.then((data) => { // 最终发具体请求的成功回调
})
配置通用的基础路径和超时
显示请求进度条
成功返回的数据不再是response, 而直接是响应体数据response.data
统一处理请求错误, 具体请求也可以选择处理或不处理
每个请求自动携带userTempId的请求头: 在请求拦截器中实现
如果当前有token, 自动携带token的请求头
对token过期的错误进行处理
import axios from 'axios'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import store from '@/store'
import router from '@/router'
NProgress.configure({ showSpinner: false }) // 隐藏右侧的旋转进度条
// 创建instance
const instance = axios.create({
// baseURL: 'http://182.92.128.115/api',
baseURL: '/api',
timeout: 20000
})
// 指定请求拦截器
instance.interceptors.request.use(config => {
// 显示进度条: 请求拦截器回调
NProgress.start()
/* 每个请求自动携带userTempId的请求头: 在请求拦截器中实现 */
const userTempId = store.state.user.userTempId
config.headers.userTempId = userTempId
/* 6. 如果当前有token, 自动携带token的请求头 */
const token = store.state.user.token
if (token) {
config.headers.token = token
}
return config // 必须返回config
})
// 指定响应拦截器
instance.interceptors.response.use(
response => { // 成功的回调
// 结束进度条: 响应拦截器回调
NProgress.done()
// 成功返回的数据不再是response, 而直接是响应体数据response.data
return response.data
},
async error => { // 失败的回调
// 结束进度条: 响应拦截器回调
NProgress.done()
// 统一处理请求错误, 具体请求也可以选择处理或不处理
// alert('请求出错: ' + error.message||'未知错误')
// 取出响应对象
const { response } = error
// 如果是请求处理出错
if (response && response.status) {
// 401说明token非法
if (response.status === 401) {
// 如果当前没在登陆页
if (router.currentRoute.path!=='/login') {
// 分发action去清除用户token信息
await store.dispatch('logout')
// 跳转到登陆页面
router.replace('/login')
// 提示
message.error('登陆已过期, 请重新登陆')
}
} else {
message.error('请求出错: ' + error.message||'未知错误')
}
} else if (!response) { // 网络连接不上服务器
message.error('您的网络发生异常,无法连接服务器')
}
// throw error
return Promise.reject(error) // 将错误向下传递
}
)
// 向外暴露instance
export default instance
Restless API
Restful API
测试: 可以使用json-server快速搭建模拟的rest api 接口
注意: session后台数据存储
http://www.baidu.com
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0aN5S2iH-1647229293225)(.\images\TCP三次握手_通俗版.jpeg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YXCZS15V-1647229293226)(.\images\四次挥手_通俗版.jpeg)]
/*
自定义函数对象的call方法
*/
function call (fn, obj, ...args) {
// 如果传入的是null/undefined, this指定为window
if (obj===null || obj===undefined) {
obj = obj || window
}
// 给obj添加一个方法: 属性名任意, 属性值必须当前调用call的函数对象
obj.tempFn = fn
// 通过obj调用这个方法
const result = obj.tempFn(...args)
// 删除新添加的方法
delete obj.tempFn
// 返回函数调用的结果
return result
}
/*
自定义函数对象的apply方法
*/
function apply (fn, obj, args) {
// 如果传入的是null/undefined, this指定为window
if (obj===null || obj===undefined) {
obj = obj || window
}
// 给obj添加一个方法: 属性名任意, 属性值必须当前调用call的函数对象
obj.tempFn = fn
// 通过obj调用这个方法
const result = obj.tempFn(...args)
// 删除新添加的方法
delete obj.tempFn
// 返回函数调用的结果
return result
}
/*
自定义函数对象的bind方法
重要技术:
高阶函数
闭包
call()
三点运算符
*/
function bind (fn, obj, ...args) {
if (obj===null || obj===undefined) {
obj = obj || window
}
return function (...args2) {
return call(fn, obj, ...args, ...args2)
}
}
/*
实现函数节流的函数
*/
function throttle(callback, delay) {
let start = 0 // 必须保存第一次点击立即调用
return function (event) { // 事件回调函数
// this是发生事件的dom元素
console.log('throttle 事件')
const current = Date.now()
if (current - start > delay) { // 从第2次点击开始, 需要间隔时间超过delay
callback.call(this, event)
// 将当前时间指定为start, ==> 为后面的比较做准备
start = current
}
}
}
/*
实现函数防抖的函数
*/
function debounce(callback, delay) {
return function (event) {
console.log('debounce 事件...')
// 清除待执行的定时器任务
if (callback.timeoutId) {
clearTimeout(callback.timeoutId)
}
// 每隔delay的时间, 启动一个新的延迟定时器, 去准备调用callback
callback.timeoutId = setTimeout(() => {
callback.call(this, event)
// 如果定时器回调执行了, 删除标记
delete callback.timeoutId
}, delay)
}
}
/*
方法1: 利用forEach()和indexOf()
说明: 本质是双重遍历, 效率差些
*/
function unique1 (array) {
const arr = []
array.forEach(item => {
if (arr.indexOf(item)===-1) { // 内部在遍历判断出来的
arr.push(item)
}
})
return arr
}
/*
方法2: 利用forEach() + 对象容器
说明: 只需一重遍历, 效率高些
*/
function unique2 (array) {
const arr = []
const obj = {}
array.forEach(item => {
if (!obj.hasOwnProperty(item)) {// 不用遍历就能判断出是否已经有了
obj[item] = true
arr.push(item)
}
})
return arr
}
/*
方法3: 利用ES6语法
1). from + Set
2). ... + Set
说明: 编码简洁
*/
function unique3 (array) {
// return Array.from(new Set(array))
return [...new Set(array)]
}
/*
数组扁平化: 取出嵌套数组(多维)中的所有元素放到一个新数组(一维)中
如: [1, [3, [2, 4]]] ==> [1, 3, 2, 4]
*/
/*
方法一: 递归 + reduce() + concat()
*/
function flatten1 (array) {
return array.reduce((pre, item) => {
if (Array.isArray(item) && item.some((cItem => Array.isArray(cItem)))) {
return pre.concat(flatten1(item))
} else {
return pre.concat(item)
}
}, [])
}
/*
方法二: ... + some() + concat()
*/
function flatten2 (array) {
let arr = [].concat(...array)
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
return arr
}
/*
深度克隆
1). 大众乞丐版
问题1: 函数属性会丢失
问题2: 循环引用会出错
2). 面试基础版本
解决问题1: 函数属性还没丢失
3). 面试加强版本
解决问题2: 循环引用正常
4). 面试加强版本2(优化遍历性能)
数组: while | for | forEach() 优于 for-in | keys()&forEach()
对象: for-in 与 keys()&forEach() 差不多
*/
const obj = {
a: {
},
b: [],
fn: function (){}
}
obj.a.c = obj.b
obj.b[0] = obj.a
/*
1). 大众乞丐版
问题1: 函数属性会丢失
问题2: 循环引用会出错
*/
export function deepClone1(target) {
return JSON.parse(JSON.stringify(target))
}
/*
获取数据的类型字符串名
*/
function getType(data) {
return Object.prototype.toString.call(data).slice(8, -1) // -1代表最后一位
// [object Array] ===> Array [object Object] ==> Object
}
/*
2). 面试基础版本
解决问题1: 函数属性还没丢失
*/
function deepClone2(target) {
const type = getType(target)
if (type==='Object' || type==='Array') {
const cloneTarget = type === 'Array' ? [] : {}
for (const key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = deepClone2(target[key])
}
}
return cloneTarget
} else {
return target
}
}
/*
3). 面试加强版本
解决问题2: 循环引用正常
*/
function deepClone3(target, map = new Map()) {
const type = getType(target)
if (type==='Object' || type==='Array') {
// 从map容器取对应的clone对象
let cloneTarget = map.get(target)
// 如果有, 直接返回这个clone对象
if (cloneTarget) {
return cloneTarget
}
cloneTarget = type==='Array' ? [] : {}
// 将clone产生的对象保存到map容器
map.set(target, cloneTarget)
for (const key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = deepClone3(target[key], map)
}
}
return cloneTarget
} else {
return target
}
}
/*
4). 面试加强版本2(优化遍历性能)
数组: while | for | forEach() 优于 for-in | keys()&forEach()
对象: for-in 与 keys()&forEach() 差不多
*/
function deepClone4(target, map = new Map()) {
const type = getType(target)
if (type==='Object' || type==='Array') {
let cloneTarget = map.get(target)
if (cloneTarget) {
return cloneTarget
}
if (type==='Array') {
cloneTarget = []
map.set(target, cloneTarget)
target.forEach((item, index) => {
cloneTarget[index] = deepClone4(item, map)
})
} else {
cloneTarget = {}
map.set(target, cloneTarget)
Object.keys(target).forEach(key => {
cloneTarget[key] = deepClone4(target[key], map)
})
}
return cloneTarget
} else {
return target
}
}
/*
自定义new工具函数
语法: newInstance(Fn, ...args)
功能: 创建Fn构造函数的实例对象
实现: 创建空对象obj, 调用Fn指定this为obj, 返回obj
*/
function newInstance(Fn, ...args) {
// 创建一个新的对象
const obj = {}
// 执行构造函数
const result = Fn.apply(obj, args) // 相当于: obj.Fn()
// 如果构造函数执行的结果是对象, 返回这个对象
if (result instanceof Object) {
return result
}
// 如果不是, 返回新创建的对象
obj.__proto__.constructor = Fn // 让原型对象的构造器属性指向Fn
return obj
}
/*
自定义instanceof工具函数:
语法: myInstanceOf(obj, Type)
功能: 判断obj是否是Type类型的实例
实现: Type的原型对象是否是obj的原型链上的某个对象, 如果是返回true, 否则返回false
*/
function myInstanceOf(obj, Type) {
// 得到原型对象
let protoObj = obj.__proto__
// 只要原型对象存在
while(protoObj) {
// 如果原型对象是Type的原型对象, 返回true
if (protoObj === Type.prototype) {
return true
}
// 指定原型对象的原型对象
protoObj = protoObj.__proto__
}
return false
}
/*
1. 字符串倒序: reverseString(str) 生成一个倒序的字符串
2. 字符串是否是回文: palindrome(str) 如果给定的字符串是回文,则返回 true ;否则返回 false
3. 截取字符串: truncate(str, num) 如果字符串的长度超过了num, 截取前面num长度部分, 并以...结束
*/
/*
1. 字符串倒序: reverseString(str) 生成一个倒序的字符串
*/
function reverseString(str) {
// return str.split('').reverse().join('')
// return [...str].reverse().join('')
return Array.from(str).reverse().join('')
}
/*
2. 字符串是否是回文: palindrome(str) 如果给定的字符串是回文,则返回 true ;否则返回 false
*/
function palindrome(str) {
return str === reverseString(str)
}
/*
3. 截取字符串: truncate(str, num) 如果字符串的长度超过了num, 截取前面num长度部分, 并以...结束
*/
function truncate(str, num) {
return str.length > num ? str.slice(0, num) + '...' : str
}
/*
冒泡排序的方法
*/
function bubbleSort (array) {
// 1.获取数组的长度
var length = array.length;
// 2.反向循环, 因此次数越来越少
for (var i = length - 1; i >= 0; i--) {
// 3.根据i的次数, 比较循环到i位置
for (var j = 0; j < i; j++) {
// 4.如果j位置比j+1位置的数据大, 那么就交换
if (array[j] > array[j + 1]) {
// 交换
// const temp = array[j+1]
// array[j+1] = array[j]
// array[j] = temp
[array[j + 1], array[j]] = [array[j], array[j + 1]];
}
}
}
return arr;
}
/*
选择排序的方法
*/
function selectSort (array) {
// 1.获取数组的长度
var length = array.length
// 2.外层循环: 从0位置开始取出数据, 直到length-2位置
for (var i = 0; i < length - 1; i++) {
// 3.内层循环: 从i+1位置开始, 和后面的内容比较
var min = i
for (var j = min + 1; j < length; j++) {
// 4.如果i位置的数据大于j位置的数据, 记录最小的位置
if (array[min] > array[j]) {
min = j
}
}
if (min !== i) {
// 交换
[array[min], array[i]] = [array[i], array[min]];
}
}
return arr;
}
/*
插入排序的方法
*/
function insertSort (array) {
// 1.获取数组的长度
var length = array.length
// 2.外层循环: 外层循环是从1位置开始, 依次遍历到最后
for (var i = 1; i < length; i++) {
// 3.记录选出的元素, 放在变量temp中
var j = i
var temp = array[i]
// 4.内层循环: 内层循环不确定循环的次数, 最好使用while循环
while (j > 0 && array[j - 1] > temp) {
array[j] = array[j - 1]
j--
}
// 5.将选出的j位置, 放入temp元素
array[j] = temp
}
return array
}
cd /etc/
ls
cd nginx
vi nginx.conf
在nginx.conf里面写nginx的配置文件,有几个项目配置几个
改server里的root,指向打包文件的地址
记得备份
cp nginx.conf nginx.conf.copy
//这个server配80端口监听前台项目
server{
listen 80 default_server;
listen [::]:80 default_server’
server_name _;
root /root/ly/www/gulishop-client/dist; (dist地址)
# Load configuration files for the default server block;
include /etc/nginx/default.d/*.conf;
location / { //访问的 / 转交到哪儿去
root /root/ly/www/gulishop/dist; (访问这个服务器的时候根目录在哪里)
index index.html (访问这个服务器的时候根目录下的哪个文件)
try_files $uri $uri/ /index.html; (配这句话可以解决history模式找不见网页报404的问题)
}
//反向代理
location /api{ //访问的 /api 转交到哪儿去 脚手架集成的webpack赔了proxy但是是开发环境,这里再配proxy是生产环境
proxy_pass http://182.92.128.115;
}
error_page 404 /404.html;
location = /40x.html{
}
error_page 500 502 503 504 /50x.html;
location = /50x.html{
}
}
//这个server配80端口监听前台项目
server{
listen 8080;
server_name localhost;
root /root/ly/www/gulishop-client/dist; (dist地址)
location / {
root /root/ly/www/gulishop/dist;
index index.html
try_files $uri $uri/ /index.html;
}
//然后重启
service nginx restart
然后就能看到项目看了
Vue实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载DOM->渲染、更新->渲染、卸载等一系列过程,我们称这是Vue的生命周期。
它的生命周期中有多个事件钩子,让我们在控制整个vue实例的过程时更容易形成好的逻辑
它可以总共分为8个阶段:创建前/后,载入前/后,更新前/后,销毁前/销毁后
beforeCreate,created,beforeMount,mounted
DOM渲染在mounted中就已经完成了
不能直接操作,但是可以通过nextTick来进行操作
1.beforecreate:可以在加个loading事件,在加载实例时触发,在这个阶段无法通过 Vue的实例去访问data中的数据和methods中的方法。
2.created:初始化完成时的事件写在这里,如在这里结束loading事件,异步请求也适宜在这里调用。这个阶段可以通过 Vue的实例去访问data中的数据和methods中的方法。因为数据代理和数据监控在这阶段已经完成
3.mounted:挂载元素,获取到dom节点
4.updated:如果对数据统一处理,在这里写上相应函数5.beforeDestroy:可以一个确认停止事件的确认框
6.nextTick:更新数据后立即操作dom
v-show是css切换,v-if是完整的销毁和重新创建
v-show 仅仅控制元素的显示方式,将 display 属性在 block 和 none 来回切换;而v-if会控制这个 DOM 节点的存在与否。当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。
v-model:一般用在表达输入,很轻松的实现表单控件和数据的双向绑定
v-html:更新元素的innerHTML
v-show与v-if:条件渲染,注意二者区别
v-on:click:可以简写为@click,@绑定一个事件。如果事件触发了,就可以指定事件的处理函数
v-for:基于源数据多次渲染元素或模板
v-bind:当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM语法
v-bind:title=”msg”简写:title=“msg”
对象方法v-bind:class="{atguigu1:false,atguigu2:false,}”
数组方法v-bind:class="[class1,class2]"要绑定多个样式,个数确定,名字也确定,但不确定用不用
行内v-bind:style="{color:color,fontSize:fontSize+'px'}”
computed
computed是计算属性,也就是计算值,它更多用于计算值的场景
computed具有缓存性,computed的值在getter执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取computed的值时重新调用对应的getter来计算
computed适用于计算比较消耗性能的计算场景
watch
watch更多的是[观察]的作用,类似于某些数据的监听回调,用于观察props $emit或者本组件的值,当数据变化时来执行回调进行后续操作
无缓存性,页面重新渲染时值不变化也会执行
当我们要进行数值计算,而且依赖于其他数据,那么把这个数据设计为computed
如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化。
.native:给某个组件的根元素上监听一个事件,之后添加 .native 修饰符就会起作用了
.once 事件只能用一次,无论点击几次,执行一次之后都不会再执行
.prevent 阻止默认行为,点击a标签href可以打开相应的链接
.stop 阻止事件冒泡
.capture 改变冒泡顺序,先执行带有capture关键字的方法
.self 只有元素本身触发时才触发方法,就是只有点击元素本身才会触发(变相的阻止了冒泡)
.lazy lazy这个修饰符会在光标离开input框才会更新数据(v-model.trim)
.number 先输入数字就会限制输入只能是数字,先字符串就相当于没有加number
在DOM事件的回调函数中传入参数$event,可以获取到该事件的事件对象
根据通信的2个组件间的关系来选择一种通信方式
父子
props
vue自定义事件
v-model
.sync
$ref, $children与$parent
插槽 ==> 作用域插槽
祖孙
$attrs与$listeners
provide与inject
兄弟或其它/任意
全局事件总线
Vuex
1). 实现父向子通信: 属性值是非函数
2). 实现子向父通信: 属性值是函数
应用: 最基本, 用得最多的方式
1). 用来实现子组件向父组件通信
2). 相关语法:
父组件中绑定自定义事件监听:
子组件中分发事件
this.$emit('eventName', data)
应用: elment-ui的组件的事件监听语法都用的是自定义事件
我们项目中的组件也用了不少自定义事件
1). 实现任意组件间通信
2). 编码:
将入口js中的vm作为全局事件总线对象:
beforeCreate() {
Vue.prototype.$bus = this
}
分发事件/传递数据的组件: this.$bus.$emit('eventName', data)
处理事件/接收数据的组件: this.$bus.$on('eventName', (data) => {})
应用: 前台项目中使用全局事件总线
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G42Z23G6-1647229293232)(C:/Users/Administrator/Desktop/笔记/images/component&vm.png)]
1). 实现父子之间相互通信/同步
2). 组件标签上的v-model的本质: 动态value属性与自定义input监听来接收子组件分发的数据更新父组件数据
父组件:
子组件:
props: ['value']
应用: element-ui中的表单项相关组件都用了v-model: Input / Select / Checkbox / Radio
1). 实现父子之间相互通信/同步(在原本父向子的基础上增加子向父)
2). 组件标签的属性上使用.sync的本质: 通过事件监听来接收子组件分发过来的数据并更新父组件的数据
父组件:
data () {
return {
total: 1000
}
},
子组件:
props: ['money']
应用:
element-ui在有显示隐藏的组件上: Dialog / Drawer
1). $attrs
实现当前组件的父组件向当前组件的子组件通信
它是包含所有父组件传入的标签属性(排除props声明, class与style的属性)的对象
使用: 通过 v-bind="$attrs" 将父组件传入的n个属性数据传递给当前组件的子组件
2). $listeners
实现当前组件的子组件向当前组件的父组件通信
$listeners是包含所有父组件传入的自定义事件监听名与对应回调函数的对象
使用: 通过v-on="$listeners" 将父组件绑定给当前组件的事件监听绑定给当前组件的子组件
应用: 利用它封装了一个自定义的带hover文本提示的el-button
1). $refs 实现父组件向指定子组件通信 $refs是包含所有有ref属性的标签对象或组件对象的容器对象 使用: 通过 this.$refs.child 得到子组件对象, 从而可以直接更新其数据或调用其方法更新数据 2). $children 实现父组件向多个子组件通信 $children是所有直接子组件对象的数组 使用: 通过this.$children 遍历子组件对象, 从而可以更新多个子组件的数据 3). $parent 实现子组件向父组件通信 $parent是当前组件的父组件对象 使用: 通过this.$parent 得到父组件对象, 从而可以更新父组件的数据 应用: 在后台管理项目中使用了$refs
1). 实现祖孙组件间直接通信 2). 使用 在祖组件中通过provide配置向后代组件提供数据 在后代组件中通过inject配置来声明接收数据 3). 注意: 不太建议在应用开发中使用, 一般用来封装vue插件 provide提供的数据本身不是响应式的 ==> 父组件更新了数据, 后代组件不会变化 provide提供的数据对象内部是响应式的 ==> 父组件更新了数据, 后代组件也会变化 应用: element-ui中的Form组件中使用了provide和inject
1). 实现任意组件间通信
2). Vuex 是一个专为 Vue 应用程序设计的管理多组件共享状态数据的 Vue 插件
任意组件都可以读取到Vuex中store的state对象中的数据
任意组件都可以通过dispatch()或commit()来触发store去更新state中的数据
一旦state中的数据发生变化, 依赖于这些数据的组件就会自动更新
应用: 前台和后台项目都有用vuex管理组件数据
12.插槽 ==> 作用域插槽slot-scope
1). 实现父组件向子组件传递标签内容
2). 什么情况下使用作用域插槽?
父组件需要向子组件传递标签结构内容
但决定父组件传递怎样标签结构的数据在子组件中
3). 编码:
子组件:
父组件:
{{$index+1}}
{{row.text}}
应用: element-ui中的Table组件
vue组件默认是类组件,也可以使用函数组件
什么是函数式组件
没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法,它只是一个接受一些 prop 的函数。简单来说是 `一个无状态和无实例的组件```
函数组件****的写法
常规组件写法
Vue.component('my-component', {
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function(createElement, context) {
// ...
}
})
.vue 单文件组件写法
...
虽然速度和性能方面是函数式组件的优势、但不等于就可以滥用,所以还需要根据实际情况选择和权衡。比如在一些展示组件。例如, buttons
, tags
, cards
,或者页面是静态文本,就很适合使用函数式组件。
三种方式
路径后面带参数 使用:to="/hello/10" 配置: componenta/:id
params传参
query传参
一般不会同级使用 ,v-for比v-if优先级高,所以使用的话,每次v-for都会执行v-if,造成不必要的计算,影响性能,尤其是当之需要渲染很小一部分的时候。
1. 虚拟DOM不会进行排版与重绘操作 虚拟DOM就是把真实DOM转换为Javascript代码
2. 虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分(注意!),最后并在真实DOM中进行排版与重绘,减少过多DOM节点排版与重绘损耗
3. 真实DOM频繁排版与重绘的效率是相当低的
4. 虚拟DOM有效降低大面积(真实DOM节点)的重绘与排版,因为最终与真实DOM比较差异,可以只渲染局部(同2)
5. 使用虚拟DOM的损耗计算:
总损耗 = 虚拟DOM增删改 + (与Diff算法效率有关)真实DOM差异增删改 + (较少的节点)排版与重绘
6. 直接使用真实DOM的损耗计算:
总损耗 = 真实DOM完全增删改 + (可能较多的节点)排版与重绘
7. 总之,一切为了减弱频繁的大面积重绘引发的性能问题,不同框架不一定需要虚拟DOM,关键看框架是否频繁会引发大面积的DOM操作
什么是混入 哪里用到过,将公共的方法或者数据进行提取,放到mixin中,然后组件中直接调用
hash与history 两种路由模式
hash模式的工作原理是hashchange事件,可以在window监听hash的变化。我们在url后面随便添加一个#xx触发这个事件。
hash模式是显示:真实地址,可直接访问
history模式的地址栏显示更接近正常的地址。 但是不能直接访问
.
window.onhashchange = function(event){
console.log(event);
}
当hash的值发生改变,我们根据hash的值,动态改变数据
尽管浏览器没有请求服务器,但是页面状态和url已经关联起来了,这就是所谓的前端路由,单页应用的标配
vue中的过滤器分为两种:局部过滤器和全局过滤器
过滤器主要用在v-html指令以及插值表达式中,用来过滤数据,改变数据的显示方式等
当有局部和全局两个名称相同的过滤器时候,会以就近原则进行调用,即:局部过滤器优先于全局过滤器被调用!
一个表达式可以使用多个过滤器。过滤器之间需要用管道符“|”隔开。其执行顺序从左往右
子组件
this.$emit('update:title', newTitle)
父组件
newTitle赋值给doc.,title
手动利用HTML5的本地存储
1、vuex的state在localStorage或sessionStorage中取值;
2、在mutations中,定义的方法里对vuex的状态操作的同时对存储也做对应的操作。
这样state就会和存储一起存在并且与vuex同步
利用vuex-persistedstate插件
插件的原理其实也是结合了存储方式,只是统一的配置就不需要手动每次都写存储方法。
安装
npm install vuex-persistedstate --save
引入及配置:在store下的index.js中
import createPersistedState from "vuex-persistedstate"
Const store =newVuex.Store({
plugins: [createPersistedState({
默认存储到localStorage
storage:window.sessionStorage
//默认存储所有的state数据
reducer(val) {
return {
// 只储存state中的assessmentData
assessmentData: val.assessmentData
}
}
})]
})
可以通过插件来实现
定义插件
// 创建插件
let chajian = {
//这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:
//插件里面所有的内容都要放到install里面
install(Vue, options) {
// 1. 添加全局方法或 property hello添加给了Vue这个class
Vue.hello = function() {
// 逻辑...
console.log("插件里面的hello方法")
}
/ 2. 添加全局资源
Vue.directive('zhi', {
bind(el, binding, vnode, oldVnode) {
// 逻辑...
console.log("全局的指令..zhi")
}
})
// 3. 注入组件选项
Vue.mixin({
created: function() {
// 逻辑...
},
data(){
return {
app:"一剪梅"
}
}
})
// 4. 添加实例方法
Vue.prototype.$good = function(methodOptions) {
// 逻辑...
console.log("very good")
}
}
}
使用插件
Vue.use(chajian);
调用插件内容
{{app}}
调用指令
{{$good() }}
调用全局方法,必须用Vue来调用
mounted() {
Vue.hello(); //只能通过Vue来调用
}
```
# 25. vue-loader是什么?用途有哪些?
vue-loader是一个加载器,主要作用解析和转换.vue文件。提取出其中的逻辑代码 script,样式代码style,以及HTML 模板template,再分别把他们交给对应的loader去处理。
# 26. axios与ajax的区别
Ajax 指的是 XMLHttpRequest(XHR), 最早出现的发送后端请求技术,隶属于原始js中,核心使用XMLHttpRequest对象,多个请求之间如果有先后关系的话,就会出现回调地狱。 后来使用jquery的ajax方法可以解决,他里面也能对跨域的解决方案jsonp, vue里面都是使用ajax进行异步请求 axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范,它本身具有以下特征: 1.从浏览器中创建 XMLHttpRequest 2.支持 Promise API 3.客户端支持防止CSRF 4.提供了一些并发请求的接口(重要,方便了很多的操作) 5.从 node.js 创建 http 请求 6.拦截请求和响应 7.转换请求和响应数据 8.取消请求 9.自动转换JSON数据
# 27. jquery与ajax
了解jquery的ajax吗?你为什么不用jquery的ajax呢?
1.本身是针对MVC的编程,不符合现在前端MVVM的浪潮
2.基于原生的XHR开发,XHR本身的架构不清晰。
3.JQuery整个项目太大,单纯使用ajax却要引入整个JQuery非常的不合理(采取个性化打包的方案又不能享受CDN服务)
4.不符合关注分离(Separation of Concerns)的原则
5.配置和调用方式非常混乱,而且基于事件的异步模型不友好。
# 28. vue特效怎么做
原理: 为内容添加transition标签,组件在进入,以及离开,都会动态添加不同的class的值, 进入的时候有fade-to,fade-in等,离开的时候有leave-active,leave-to , 我们为不同的class设置不同的样式,就可以添加对应过渡效果. 具体实现: 结合Animate.csc或者Velocity.js一起使用
# 29. 动态组件
可以在一个地方,动态展示不同的组件
# 30. 双向绑定的原理
在 Vue 2.x 中,利⽤的是 Object.defineProperty 去劫持对象的访问器(Getter、Setter),
当对象属性值发⽣变化时可获取变化,然后根据变化来作后续响应;
# 31. 对mvc和mvvm的理解
MVC: Controller负责将Model的数据用View显示出来
MVVM: 要分成model层,view层,viewModel层
MVVM 的核心是 ViewModel 层,它就像是一个中转站(value converter),
负责转换 Model 中的数据对象来让数据变得更容易管理和使用,
该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,
起呈上启下作用。View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,
由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,
它是前后端分离方案实施的最重要一环。
# 32. Vue3.0新特性
1.创建实例,使用createApp,不用new Vue
2.setup组件选项,在创建组件之前执行,在 setup 选项中没有 this。这意味着,除了 props 之外,你将无法访问组件中声明的任何属性——本地状态、计算属性或方法。
3.在setup中使用钩子需要从vue中引入才能使用
4.数据和方法要写在return里面
5.可以兼容2版本的写法
6.可以在script标签上添加setup,直接使用compositionAPI就可以直接导出变量或者方法,或者钩子等等,不用显示的导出组件
7.可以支持在style里面引用组件里面的变量
8.style里面的scope里面可以有选择的改变全局样式,插槽样式或者当前文件,里面只能当前文件
# 33.nextTick
因为vue的数据更新是异步的,通过nextTick可以在数据更新之后进行回调
# 34.keep-alive
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染
缓存组件的状态,当组件的内容切换之后,还能退回之前的状态 里面可以设置include表示哪些组件要缓存,exclude表示哪些不适用缓存, max设置最大缓存的组件实例,会销毁最久没被使用的实例
keep-alive包含的组件/路由中,会多出两个生命周期的钩子
activated 与 deactivated。
进入组件执行activated,不会执行beforeCreate,beforeMounte
离开组件执行deactivated,不会执行destroy
# 35. vue调用微信sdk的流程
\1. 安装weixin-js-sdk 然后引入到vue里面去
\2. 调用后台提供的接口初始化SDK
\3. 初始化完成就可以后端传url去调用了wx里的一些功能了
# 36. 用过哪些vue的ui组件
Elementui 、iview 、mintui 、layui
# 37. vue项目首页加载图表和地图插件很慢怎么解决
1、 可以使用组件懒加载 vue-lazy-component
2、 组件异步加载(先给需要加载的组件v-if=“false”然后在created里用一个延时器设置为true,利用了v-if的惰性)
# 38. vue和react的区别
虽然Vue和React两者在定位上有一些交集,但差异也是很明显的。
1、 Vue 使用的是 web 开发者更熟悉的模板与特性 React 的特色在于函数式编程的理念和丰富的技术选型
2、 Vue更加注重web开发者的习惯 React更偏向于原生开发
3、 Vue跟React的最大区别在于数据的reactivity,就是反应式系统上。Vue提供反应式的数据,当数据改动时,界面就会自动更新,而React里面需要调用方法SetState
# 39. vue组件中的key有什么用
key的作用主要是为了高效的更新虚拟DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
# 40. vue组件的scoped属性的作用
在style标签上添加scoped属性,以表示它的样式作用于当下的模块,很好的实现了样式私有化的目的;
但是也得慎用:样式不易(可)修改,而很多时候,我们是需要对公共组件的样式做微调的;
解决办法:
①:使用混合型的css样式:(混合使用全局跟本地的样式)
②:深度作用选择器(>>>)如果你希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>> 操作符:
# 41. vue更新数组时触发视图更新的方法
push() pop() shift() unshift() splice() sort() reverse()
# 42. vue等单页面应用及其优缺点
缺点:
不支持低版本的浏览器,最低只支持到IE9;
不利于SEO的优化(如果要支持SEO,建议通过服务端来进行渲染组件);
第一次加载首页耗时相对长一些;
不可以使用浏览器的导航按钮需要自行实现前进、后退。
优点:
无刷新体验,提升了用户体验;
前端开发不再以页面为单位,更多地采用组件化的思想,代码结构和组织方式更加规范化,便于修改和调整;
API 共享,同一套后端程序代码不用修改就可以用于Web界面、手机、平板等多种客户端
用户体验好、快,内容的改变不需要重新加载整个页面。
# 43. 怎样理解单向数据流
这个概念出现在组件通信。父组件是通过 prop 把数据传递到子组件的,但是这个 prop 只能由父组件修改,子组件不能修改,否则会报错。子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。
# 44. 理解Vue中的Render渲染函数
Vue框架的核心是虚拟DOM,编译template模板时要转译成VNode的函数,当用render函数构建DOM时,Vue就免去了转译的步骤。