一.JS
1.闭包
1. 什么是闭包
函数嵌套函数 当内部函数访问外部函数变量时 就产生了闭包
2.特性
函数嵌套函数
内部函数可以直接访问外部函数的内部变量或参数
变量或参数不会被垃圾回收机制回收
3. 闭包应用场景
- 函数作为参数被传递
- 函数作为返回值被返回
- 实际应用(隐藏数据):普通用户只能通过get、set等api对数据进行查看和更改等操作,没法对data直接更改,达到所谓隐藏数据的效果;jquery就利用了这一特性,必须调用$.ajax()才能访问内部属性方法。
封装功能时(需要使用私有的属性和方法),函数防抖、函数节流、单例模式
4.闭包优点
避免全局变量污染
私有成员的存在
变量长期驻扎在内存中
5.闭包的缺点
常驻内存
增大内存的使用量
使用不当 会造成内存泄漏
2.作用域
变量作用域就是一个变量可以使用的范围
- 一段代码起作用的范围 分为全局作用域和局部作用域
- 函数内部使用变量,先从当前函数查找,如果找不到,则继续向父级查找,如果还找不到继续往上一级查找,最后找到window对象,如果还找不到,则报错提示变量不存在,这种链条关系就是作用域链
1.作用域种类:
-
js中首先有一个最外层的作用域,全局作用域
-
js中可以通过函数来创建一个独立作用域称为
函数作用域
,函数可以嵌套,所以作用域也可以嵌套; -
es6新增了块级作用域
{}
比如if{} for{}
-
es6作用域,只适用于const,let
2.自由变量:当前作用域没有定义的变量
-
一个变量在当前作用域没有定义,但被使用了
-
向上级作用域,一层一层依次寻找,直达找到为止
-
如果全局作用域都没找到,则报错 xx is not defined
3.作用域链: 自由变量向上级作用域一层一层查找,直到找到为止,最高找到全局作用域,这就是作用域链
变量提升(预解析)
var声明的变量,function声明的函数存在变量提升 let const 不会变量提升
- javascript中声明并定义一个变量时,会把声明提前
- 函数声明也会把整个函数提升到作用域的最上面
- 函数表达式不能变量提升,只会把声明的 var fn 提升到作用域的最顶端,
3.原型
1.原型的概念
JavaScript的所有对象都包含一个proto属性,这个属性对应的就是自身的原型
JavaScript的函数对象,除了原型proto属性之外还有prototype属性,当函数对象作为构造函数创建实例时,该prototype属性值将被作为实例对象原型proto
2.原型链
获取对象属性时,如果对象本身没有这个属性,那就会去他的原型__proto__
上去找,如果还查不到,就去找原型的原型,一直找到最顶层(Object.prototype
)为止。Object.prototype对象也有__proto__属性值为null。
4.继承
1. 使用继承的好处
- 减少代码量,减少代码冗余
- 可以属性和方法的共用
- 减少了内存使用
2.ES5中的继承
- 原型继承:父类的实例作为子类的原型
- 借用构造函数继承:在子类中使用call方法调用父类的方法并将父类的this改成子类的this
- 组合继承:既能调用父类的实例属性又能调用父类的原型属性
3.ES6继承
- 使用class构造一个父类
- 使用class构造一个子类,并使用extends实现继承,super指向父类的原型对象
- 实例化对象
4.ES6中class继承
- class相当于es5中的构造函数
- class中定义方法是前后不能加function,全部都定义在class的prototype属性中
- class中只能定义方法不能定义对象变量等
- class默认是严格模式
- 在子类中调用extends方法可以调用父类的属性,用eat调用父类的方法
5.ES6中的6种继承方式
- 原型链继承
原型链继承的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法 - 借用构造函数
在子类型的构造函数中调用超类型构造函数 - 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合继承
1. 原型链继承
- 父类的实例作为子类的原型
- 可以使用父类原型上的方法,不可以传递参数
2. 构造函数继承
- 在子类内使用
call()
调用父类方法,将父类this的指向修改为子类的this,相当于把实列属性复制一份给子类 - 可以父类实例中的方法,可以传递参数。不能使用父类原型的方法
3. 组合继承
- 即在子类中使用
call()
方法,再把父类的实例作为子类的原型 - 既能调用父类实例的方法,又能调用父类原型的方法,还可以传递参数
5.手写深拷贝
- 浅拷贝: 将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用
- 深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”
const add = { //定义的一个对象
name: "名字",
age: 9,
sex: {
sex: "男"
},
arr: ["a", "b", "c"]
}
//递归函数
function deepClone(obj) {
//obj 是 null ,或者不是对象和数组,直接返回
if (typeof obj !== "object" || obj == null) {
return obj;
}
let result //初始化变量
//判断是不是一个数组
if (obj instanceof Array) {
result = []
} else {
result = {}
}
//循环obj
for (let key in obj) {
//判断 key 不是一个原型属性
if (obj.hasOwnProperty(key)) {
//调用递归函数
result[key] = deepClone(obj[key])
}
}
//返回数据
return result
}
//将add拷贝到obj1中
const obj1 = deepClone(add)
console.log(obj1.arr) //打印结果 ["a","b","c"]
//给数据arr的第一个值重新赋值
obj1.arr[0] = "d"
console.log(obj1.arr) //打印结果 ["d","b","c"]
console.log(add.arr) //打印结果 ["a","b","c"]
二.ES6
1.promise
- promise简单来说就是一个容器,里面存放一些异步操作的结果
- 也可以说他是一个对象,他可以获取异步操作的最终结果(成功或者失败)
- 他还是一个构造函数,他有对外统一的API,自身有all ,reject ,resolve等方法,他的原型上有then ,catch等方法。
- 他可以链式调用,(.then().then()。。。。。)
- 有三种状态 : pending 初始状态 ,fulfilled 成功状态,rejected 失败状态
- 解决了地狱回调
- 应用场景:封装ajax,axios的get,post封装,微信小程序中封装wx.request(),uniapp开发中uni.request()
- promise(首字母小写)对象指的是“Promise实例对象” ,Promise首字母大写且单数形式,表示“Promise构造函数” ,Promises首字母大写且复数形式,用于指代“Promises规范”
- 他的状态一旦改变就不可逆,只能从初始状态改成成功或者失败状态
- all的方法是并行执行异步操作,等所有异步操作完成后才会执行回调
- race方法是只要有一个异步操作执行完毕,就会马上进行回调
- 一般用来请求接口
2.Promise有哪些API
- .then
- 把原来的回调分离出来,用链式调用的方式回调,then有两个回调函数,第一个成功,第二个失败
- .catch
- 和then的参数一样,用来指定错误的reject的回调,在执行then方法时,如果报错会执行catch方法
- .all
- 并行执行异步操作,在所有异步操作完成后回调
- .racel
- 用法和all一样,只有一个异步操作完成后回调,其他没有执行完成的异步操作会继续执行
3.promise(异步加载图片过程)
function requestImg(){
var p = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){
resolve(img);
}
img.src = 'xxxxxx';
});
return p;
}
//延时函数,用于给请求计时
function timeout(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
reject('图片请求超时');
}, 5000);
});
return p;
}
Promise.race([requestImg(), timeout()]).then(function(results){
console.log(results);
}).catch(function(reason){
console.log(reason);
});
//上面代码 requestImg 函数异步请求一张图片,timeout 函数是一个延时 5 秒的异步操作。我们将它们一起放在 race 中赛跑。
//如果 5 秒内图片请求成功那么便进入 then 方法,执行正常的流程。
//如果 5 秒钟图片还未成功返回,那么则进入 catch,报“图片请求超时”的信息。
3.async\await
- async可以把普通函数改成异步函数,调用都是一样的
- 异步的async函数返回的是一个promise对象
- async配合await使用是异步方法但它是一个阻塞的异步方法
优点:
- 方便级联调用:调用依次发生的场景
- 同步代码编写方式:从上到下执行
- 多个参数传递:跟promise不一样,可以当做普通变量来处理,想怎么用就怎么用,想定义几个变量就定义几个
- 同步代码和异步代码可以一起编写:不用纠结同步异步的区别,把异步封装成一个promise对象放在await后面
- 是对promise的优化
特点:
- async 用法它作为一个关键字放到函数前面,这个普通函数就变成了异步函数
- 异步async函数调用,跟普通函数的使用方式一样
- 异步async函数返回一个Promise
- async配合await关键字使用(阻塞代码往下执行)是异步的方式,但是是阻塞式的
使用场景
- async主要处理异步操作
- 简化Promise的链式回调 最终版处理地狱回调
三.vue
1.双向数据绑定
- vue中的v-model可以实现双向绑定,其核心思想通过Object.definePropery来对Vue的数据进行数据劫持
- 主要分为三部分
- observer主要是负责对Vue数据进行数据劫持,使其数据拥有get和set方法
- 指令解析器负责绑定数据和指令,绑定试图更新方法
- watcher负责数据监听,当数据发生改变通知订阅者,调用视图更新函数更新视图,主要做的事情是:
1)在自身实例化时往属性订阅器(dep
)里面添加自己
2)自身必须有一个update
()方法
3)待属性变动dep.notice()
通知时,能调用自身的update()方法
,并触发Compile中绑定的回调
,则功成身退。
MVVM
作为数据绑定的入口,整合Observer、Compile和Watcher三者,
通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
2.组件通信
1.兄弟组件通信
- 在src中新建一个Bus.js的文件,然后导出一个空的vue实例
- 在传输数据的一方引入Bus.js 然后通过
Bus.$emit(“事件名”,"参数")
来派发事件,数据是以$emit()的参数形式来传递 - 在接受的数据的一方 引入 Bus.js 然后通过 Bus.$on(“事件名”,(data)=>{data是接受的数据})
2.父传子
- 在父组件的子组件标签上绑定一个属性,挂载要传输的变量
- 在子组件中通过props来接收数据,props可以是数组也可以是对象,接收的数据可以直接使用props:[“属性名”],props:{属性名: 数据类型}
3.子传父
- 在父组件的子组件标签上自定义一个事件,然后调用需要的方法
- 在子组件方法中通过this.$emit(“事件”)来触发在父组件中定义的事件,数据是以参数形式传递的
3.虚拟dom
将虚拟节点渲染到视图上,如果直接使用虚拟节点覆盖旧节点的话会有很多不必要的操作,为了避免不必要的dom操作,虚拟dom在虚拟节点映射到视图的过程中,将虚拟节点与上一次渲染视图所使用的的旧虚拟节点做对比,找出真正需要更新的节点来进行dom操作,从而避免其他无需改动的dom。
虚拟dom是利用js描述元素与元素的关系
好处:可以快速的渲染高效的更新元素,提高浏览器性能
diff算法
优点:最终表现在DOM上的修改只是部分的变更,可以保证高效的渲染,提高网页的性能
缺点:首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHtml慢一点
如何获取dom
在vue中可以通过给标签加ref属性,就可以在js中利用ref去引用它,从而操作该dom元素
4.vuex
1. vuex是一个专门为vue.js应用程序开发的状态管理模式。采用集中式存储和管理程序的所有组件数据
2. 好处
在大型程序中如果多个组件中用到的数据我们可以存储到vuex,如果小型项目我们可以适当使用vuex
3. 运行机制
在组件中通过dispatch来调用actions中的方法在actions中通过commit来调用mutations中的方法,在mutations中可以直接操作state中的数据,state的数据只要一发生改变立马响应到组件中
4. 异步修改数据
通过dispatch调用actions中的方法,再通过commit提交调用mutations中的方法,修改state数据达到修改数据目的
5. vuex核心概念及作用(五大属性)
- state所有的数据都存储在state中 state是一个对象
- mutations 可以直接操作state中的数据
- actions 只能调用mutations的方法
- getters 类似计算属性实现对state中的数据做一些逻辑性的操作
- modules 将仓库分模块存储
6.vue持久化
- vuex里面存放的数据,页面一经刷新会丢失:
解决办法: 存放在localStorage或者sessionStorage里面,进入页面时判断是否丢失,丢失再去localStorage或者sessionStorage里面取;
在app.vue根组件的created里面判断是否丢失,在进行上面的操作; - vuex-persistedstate 插件
5.keep-alive
概念: keep-alive是Vue的内置组件,能在组件切换过程中将状态保留在内存中,取消组件的销毁函数,防止重复渲染DOM
-
我们在切换路由的时候,想保存组件的状态,比如列表页面进入详情,我们想保存列表滚动的位置,我们就可以使用keep-alive保存列表页面的滚动位置。
-
组件使用keep-alive以后会新增两个生命周期 actived() deactived()
1. 全局保存在App.vue中 <keep-alive>把routerView保存起来 2. 部分缓存 1、router.js中设置要缓存的页面 { path: '/child1', name: 'Child1', component: Child1, meta:{ keepAlive:true } } }, 2、用v-if来显示router-view是否在keep-alive中出现 <keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view> </keep-alive> <router-view v-if="!$route.meta.keepAlive"></router-view> 3、使用keep-alive的标签属性, include() exclude() <keep-alive inclue="list,detail" ></keep-alive> //include 包含标签名字被缓存 exclude 包含的标签不被缓存 //缓存名字组件中有个name属性进行定义即可
6.自定义组件
面试常问题
1. 你封装过组件吗?
2. 说一下组件封装?
3. 你在项目中如何封装组件?
-
我用vue开发的所有项目,都是采用组件化的思想开发的。一般我在搭建项目的时候,会创建一个views目录和一个commen目录和一个feature目录,views目录中放页面级的组件,commen中放公共组件(如:head(公共头组件),foot(公共底部组件)等),feature目录内放功能组件(如:swiper(轮播功能组件),tabbar(切换功能组件)、list(上拉加载更多功能组件))
-
首先,组件可以提升整个项目的开发效率。能够把页面抽象成多个相对独立的模块,解决了我们传统项目开发:效率低、难维护、复用性低等问题。
-
使用Vue.extend方法创建一个组件,然后使用Vue.component方法注册组件。但是我们一般用脚手架开发项目,每个 .vue单文件就是一个组件。在另一组件import 导入,并在components中注册,子组件需要数据,可以在props中接受定义。而子组件修改好数据后,想把数据传递给父组件。可以采用emit方法。
7.自定义指令
除了核心功能默认内置的指令 (如v-model 和 v-show等),Vue 也允许注册自定义指令。有的情况下,对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
1.自定义指令分为全局自定义指令和局部自定义指令
- 使用
Vue.directive('focus',{bind(el,binding){},inserted(){}})
进行全局自定义指令 - 参数1 :指令的名称
- 参数2: 是一个对象,这个对象身上,有钩子函数.
bind(){} 只调用一次,指令第一次绑定到元素时调用
inserted(){}被绑定元素插入父节点时调用
update(){}被绑定元素所在的模板更新时调用,而不论绑定值是否变化
componentUpdated(){}被绑定元素所在模板完成一次更新周期时调用
unbind(){}只调用一次, 指令与元素解绑时调用
2.指令钩子函数会被传入以下参数
- el:指令所绑定的元素,可以用来直接操作 DOM 在每个函数中,第一个参数el ,表示被绑定了指令的那个元素,这个 el 参数,是一个原生的JS对象,。
- binding:一个对象,包含以下属性:
- name:指令名,不包括 v- 前缀。
- value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2。
- oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
- expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
- arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
- modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
- vnode: Vue编译生成的虚拟节点。移步 VNode API 来了解更多详情。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
3.实际应用
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
<input v-focus>
8.路由守卫
路由钩子函数有三种分别为 全局守卫 单个路由守卫 组件内部守卫
全局守卫钩子 beforeEach
单个路由守卫 beforeEnter
组件内部守卫
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
- 全局路由守卫
所谓全局路由守卫,就是小区大门,整个小区就这一个大门,你想要进入其中任何一个房子,都需要经过这个大门的检查
全局路由守卫有个两个:一个是全局前置守卫,一个是全局后置守卫
router.beforeEach((to, from, next) => { console.log(to) => // 到哪个页面去? console.log(from) => // 从哪个页面来? next() => // 一个回调函数 } router.afterEach(to,from) = {} next():回调函数参数配置
next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址
next(’/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: ‘home’ 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项
2. 组件路由守卫
// 跟 methods: {}等同级别书写,组件路由守卫是写在每个单独的 vue 文件里面的路由守卫
beforeRouteEnter (to, from, next) {
// 注意,在路由进入之前,组件实例还未渲染,所以无法获取 this 实例,只能通过 vm 来访问组件实例
next(vm => {}) } beforeRouteUpdate (to, from, next) { // 同一页面,刷新不同数据时调用, } beforeRouteLeave (to, from, next) { // 离开当前路由页面时调用 }
3. 路由独享守卫
路由独享守卫是在路由配置页面单独给路由配置的一个守卫
export default new VueRouter({ routes: [ { path: '/', name: 'home', component: 'Home', beforeEnter: (to, from, next) => { // ... } } ] })
9.生命周期
- 创建
beforeCreate()执行这个钩子时只有一些实例本身的事件和生命周期函数用户自定义不能使用
created()最早开始使用data和methods中数据的钩子 - 载入
beforeMount()指令解析完毕,内存中生成dom数
mounted()dom已经渲染完毕,页面和内存已经同步 - 更新
beforeUpdate()当data的数据发生改变会执行这个钩子,内存中数据是新的,页面是旧的
updated()内存和页面都是新的 - 销毁
beforeDestroy()即将销毁data和methods中的数据此时还是可以使用的,可以做一些释放内存的操作
destroyed()已经销毁完毕
- keep-alive 方法
actived() 组件加上keep-alive,进入组件触发的方法
deactived 离开组件的时候触发的方法。 - errorCaptured() 组件内发生错误的时候的触发的方法
四.项目开发流程
五.http
1.跨域(keep,vue)
跨域是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javaScript实施的安全限制。
-
跨域解决方案,最少说出三种
Jsonp, CORS, 代理,反向代理,哈希处理跨域,a链接处理跨域,nginx代理跨域,nodejs中间件代理跨域
-
jsonp的原理
-
script的src属性不受同源策略的限制
-
将不同源的服务器端请求地址写在 script 标签的 src 属性中
-
-
什么是同源策略
同源策略 是由NetScape提出的一个著名的安全策略,它是浏览器最核心也最基本的安全功能
答1: 所谓的同源,指的是协议,域名,端口相同。浏览器处于安全方面的考虑,只允许本域名下的接口交互,不同源的客户端脚本,在没有明确授权的情况下,不能读写对方的资源
答2: 所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。 -
jsonp解决跨域
JSONP是服务器与客户端跨域通信的常用方法。最大特点就是简单适用,兼容性好。缺点是只支持get请求,不支持post请求。 -
核心思想:
网页通过添加一个 script 元素,向服务器请求JSON数据,服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。
服务器不在返回JSON格式的数据,而是返回回调函数包裹数据,在src中进行调用,实现了跨域。
2.常见状态码
1.分类
100-199 提示信息 – 表示请求正在处理
200-299 成功 – 表示请求正常处理完毕
300-399 重定向 – 要完成请求必须进行更进一步的处理
400-499 客户端错误 – 请求有语法错误或请求无法实现
500-599 服务器端错误 – 服务器处理请求出错
3.输入ul到页面出现发生了什么
-
浏览器的地址栏输入URL并按下回车。
-
浏览器查找当前URL是否存在缓存,并比较缓存是否过期。
-
DNS解析URL对应的IP。
-
根据IP建立TCP连接(三次握手)。
-
HTTP发起请求。
-
服务器处理请求,浏览器接收HTTP响应。
-
渲染页面,构建DOM树。
-
关闭TCP连接(四次挥手)。
补充
- Object.defineProperty方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,并返回这个对象
Object.defineProperty 有三个参数(object , propName , descriptor)
第一个参数:定义属性的对象。
第二个参数:要定义或修改的属性的名称。
第三个参数:将被定义或修改的属性描述符。
this总结 (重要)
- 普通函数中调用,this指向window
- 对象方法中调用,this指向当前对象
- call apply bind中调用, this指向被传入的对象
- class中的方法中调用, this指向实例对象
- 箭头函数,this就是父级上下文中的this