一、性能优化
1. 虚拟列表
比如:table的数据,如果不分页的情况,要展示XXX条数据,可以用虚拟列表解决
场景有:options数据特别多、table数据特别多...
通常的面试题:后端一次性给你10W条数据如何优化?虚拟列表 & 长列表
总结:不过有多少条数据,在页面上永远渲染的dom个数是有限的,比如10W条数据渲染永远都渲染10个dom
2. 关于项目打包的优化【webpack构建项目的打包优化】【基于vite构建项目的打包优化】
注意:
懒加载:是指盒子存在,只是图片路径没赋值
<img src='' data-src='1.jpg'> ==> 等到这个盒子需要加载了,把data-src的1.jpg赋值给src
面试中被问到:你在项目中做了哪些性能优化:
1. 可以把部分只是渲染到数据做冻结(不需要修改和删除此数据的)
vue2中使用:Object.freeze
Vue3中使用:markRaw()
2. 后端给的数据比较多,减少dom渲染可以用虚拟列表
比如options数据特别多、table数据特别多,或者BI项目,反正就是需要展示很多数据,我们需要减少dom
3. 剩下的小细节就不说了 , 比如减少重绘,回流、做图标优化...
4. 最主要的就是项目打包的优化,比如项目打包的分包处理,在vite中无需配置,在webpack中需要配置路由懒加载的name,还有比如拆包的处理(某一个主体js文件体积多大,可能是多个路由都打包在一个js中了),还有打包完成的主体js文件内容过于庞大,可以gzip(需要后端去配置,前端就需要打包就行了)
二、兼容处理
2.1 全屏设置兼容
//目前是否是放大
var isFull=!!(document.webkitIsFullScreen || document.mozFullScreen ||
document.msFullscreenElement || document.fullscreenElement);
if( isFull ){//缩小
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
}else{//放大
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
}
}
三、权限
1.1 左侧菜单权限
1. 用户登录,需要请求 【个人信息】 接口 (接口会返回:角色权限编码)
2. 接着 【获取路由】(需要把 角色权限编码 传递给后端)
3. 【获取路由】接口会返回 : 对应登录的账号的权限路由树
1.2 右侧某按钮权限
1. 用户登录,需要请求 【个人信息】 接口 (接口会返回:权限信息)
2. 这时候会把后端返回过来的 用户的 “权限信息” 存储起来
3. 新建自定义指令 v-auth='某某权限字符'
4. 自定义指令大体的判断:
1. 指令有没有传值
2. 在用户的权限信息中, 有没有指令 传递过来的参数
3. 如果有return , 如果没有删除该节点
1.3 某某用户如果没有此权限不可以进入某某路由 [ 在软件级别的系统中,无需判断 ]
1. 在导航守卫【前置性导航守卫】进行判断
2. 如果是超级管理员直接通行
3. 如果不是超级管理,需要拿到用户的所有路由信息(扁平化:map对象) 和 当前的路由判断,存不存在,如果不存在那么就提示(展示错误页面)
1.4 权限管理 [ 删除 、 新增 ]
前沿:其实权限是由 角色 来控制的
1. 给某某用户 “新增” 一个权限
直白点:给某某用户,新增一个 角色
2. 角色新增流程
1. 点击新增按钮
2. 获取菜单权限树
3. 选择对应的权限,其实就是选择了某权限的id
4. 当点击【确认新增】,需要给后端传递 沟通的权限id。
5. 后端会把多个权限id,组合成一个 角色, 一个角色对应一个ID( 这个id就是多个权限生成)
1. 给某某用户 “删除” 一个权限
直白点:给某某用户,去掉一个 角色
***要注意一点:如果某某用户,在使用本角色,那么直接删除(返回:30006 [数据存在引用资源] )
直白点:用户没有用某权限才可以删除,如果被引用会返回:30006
四、微信登录
***在开发平台中,是有基础url的,就是我们项目的网址
2.1 点击 微信登录 ==> 二维码
1. 渲染进程:点击按钮 ==> 通信主进程
2. 主进程:创建新窗口 ==> 打开由 后端 给 前端 的 url 从而展示二维码 ==> 二维码已经渲染到页面上了
2.2 点击 微信登录 ==> 二维码 ==> 如何知道 用户 扫码了 , 并且 成功成功
1. 主进程 监听 URL改变
2. 当URL改变的时候 ==》 去发送一个请求 ==》传递的参数:{
url:网站的网址
headers:{
WechatOauthType:'login'
}
}
3. 请求发送成功后 会返回 微信授权code码
4. 主进程 通信 渲染进程 同时把code码(微信授权code码)传递给渲染进程
5. 渲染进程拿到主进程给的code码(微信授权code码),再次发送请求 [获取AccessToken],同时把code码(微信授权code码)传递给后端
***后端返回:如果此微信用户 绑定过 会直接返回 token , 如果没有绑定 会返回其他信息(该微信未绑定账号)
***微信登录之前,需要绑定 【在我们这个系统中是这样的:后台管理系统】
2.3 绑定用户
1. 渲染进程:点击按钮 ==> 通信主进程 (需要渲染进程给主进程传递token)
2. 主进程:创建新窗口 ==> 打开由 后端 给 前端 的 url 从而展示二维码 ==> 二维码已经渲染到页面上了
3. 主进程 监听 URL改变
4. 当URL改变的时候 ==》 去发送一个请求 ==》 传递的参数:{
url:网站的网址
headers:{
WechatOauthType:'bind',
Authorization:'token'
}
}
5. 请求发送成功后 会返回 微信授权code码
6. 主进程 通信 渲染进程 同时把code码(微信授权code码)传递给渲染进程
7. 渲染进程拿到主进程给的code码(微信授权code码),再次发送请求 [检查绑定状态],同时把code码(微信授权code码)传递给后端
***后端返回:如果此微信用户 绑定过 会返回 【已绑定】, 如果没有绑定 会返回【绑定成功】
五、阿里云oss
1.1 下载oss
参考网址:www.npmjs.com/package/ali…
下载安装:npm install ali-oss --save
1.2 配置oss文件
//引入
import OSS from 'ali-oss'
//实例化 OSS : 创建OSS对象
export const clint = new OSS({
region: 'oss-cn-beijing', //区域
bucket: 'xiaoluxian-1302830050', //bucket名称
accessKeyId: 'LTAI5t9m6NZe2GiCsFBiF2nT', //访问键ID
accessKeySecret: 'EPnMPkHg41O7Tk3wZh212wLmQ6r7jA', //访问密钥
endpoint: 'oss-cn-beijing.aliyuncs.com',//地域节点 ===> 外网访问
secure:true, //如果是http默认就是false,如果是https就要写成true
});
1.3 阿里云oss上传文件的方法
client.multipartUpload(上传文件的名称 | 目录 + 文件名称 , 上传的文件 )
or
new OSS.multipartUpload(上传文件的名称 | 目录 + 文件名称 , 上传的文件 )
1.4 oss上传代码
const tool = {};
import { nanoid } from 'nanoid'
import { client } from '@config/alioss'
//阿里云oss ==> 断点续传
tool.oss = {
//执行上传文件操作的方法
upload( file , loading = true ){
const uuid = nanoid();
//截取文件后缀名
const index = file.name.lastIndexOf('.');
const suffix = file.name.substring( index + 1 );
const currentDate = tool.dateFormat( new Date() , 'yyyy-MM-dd');
let fileName = 'xiaoluxian-crm/' + currentDate +'/' + uuid + '.' +suffix;
//const _loading = loading ? ElLoading.service({ lock: true }) : {close:noop};
//执行上传操作
return client.multipartUpload(fileName,file,{
progress:function( percentage , checkpoint ){
// console.log( percentage , checkpoint )
},
//checkpoint:'',
}).then(data=>{
console.log( data );
}).finally(()=>{
//_loading.close();
})
}
}
//时间戳转换为标准时间
tool.dateFormat = ( date:String , fmt='yyyy-MM-dd hh:mm:ss' )=>{
date = new Date( date );
var o = {
'M+': date.getMonth() + 1, // 月份
'd+': date.getDate(), // 日
'h+': date.getHours(), // 小时
'm+': date.getMinutes(), // 分
's+': date.getSeconds(), // 秒
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
'S': date.getMilliseconds() // 毫秒
}
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
for (var k in o) {
if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
}
return fmt;
}
export default tool;
六、electron下载文件
简单来说:导出就是一个请求,传递对应的参数就可以了,下载就是一个任务列表,在任务列表中点击下载按钮,对应调用fs创建文件调用管道写入内容。
流程为:
1. 页面(渲染进程)点击下载按钮,给主进程传递参数
2. 由主进程创建新的窗口
3. 新窗口打开一个.vue文件,这个文件其实是一个路由的component
4. 打开页面后,点击任务列表中的某一个下载按钮
5. 调用https.get方法 并且 主进程执行dialog ,当dialog中用户选择了存储的地址后,通过fs.createWriteStream创建生成文件,由https.get请求的res.pipe(file) 【通过管道往文件中写入内容】
七、ws通信
前言:接收消息(下载了某一个东西,通知)。
1. 生命周期执行WebSocket操作
a>创建WebSocket对象 new WebSocket
b>连接成功后的回调函数 onopen
c>从服务器接收信息时的回调函数 onmessage
***如果有消息,onmessage中的函数参数可以接收到
把数据渲染到页面上
2. 点击联系人【树】==》获取某一个用户的id
a>发起请求,产生会话==》谁发消息,谁接收
b>请求聊天记录接口==》获取聊天记录内容,展示页面
3. 点击发送消息
a>向服务端发送的消息 ==》WebSocket对象.send(内容:按照后端给的数据结构来传递)
b>把自己输入的内容展示到页面
http 和 tcp区别
1. Socket建立连接,服务器可以向客户端主动发送消息 ( http做不到,http的服务端是被动的,需要客户端主动发,服务端才可以返回数据
2. 如果请求内容特别多的情况下(http频繁去发送请求会导致性能下降,Socket因为一直通信,所以可以解决这个问题)
WebSocket使用场景:
1. 如果不确定用户什么时候会操作,同时操作完成,客户端要有响应的行为。
扫码支付(短轮训、WebSocket)
聊天(WebSocket)
即时消息[站内信、通知...](WebSocket)
2. 如果要做纯聊天,没人直接用WebSocket,一般会用(环信、腾讯IM)
八、electron项目打包上线
1. electron-vite 什么都不写的情况下,打包40MB+
2. 打包多端:linux、macos、windows
3. 打包命令:npm run build:win
4. electron不用做优化 ==》因为electron内部已经做了
5. electron不涉及到上架问题
appId: com.electron.app //包名
productName: 小鹿线CRM客户管理系统 //软件的名称
windows配置: 256*256 图标
win:
executableName: electron-app //可执行文件
nsis:
artifactName: ${name}-${version}-setup.${ext} //安装包的名称和后缀
shortcutName: ${productName} //快捷方式的名称
uninstallDisplayName: ${productName} //卸载程序的名称
createDesktopShortcut: always //是否创建快捷方式
oneClick:false //一键安装
allowToChangeInstallationDirectory //是否可以选择安装目录
mac配置
图标:icon.icons 512 * 512 图标
如果是vue2、vue3项目无所谓,因为取决于构建工具:webpack、vite
webpack ==》
1. 代理(解决跨域问题,在开发阶段生效,生产阶段不生效) ==> 环境变量解决
2. 路由懒加载 import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/Foo') ==> 分包
3. 拆包
4. 压缩打包(某一个js文件贼大:zip/gz)
vite
1. 代理(解决跨域问题,在开发阶段生效,生产阶段不生效) ==> 环境变量解决
2. 自动分包( 不需要配置 )
3. 拆包(也会自动,可能自己有个性化的内容)
4. 压缩打包(某一个js文件贼大:zip/gz)
***注意坑:node版本太高打包可能有问题,建议node版本:16.19.0
面试回答:
1. node版本不要太高,我设置的是16.9.0
2. windows跟根据公司的要求来配置,比如是否可以选择安装目录,是否创建快捷方式,设置卸载的名称...
3. windows配置的图标是:build/icon.ico 、mac os 配置的图标是build/icon.icons
4. 包名无所谓/名称自己起
5. 设置好 环境变量 进行打包即可 : npm run build:win. 、 npm run build:mac
6. 可能会配置更新 ???
打包所遇到的问题:
mac os 有问题 : Error: Cannot find module 'builder-util-runtime'
解决:
1. 在package.json中dependencies中的
"@electron-toolkit/preload": "^1.0.3",
"@electron-toolkit/utils": "^1.0.2",
"electron-log": "^4.4.8",
"electron-updater": "^5.3.0",
"electron-win-state": "^1.1.22",
放入devDependencies中
2. 重新安装依赖
九、electron判断多端
import isElectron from 'is-electron';
const isInElectron = isElectron();
export const isOsx = isInElectron && window._platform && window._platform === 'darwin';
export const isWin = isInElectron && window._platform && window._platform === 'win32';
export const isLinux = isInElectron && window._platform && window._platform === 'linux';
九.1 软件更新问题
electron 中 有自动更新软件的api方法他叫:autoUpdater,在我们打包项目上线的时候
会生成latest-mac文件,我们需要给后端,后端上线以后,会给一个地址,这个地址包含了软件的安装包也包含了软件的更新latest-mac文件,如果有需要更新的(其实根据再次打包的package.json中的版本号和线上更新文件的版本号是否一致,如果不一致证明需要更新了),更新操作通过autoUpdater的事件来完成。
十、 目前项目使用的vue技术
2.1 全局混入:字典
2.2 自定义指令:判断按钮级权限控制
2.3 store + 持久化存储:刷新页面数据还在
十一、vue2和3通用的api
11.1 混入
面试题1:混入有什么缺点?
不好维护(因为数据来源不好找)
面试题2:混入是什么?什么情况下去用?
混入就是在任何组件中混入数据或者方法( 混入就是全局 )
场景:字典
混入分为2种:
1. 全局 ( main.js )
2. 混入到组件中( 某某.vue )
11.2 插槽
匿名:
父组件:
<Table>
<span>这是插入的东西222222</span>
</Table>
子组件:
<template>
<div>第一行</div>
<div>
第二行
<slot></slot>
</div>
<div>
第三行
<slot></slot>
</div>
</template>
具名:
父组件:
<Table>
<template v-slot:er>
<span>这是插入的东西222222</span>
</template>
<template #san>
<span>这是插入的东西333333</span>
</template>
</Table>
子组件:
<template>
<div>第一行</div>
<div>
第二行
<slot name="er"></slot>
</div>
<div>
第三行
<slot name="san"></slot>
</div>
</template>
作用域:就是传值
父组件:
<Table>
<template v-slot:er="scope">
<span>这是插入的东西222222 {{ scope.row }} {{ scope.names }} </span>
</template>
<template #san="scope">
<span>这是插入的东西333333 {{ scope.row }} {{ scope.names }} </span>
</template>
</Table>
子组件:
<template>
<div>第一行</div>
<div>
第二行
<slot name="er" :row="headerTitle" :names="name"></slot>
</div>
<div>
第三行
<slot name="san" :row="headerTitle" :names="name"></slot>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
let headerTitle = ref(100);
let name = ref('张三');
</script>
11.3 APP.use具体做了什么
app.use( 参数1 , ...options 多个参数 )
1. 首先判断插件 参数1 是否是函数
2. 判断Vue是否已注册过这个插件,如果注册过就跳出方法
3. 如果没有注册就进行注册 ,vue直接调用install方法,在install方法中进行注册 并且传递 vue 这个函数
11.4 自定义指令
语法:app.directive( 指令名称 , function );
场景:后台管理系统 ,按钮级别的控制,就需要自定义指令
11.5 nextTick
是什么:整体返回一个异步行为==》当dom更新完毕触发
场景:比如,发起请求拿到后端数据,把数据渲染到页面上,当渲染完毕 nextTick(()=>{ 设置滚动条位置 })
11.6 组件传值
Vue2:
父子:
父:<abc :msg='msg'/>
子:props
子父:自定义事件
this.$emit()
兄弟:bus
Vue3
父子:
父:<abc :msg='msg'/>
子:definedProps
子父:自定义事件
this.$emit()
兄弟:mitt()
Vue3:v-model传值
父组件:
<abc v-model:msg='msg'/>
子组件:
$emit('update:msg','123');
//原来父组件的msg 修改成了 123
11.7 组件直接修改值
Vue2
父 组件 直接使用[修改]子组件的值: ref
子 组件 直接使用[修改]父组件的值: $parent
Vue3没有$parent
11.8 vue2.6以后版本和vue3的依赖注入
是什么:跨域组件
问题:不好维护(数据来源不明确)
11.9 keep-alive
是什么:缓存组件,会多2个生命周期
场景:后台管理系统,有一个打包路由页面的标签,当打开A标签(A页面),在打包B标签(B页面),之前A页面填写了内容,然后切换到B标签页,那么A标签的内容就不存在(那么就白写了)
***提升用户体验、提升性能
11.10 computed
有缓存,有2种写法,
不可以修改
computed:{
changeStr(){}
}
this.changeStr = 123;
可以修改
computed:{
changeStr:{
get(){
return this.str
},
set( value ){
this.str = value;
}
}
}
this.changeStr = 123;
11.11 watch
监听某一个数据变化
监听路由的变化
初始化监听
深度监听(以下俩种情况需要深度监听)
data : {
arr:[1,2,3]
}
data:{
obj:{
a:1
}
}
11.12 双向绑定原理
vue是由v-model完成双向绑定,那么在完成双向绑定之前就要有单项绑定过程,这个单项绑定就被叫做初始化编译解析过程,也就是把data中定义的数据,展示到页面上。其实在定义data中属性的时候,就已经通过new Proxy或者Object.defineProperty已经做好了劫持(给没一个属性添加了get和set),v-model的行为就是视图修改数据驱动data数据修改,那么已经做好了监听,当input发生改变时在源码中其实是加了input事件的,当value发生改变时,在添加的订阅者中进行更新,从而视图和数据的值就都发生了改变。
11.13 diff算法
虚拟dom的对比==》数据的对比
算法对比条件( 平级 、有层级 )
1. 平级关系
如果新和旧的节点都更换了,那么就不对比了
有旧dom => 转换成虚拟dom [其实拿key做对比]
有虚拟dom了 => 旧虚拟dom的节点名称和key 与 新虚拟dom的节点名称和key进行对比
旧前=》新前
旧后=》新后
旧前=》新后
旧后=》新前
查找
如果不满足以上5个条件,就反复不断执行,到最后剩下的,要不然新建要不然删除
2. 有层级
1. 顶级去做对比 , 对比条件和平级关系一样
2. 平级对比
11.14 虚拟dom
把dom弄成数据的结构
11.15 store
1. 持久化存储
store + localStorage插件
***面试题:a组件使用了vuex中的某一个数据,然后修改了数据,修改完数据刷新页面又回到之前的数据了为什么?
解决:store + localStorage插件
2. vuex的mutaitons和actions区别
mutaitons同步
actions异步
3. vuex和pinia区别
pinia小
pinia没有mutations和modules
pinia可以直接修改state数据
*** vuex也可以,只不过需要this.$store.state.xx , 不能使用辅助函数的形式修改mapState
11.16 路由
1. 路由导航守卫
三大类型:全局、路由独享、组件内
场景:
beforeEach(前置导航守卫):开启加载条、判断用户是否登录、判断有没有权限、document.title设置...
afterEach(全局后置):关闭加载条
2. 路由传参
query==》注意应用类型转换格式 ( JSON.xxx )
3. 动态路由
根据返回的不同数据,展示不同的路由 ==》 :id
4. 添加路由 : add.addRoute
router.addRoute('名称',{
path: "/login",
meta:{
title:'登录',
},
component: ()=>import('@views/login/Login.vue')
})
5. router和route
router是整个路由的对象,包含路由所有内容和route
route是router中的属性,包含当前路由内容
6. Vue3的路由router4.x版本 : <router-link tag='li'> 取消了tag
11.17 ref
Vue2:<div ref='aaa'> ==> this.$refs.aaa
Vue3:<div ref='bbb'> ==> let bbb = ref(); bbb.value
十二、Vue3面试题
12.1 Vue3项目采用什么写法?
纯setup写法
12.2 在setup写法中无法使用this,你怎么办?
import { getCurrentInstance} from 'vue'
const { proxy } = getCurrentInstance();
12.3 Vue2和Vue3有什么区别?
1. 双向绑定原理不同
Vue2:defineProperty
不在Object.defineProperty中添加的属性,是监听不到的(如果对象创建了,后添加的也监听不到),这个问题衍生出来的问题是(数据修改了,视图不更新),在vue2中使用$set来解决这个问题. this.$set(对象,key,值)
Vue3:new Proxy
即使给对象后添加的属性,也可以被监听到
2. Vue3删除了一些Vue2的api:$set、$parent...
3. 纯setup写法,没有this,要获取this,就要config上下文
12.4 异步组件
异步组件写法:对于加载没有用,但是打包后会单独分包
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
如果要用异步组件,一定要给条件( 什么时候加载这个组件 )
也可以配合Suspense内置组件一起使用,说明:知道此异步组件加载的状态
课程:https://www.xuexiluxian.cn/course/detail/e88ee53679324a2292dbba23051b81db,搜索:异步组件
十三、vue项目打包出现的问题
一、面试题:出现空白页面怎么解决?
配置路径相对于本目录: publicPath:'./'
配置环境变量:axios代理请求的问题,开发环境不生效
二、面试题:做过单元测试
我们公司有专门测试,不需要我做!
链接:https://www.bilibili.com/video/BV1Ua411o7KT?p=5&vd_source=428ad5db675cd7c14aa84c9336139535
十四、ES6
14.1 Promise和async/await区别
1. 关于严格控制代码执行顺序
async/await : 返回虽然是异步(请求的数据)但是也可以控制代码的执行顺序
Pormise不可以控制以上的行为
2. await可以直接解析promise
14.2 使用过promise的哪些方法?如果同时发送多个请求怎么办?
.all
场景:选中删除( 后端给的接口,只能传递一个删除的id ) xxx/delete/id号
14.3 es6用过哪些东西?
promsie、解构、let、const、箭头函数、字符串扩展方法、对象扩展方法、new Map、?.
14.4 new Map和普通对象区别
1. key的类型
new Map的key可以是任何类型,普通对象的key永远是字符串
2. new Map对于判断操作性更强 ( has ) ,对于数据存储对象形式,更加优雅。
*** 很多算法,都会用map
14.5 箭头函数和普通函数区别
1. this指向
箭头函数this不可以修改,定义时候就决定了
普通函数this可以修改,随着环境也会更改
2. 箭头不能new,不能当作构造函数
3. 箭头没有arguments
...
面试题:如果拷贝了呢?和箭头函数没关系,this永远指向定义时的对象,拷贝的值和this指向没关系,因为对象多了1份
十五、TS和Js有什么区别
15.1 关于面向对象
TS语法是面向对象的语法
ES6的class是一种模拟形式的面向对象,但是在ES2022添加了私有化
十六、JavaScript
16.1 event loop(事件循环机制)
js的执行机制:同步任务和异步任务
js的执行顺序为:同步任务==》异步任务(当前的微任务==》下一个宏任务==》)
16.2 防抖、节流、手写Promise源码
16.3 判断某某变量是不是数组类型
Array.isArray
Object.prototype.toString.call(变量) === '[object Array]'
16.4 new 操作具体做了什么?
1. 创建了一个空的对象
2. 将空对象的原型,指向于构造函数的原型
3. 将空对象作为构造函数的上下文(改变this指向)
4. 对构造函数有返回值的处理判断
function Fun( age,name ){
this.age = age;
this.name = name;
}
function create( fn , ...args ){
//1. 创建了一个空的对象
var obj = {}; //var obj = Object.create({})
//2. 将空对象的原型,指向于构造函数的原型
Object.setPrototypeOf(obj,fn.prototype);
//3. 将空对象作为构造函数的上下文(改变this指向)
var result = fn.apply(obj,args);
//4. 对构造函数有返回值的处理判断
return result instanceof Object ? result : obj;
}
console.log( create(Fun,18,'张三') )
16.5 闭包
1. 闭包是什么
闭包是一个函数加上到创建函数的作用域的连接,闭包“关闭”了函数的自由变量。
2. 闭包可以解决什么问题【闭包的优点】
2.1 内部函数可以访问到外部函数的局部变量
2.2 闭包可以解决的问题
var lis = document.getElementsByTagName('li');
for(var i=0;i<lis.length;i++){
(function(i){
lis[i].onclick = function(){
alert(i);
}
})(i)
}
3. 闭包的缺点
3.1 变量会驻留在内存中,造成内存损耗问题。
解决:把闭包的函数设置为null
3.2 内存泄漏【ie】 ==> 可说可不说,如果说一定要提到ie
16.6 原型链
1. 原型可以解决什么问题
对象共享属性和共享方法
2. 谁有原型
函数拥有:prototype
对象拥有:__proto__
3. 对象查找属性或者方法的顺序
先在对象本身查找 --> 构造函数中查找 --> 对象的原型 --> 构造函数的原型中 --> 当前原型的原型中查找
4. 原型链
4.1 是什么?:就是把原型串联起来
4.2 原型链的最顶端是null
16.7 JS继承
方式一:ES6
class Parent{
constructor(){
this.age = 18;
}
}
class Child extends Parent{
constructor(){
super();
this.name = '张三';
}
}
let o1 = new Child();
console.log( o1,o1.name,o1.age );
方式二:原型链继承
function Parent(){
this.age = 20;
}
function Child(){
this.name = '张三'
}
Child.prototype = new Parent();
let o2 = new Child();
console.log( o2,o2.name,o2.age );
方式三:借用构造函数继承
function Parent(){
this.age = 22;
}
function Child(){
this.name = '张三'
Parent.call(this);
}
let o3 = new Child();
console.log( o3,o3.name,o3.age );
方式四:组合式继承
function Parent(){
this.age = 100;
}
function Child(){
Parent.call(this);
this.name = '张三'
}
Child.prototype = new Parent();
let o4 = new Child();
console.log( o4,o4.name,o4.age );
16.8 说一下call、apply、bind区别
共同点:功能一致
可以改变this指向
语法: 函数.call()、函数.apply()、函数.bind()
区别:
1. call、apply可以立即执行。bind不会立即执行,因为bind返回的是一个函数需要加入()执行。
2. 参数不同:apply第二个参数是数组。call和bind有多个参数需要挨个写。
场景:
1. 用apply的情况
var arr1 = [1,2,4,5,7,3,321];
console.log( Math.max.apply(null,arr1) )
2. 用bind的情况
var btn = document.getElementById('btn');
var h1s = document.getElementById('h1s');
btn.onclick = function(){
console.log( this.id );
}.bind(h1s)
16.9 深拷贝和浅拷贝
共同点:复制
1. 浅拷贝:只复制引用,而未复制真正的值。
var arr1 = ['a','b','c','d'];
var arr2 = arr1;
var obj1 = {a:1,b:2}
var obj2 = Object.assign(obj1);
2. 深拷贝:是复制真正的值 (不同引用)
var obj3 = {
a:1,
b:2
}
var obj4 = JSON.parse(JSON.stringify( obj3 ));
//递归的形式
function copyObj( obj ){
if( Array.isArray(obj) ){
var newObj = [];
}else{
var newObj = {};
}
for( var key in obj ){
if( typeof obj[key] == 'object' ){
newObj[key] = copyObj(obj[key]);
}else{
newObj[key] = obj[key];
}
}
return newObj;
}
console.log( copyObj(obj5) );
补充
vue优点
1. 因为vue是单页面应用,所以不涉及到跳转页面,如果要按照传统方式a标签的形式,就会多长时间等待页面加载出来。
2. vue中有v-model,这个模式比传统的dom操作要方面的多。
vue缺点
因为vue是单页面应用,也就是说打包后整个项目只有一个html,所以对于SEO方面就比较弱。所以vue适合后台管理系统 或 不做SEO的项目
SEO条件:其中做seo必须的条件是 ==》 多页面