2022-06-27 前端vue面试题2

#vue通信传值

1.props&$emit

1.1 父传子props

现在我们要从Index页面给A页面传递一个数组list

// index.vue<template><div><A:list="list"/></div></template><script>importAfrom"./components/A";exportdefault{name:"Index",components:{A},data(){return{list:["html","css","js"],};},},</script>

// A.vue<template><div><h2>PageA</h2><ul v-for="(item, index) in list":key="index"><li>{{item}}</li></ul></div></template><script>exportdefault{name:"A",props:["list"],};</script>

效果图如下, 此时list数组以自上而下的一种方式从Index页面传递给A组件, 且props只可以从上一级想下一级传输, 即所谓的单向数据流

image.png

1.2 子传父$emit

那A组件想给Index页面传送数据应该如何操作? 关于子组件给父组件传值, 一般都是通过一个事件搭配$emit进行传输

// A.vue<template><div><h2>PageA</h2><ul v-for="(item, index) in list":key="index"><!--定义事件--><li @click="onItemClick(item)">{{item}}</li></ul></div></template><script>exportdefault{name:"A",props:["list"],methods:{onItemClick(item){this.$emit("on-item-click",`'${item}' from page A`);},},};</script>

父组件监听子组件上的事件名on-item-click

// index.vue<template><div><!--监听--><A:list="list"@on-item-click="handleItemClick"/></div></template><script>importAfrom"./components/A";exportdefault{name:"Index",components:{A,},data(){return{list:["html","css","js"],};},methods:{handleItemClick(value){console.log(`In page Index get ${value}`);},},};</script>

一、为什么要使用vue-bus?

学习vue的开发者都知道,父子组件的直接的通讯直接使用vue提供的props属性和emit方法。props接受来自父组件的参数,emit将子组件的参数传递给父组件。这样一来父子组件之间的参数传递就得到了解决。(之前做项目的时候看到$parent和$children也可以进行父子组件和兄弟之间的参数传递,但是不提倡,原因在于如果组件想要替换位置就有问题)。

那么问题来了,兄弟组件如何进行通讯?有哪些方法

1、vuex全局状态管理

2、bus总线机制/发布订阅者模式/观察者模式

两者相比较,前者适用于大型项目的开发,如果项目业务没那么复杂,推荐使用bus来进行解决这类问题。

二、vue-bus如何使用?

第一步:使用npm install vue-bus --save

第二步:在main.js进行全局注册

第三步:在一个页面引用两个兄弟组件

第四步:使用emit进行参数传递

第五步:在created或mounted生命周期钩子,执行事件监听。最后记得将触发的事件销毁,不然会出现点击多次触发的情况。

三、bus总线机制/发布订阅者模式/观察者模式

比如有一个bus对象(中央事件总线),这个对象上有两个方法,一个是on(监听,也就是订阅),一个是emit(触发,也就是发布),就好比我们订阅报纸,到报社去付钱,才知道你要订阅的。

观察者模式是用来监听数据变化,改变视图层。

#vuex

1、vuex 是什么?

集中式存储管理应用的所有组件的状态(单独出来管理共用的状态,如:token的管理等,相当于一个全局对象),以相应的规则保证状态以一种可预测的方式发生变化(不能直接修改数据,需要通过特定的方法, this.

store.commit() ),无法持久化存储。

vuex 和 Cookie,localstorage 的区别

Cookie:采用键值对的方式存储( document.cookie =  键= 值; 键= 值),可以定义过期时间(document.cookie ="expires=有效期"),存储大小4k

localstorage:使用方法进行键值对的方式存储(getItem(键,值)),永久存储,但是可以根据方法清除, 存储大小5m

方法描述实例

setItem()将对应的名字和值传递进去存储localStorage.setItem('users','我是user')

getItem()名称作为参数,可以获取对应的值localStorage.getItem('users')

removeItem()名称作为参数,可以删除对应的数据localStorage.removeItem('users')

clear()可以删除所有存储的数据localStorage.clear()

sessionstorage:跟 localstorage 方法一样,但是是单页面回话存储,跳转页面被清除,

vuex:相当于全局变量,刷新页面清除,想长久保持数据存在,只能使用Cookie和localstorage存储,然后再赋值回去

如:

getters :{userName: (state,getters,rootState)=>{console.log(state.userId)let a =(state.userId?state.userId:localStorage.getItem('users'))return a}},

2、vue 使用

state 唯一数据源

像我们vue的data

定义conststate={token:'我是token'}conststore=newVuex.Store({state})直接使用this.$store.state.token=='我是token'引入使用import{mapState}from'vuex'computed:{...mapState(['token'])//不取别名 this.token==  '我是token'...mapState({userToken:'token'//取别名,this.userToken ==  '我是token'})},

getter 计算属性

从 store 中的 state 中派生出一些状态

定义conststate={token:'我是token'}constgetter={tokenName(state,getters)=>{returnstate.token+'的getter '//在token后面加数据}}conststore=newVuex.Store({state,    getter})直接使用this.$store.getter.tokenName=='我是token的getter'引入使用import{mapGetters}from'vuex'computed:{...mapGetters(['tokenName'])//不取别名 this.tokenName == '我是token的getter'...mapGetters({userTokenName:'tokenName'//取别名,this.userTokenName =='我是token的getter'})},

mutation 唯一store修改方法

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation,mutation 必须同步执行

定义conststate={token:'我是token'}constmutation={setToken(state,val){state.token=val}}conststore=new Vuex.Store({state,mutation})直接使用this.$store.commit('setToken','白日梦')// token ==  '白日梦'引入使用import{mapMutations}from'vuex'methods:{...mapMutations(['setToken'])//不取别名 this.setToken('白日梦')  =>  token ==  '白日梦'...mapMutations({setName:'setToken'//取别名, this.setName('白日梦')  =>  token ==  '白日梦'})},

Action 提交的是 mutation

Action 提交的是 mutation,而不是直接变更状态。

Action 可以包含任意异步操作。

定义conststate={token:'我是token'}constmutation={setToken(state,val){state.token=val}}constAction={ActionToken({commit},val){commit('setToken',val)}}conststore=new Vuex.Store({state,mutation,Action})直接使用this.$store.dispatch('ActionToken','白日梦')// token ==  '白日梦'引入使用import{mapActions}from'vuex'methods:{...mapActions(['ActionToken'])//不取别名 this.setToken('白日梦')  =>  token ==  '白日梦'...mapActions({setName:'ActionToken'//取别名, this.setName('白日梦')  =>  token ==  '白日梦'})},

modules 模块化

每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块

当  namespaced: false    默认值为false    ,除了state取值需要添加模块名称,别的不需要添加

constuser={namespaced:false,  state:()=>({name:'小青'}),getters:{names(state){returnstate.name+'青'}},mutations:{setName(state,val){state.name=val}},actions:{setAcName({commit},val){commit('setName',val)}},}conststore=new Vuex.Store({modules:{user}})使用this.$store.state.user.name=='小青'this.$store.getters.names=='小青青'this.$store.commit('setName','小周')//    name == '小周'this.$store.dispatch('setAcName','小周')//    name == '小周'

当  namespaced: true ,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:

constuser={namespaced:true,  state:()=>({name:'小青'}),getters:{names(state){returnstate.name+'青'}},mutations:{setName(state,val){state.name=val}},actions:{setAcName({commit},val){commit('setName',val)}},}conststore=new Vuex.Store({modules:{user}})使用this.$store.state.user.name=='小青'this.$store.getters['user/names']=='小青青'this.$store.commit('user/setName','小周')//    name == '小周'this.$store.dispatch('user/setAcName','小周')//    name == '小周'

Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:

应用层级的状态应该集中到单个 store 对象中。

提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。

异步逻辑都应该封装到 action 里面。

只要你遵守以上规则,如何组织代码随你便

#axios和ajax

  本文主要针对Ajax,Promise,Axios三者的本质、优缺点,使用实战做了阐述,抽象了应用办法,高度横向做了对比,一起进入学习吧~

一、Ajax

AJAX:异步 JavaScript 和 XML,用来发送异步请求。有了Ajax之后,在无需重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。

Ajax是基于现有的Internet标准,联合使用了XMLHttpRequest,JS/DOM,CSS,XML等技术。

它有个较大的缺陷就是业务逻辑需要在回调函数中执行,如果多个请求存在依赖关系,就会产生回调地狱问题,对读写理解困难。

1. 创建XHR

  由于IE7以下版本不支持XMLHttpRequest对象,使用 ActiveXObject,因此需要做兼容处理:

letrequest;// code for IE7+, Firefox, Chrome, Opera, Safariif(window.XMLHttpRequest){request=newXMLHttpRequest();}else{// code for IE6, IE5request=newActiveXObject("Microsoft.XMLHTTP");// 新建Microsoft.XMLHTTP对象}

2. XHR对象常用方法

(1)XMLHttpRequest.open()

初始化一个请求。已激活的请求再次调用此方法时,相当于调用了abort(),会中断上一个请求。

当设置了async(第三个参数)true之后,请规定onreadystatechange事件中的就绪状态执行响应函数。

(2)XMLHttpRequest.send()

向服务器发送http请求。如果open()中定义的是异步请求,则此方法会在请求发起后立刻返回;如果是同步,则在请求返回后才执行。

(3)XMLHttpReqeust.abort()

当请求已经发出,则立刻中断请求。将readystate设置为0,立刻调用onreadystatechange方法执行回调函数。

(4)XMLHttpRequest.setReqeustHeader()

用于给HTTP请求增加自定义请求头,在open()之后,send()之前定义。设置多个相同的请求头,在发起时会进行合并。

(5)XMLHttpRequest.getResponseHeader()

根据名称获取HTTP请求头,如果需要一次请获取全部,请使用getAllHeetResponseHeader()方法。

3. 封装一个原生的Ajax请求

  结合以上的内容,封装一个兼容Post和Get的Ajax请求如下:

ajaxRequest(method,url,data,callback){letxhr;if(window.XMLHttpRequest){xhr=newXMLHttpRequest();}else{xhr=newActiveXObject("Microsoft.XMLHTTP");}if(method.toLocalCase()==="get"){url=url+this.getParams(data);xhr.open(method,url,true);xhr.send();}else{// postxhr.open(method,url,true);xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");data?xhr.send(data):xhr.send()}xhr.onreadystatechange=function(){if(xhr.readyState===4){if(xhr.state===200){// 约定返回字符串格式,如是xml格式使用responseXMLcallback(xhr.responseText);}else{reject(newError(xhr.statusText))}else{if(xhr.readyStatue===0){alert("请求已取消");}}}}buildGetParams(obj){if(!obj||obj.keys.length===0)return'';letstr=Object.keys(obj).reduce(function(prev,cur,index){if(index===1){prev=prev+'='+obj[prev]}returnprev+(prev?'&':'')+cur.toString()+'='+obj[cur]})}

  模拟使用post调用

this.ajaxRequest('post','http://demo-domain:8080/test/ajax_post',{name:'test'},function(data){letresult=JSON.parse(data)console.log(result)});

二、Promise

  Promise对象由ES6提供,它表示一个尚未完成且预计在未来完成的异步操作。Promise 仅仅是一种决定异步任务状态确定时会出现什么结果的技术。它本身并不提供异步请求功能,更不能替代Ajax。

Promise将异步请求的回调操作,改成了同步的链式写法,最直观的就是使用Promise处理ajax请求时,可以解决回调地狱问题;另外,Promise封装了统一的接口,使得控制异步操作变得更加简单。

但它的缺点也比较明显,发起的Promise无法取消中断,其次,如果不设捕获异常的回调函数,Promise内部抛出的错误无法反应到外部出来。另外,未执行完成的Promise也无法确定是刚开始还是即将结束(即ajax的readyState在哪个过程)。

1. 创建Promise

Promise自身提供了封装好的Promise()构造器,使用new关键字创建。我们可以自定义一个异步函数,传入Promise的构造器如:Promise(asyncFn)

letpromise=newPromise((resolve,reject)=>{// 执行结束需要使用resolve或reject将结果返回})

2. Promise的基本API

Promise有三种状态,分别是pending:执行状态、fulfilled:已成功、rejected:已失败。

pending只能改变为fulfilled或rejected其中一种,且状态一旦改变,将凝固不再改变。即使再次修改,该操作也会被忽略。

(1)Promise.prototype.then(onFulfilled, onRejected)

当Promise的状态凝固后,就会调用.then方法。该方法接受两个回调函数,即成功回调函数resolve(),和失败回调函数reject(),并且返回一个Promise。通常情况下,我们通过一定的条件判断如res.code为1表示成功,将结果作为参数即resolve(res.data)传递出去。否则使用reject(res.message)返回错误。

当然,.then()的第二个参数非必选,你也可以通过.catch()API来实现

(2)Promsie.prototype.catch(onRejected)

该API表示Promise从pending状态改变为rejected状态时调用,接受一个失败回调函数,返回一个Promise,使用效果同.then()的第二个参数。

(3)Promise.prototype.all()

该方法接受一个数组,用于将多个Promise包装成一个Promise来执行,只有所有的请求状态都凝固后才改变自己的状态。并且只有在全部请求都返回fulfilled,该函数才返回fulfilled

(4)Promise.prototype.race()

竟速模式,该方法接受一个可遍历对象,将多个Promise包转成一个Promise来执行,最先成功(fulfilled)返回的请求当作该Promise的响应返回

......

3. 一种建议的Promise链式写法

虽然.then()方法很强大,但并不建议在其中定义处理异常方法,原因是如果在.then()的onFulfilled回调函数中发生了异常,其内部定义的onRejected是无法捕获到的。但是在.then()后面使用.catch()则可以捕获到之前发生的所有异常。一种健康的写法是:

functiontaskA(){...};functiontaskB(){...};functionfinalTask(){...};varpromise=newPromise();promise.then(taskA).catch(onRejectedA).then(taskB).catch(onRejectedB).then(finalTask)

】不要使用promise.then(taskA).then(taskB).then(finalTask).catch(onRejected),虽然这么定义所有的异常也都会被捕获到,但如果是taskA发生了异常,那taskB,fianlTask也将不被执行。

4. 使用Promise写法实现Ajax请求

  为了简便以下只以Get请求为例,

getRequest(url){returnnewPromise((resolve,reject)=>{letxhr=newXMLHttpRequest();xhr.open('get',url,true);xhr.send();xhr.onreadystatechange(){if(xhr.readyState===4){if(xhr.status===200){resolve(JSON.parse(xhr.responseText));}else{reject(newError(xhr.statusText))}}}})}// 调用this.getRequest("http://demo-domain:8080?name=test").then(resp=>{console.log(resp)})

三、Axios

Axios是一个基于Promise的http库,支持node端和浏览器端,支持拦截器、自定义请求头、取消请求等高级配置,支持自动转换JSON,也支持npm包的使用。总之,优点多多,用就对了...

针对Axios,在本文笔者不想写简单通用的功能,如有需要可以去axios官网学习了解。笔者想阐述更高效的用法如下

1. 自定义axios实例

  使用自定义配置新建一个简单的axios实例

letrequest=axios.create({baseURL:'http://some-domain.com/api/',timeout:1000,headers:{'X-Custom-Header':'footbar'}})

2. 常用axios的请求配置

以下配置中,只有url是必须的,如果没有配置method,将默认使用get方法。以下为常用的配置,更多请参见官网。

url: '/user'用户请求服务器资源的 URL

method: 'post'创建请求时使用的方法

baseURL: 'http://demo-domain:8080/api/'自动加在URL(非绝对路径时)前的路径

headers: { 'x-Requested-With': 'XMLHttpRequest' }自定义请求头

params: { 'ID': '12345' }与请求一起发送的URL参数,当method指定为GET时使用

data: { 'name': 'zhangfs' }请求主体参数,用于PUT,POST,PATCH方法

timeout: 8000请求超时时间,0表示无超时;超过时间请求被中断

withCredentials: false请求跨域时是否需要使用凭证

auth: { username: 'zhangfs' }用于HTTP基础验证,将复写Authorization头

proxy: {

host: '127.0.0.1',

port: 9000,

}代理服务器的主机名与端口,将设置Proxy-Authoriation

cancelToken: new CancelToken(function(cancel) { ... })指定取消请求的cancel token

全局默认配置

使用axios.defaults.xxx来配置;注意在实例中配置的优先级高于默认配置优先级

letrequest=newaxios({baseURL:'http://demo-domain.com:8080/api'})request.defaults.header.common['Authorization']=AUTH_TOKENrequest.defaults.header.post['Content-Type']='application/x-www-form-urlencoded'

3. 请求拦截器

在请求或响应被 then 或 catch 处理前拦截它们。如果自定义了axios实例如request,则使用request.interceptors.xxx

// 添加请求拦截器axios.interceptors.request.use(function(config){// 在发送请求之前做些什么returnconfig;},function(error){// 对请求错误做些什么returnPromise.reject(error);});// 添加响应拦截器axios.interceptors.response.use(function(response){// 对响应数据做点什么returnresponse;},function(error){// 对响应错误做点什么returnPromise.reject(error);});

当然,也可以对拦截器进行移除,此时需要对拦截器显命名

varmyInterceptor=axios.interceptors.request.use(function(){/*...*/});axios.interceptors.request.eject(myInterceptor);

4. 取消请求

ajax调用请求的abort()通过改变readyState=0来进行中断请求,axios则通过cancel token取消。

Axios 的 cancel token API 基于cancelable promises proposal,它还处于第一阶段。

可以使用CancelToken.source工厂方法创建 cancel token

varCancelToken=axios.CancelToken;varsource=CancelToken.source();axios.get('/user/12345',{cancelToken:source.token}).catch(function(thrown){if(axios.isCancel(thrown)){console.log('Request canceled',thrown.message);}else{// 处理错误}});// 取消请求(message 参数是可选的)source.cancel('Operation canceled by the user.');

  还可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token

varCancelToken=axios.CancelToken;varcancel;axios.get('/user/12345',{cancelToken:newCancelToken(functionexecutor(c){// executor 函数接收一个 cancel 函数作为参数cancel=c;})});// 取消请求cancel();

值得注意的是,可以使用同一个cancel token取消多个请求。

5. 一个axios实战实例

  请求封装文件service.js

constservice=axios.create({headers:{},baseURL:Vue.prototype.configer.baseURL,timeout:8000,withCredentials:true})service.interceptors.request.use(config=>{if(config.method==='get'){if(config.url.indexOf('?')<0){config.url+='?r='+newDate().getTime()}else{config.url+='&r='+newDate().getTime()}}returnconfig},error=>{returnPromise.reject(error)})service.interceptors.response.use(response=>{// 处理返回体constresult=response.dataconstcode=Number(result.code)/**

    * Desc: 接口请求业务异常统一处理

    * Desc: 如:50008: 非法Token; 50012: 第三端登录; 50014: Token已过期;

    */if(code===3002||code===50008||code===50012||code===50014||code===302){alert('您已退出登录')}else{if(result.code&&result.code!=='200'){console.log(result.message)}returnresult}},errorFn// HTTP Code异常统一处理)

  api封装文件 api.js

importservicefrom'@/plugins/service'importaxiosfrom'axios'constCancelToken=axios.CancelTokenexportfunctionqueryDemoApi(data,_this){returnrequest({url:'/packageRoute/queryDemoApi',method:'post',data,cancelToken:newCancelToken(functionexecutor(c){_this.cancelAjax=c})})}

  组件内使用 info.vue

data(){return{cancelRequest:null}}method:{pageTriggleSearch(username){if(typeofthis.cancelRequest==='function'){this.cancelRequest()}this.queryInfoApi({name:username},this).then((data)=>{this.cancelRequest=null// do something else})}}

#vue-router

SPA

SPA单页面应用程序:只有一个页面(index.html),使用vue-router实现组件的创建与销毁,在视觉上给人一种“页面切换”的错觉。所以,对SPA应用来讲,vue-router是核心。

思想:在SPA应用程序中,一切皆组件,所有的组件都直接或间接地与“路由系统”有关。

路由规则是如何配置的?

创建router实例,在main.js挂载

什么是“嵌套路由”?在“路由规则”如何配置“嵌套路由”?

children叫做“嵌套路由”

自定义路由规则,如何理解路由规则?

所谓的路由规则,当你访问某个url时,路由系统就加载指定的组件进行显示

两个全局组件

<router-link>

常用属性to/tag/active-class/exact-active-class等

表示目标路由的链接。当被点击后,内部会立刻把 to 的值传到 router.push(),所以这个值可以是一个字符串或者是描述目标位置的对象。

详细请见官网

<router-view>

<router-view> 组件是一个 functional 组件,渲染路径匹配到的视图组件。<router-view> 渲染的组件还可以内嵌自己的 <router-view>,根据嵌套路径,渲染嵌套组件。

其他属性 (非 router-view 使用的属性) 都直接传给渲染的组件, 很多时候,每个路由的数据都是包含在路由参数中。

因为它也是个组件,所以可以配合 <transition> 和 <keep-alive> 使用。如果两个结合一起用,要确保在内层使用 <keep-alive>

<transition>

        <keep-alive>

                <router-view></router-view>

        </keep-alive>

</transition>

两个内置API

$route描述是当前url的信息(它是一个响应式变量,可以参与计算属性,还可以使用watch监听)

$router是路由API,常用方法有 push()入栈、replace()替换入栈、back()出栈。

this.router.push(′/hot?list=′+c.cate)改变url,接着route变化

两种路由模式

Hash模式

url上有个#,背后是监听onhashchange事件来实现的;hash值发生变化,客户端不会向服务器发起页面请求;hash模式的SPA应用部署到服务器上时不会出404问题

History模式

url上没有#,背后是使用history api来实现的;当url路径发生变化时,客户端会向服务器发起页面请求;history模式的SPA应用部署到服务器上时会出现404,怎么办?(在服务器使用Nginx做重定向处理)

两种路由跳转

声明式路由跳转,使用实现页面跳转,一般用于菜单上

编程式路由跳转,使用this.$router这个api实现页面跳转,一般用于js逻辑中

两种命名

命名路由

命名路由,就是给“路由规则”添加一个名字

在声明式导航中使用“命名路由”,<router-link :to='{name:'vip'}'>

在编程式导航中使用“命名路由”,this.push({name:'vip'})

命名视图

命名视图,给添加一个名字。那么路由规则这样配置{path,components:{xxx:'组件'}}。

两种路由传参

动态路由传参

“动态路由”一般用于从列表页跳转到详情页,“路由规则”配置一般这种风格{path:'/detail/:id'}。那么在详情页我们可以使用this.$route.params来接收这个“动态的路由参数”。还在在“路由规则”上开启props传参{path:'/detail/:id', props:true},在详情页使用props接收这个“动态的路由参数”。

Query传参

在路由跳转时使用?a=1&b=2这种查询字符串,在另一个页面中使用this.$route.query来接收

两个优化

重定向

给未定义的url添加默认跳转,跳转到指定的路由规则上,像这样配置{path:'/*',redirect:'/404'}。这是一种用户体验层面的优化。

一般放在路由规则的最后一条,并且重定向到一个已定义过的规则

路由懒加载

当页面太多时,我们要根据url访问需求按需加载组件。背后技术原理是Vue异步组件和Webpack代码分割技术。扩展:这种()=>import()代码是异步的,并且在webpack打包时只要遇到这种语法,会默认将其分割一个独立的小的.js文件。

两个高级技巧

导航守卫

我们知道每次发生url变化时,路由系统要根据“路由规则”去寻找对应的组件进行显示。vue-router把这个匹配的过程抽象成几个重要的“路由钩子”——beforeEach()/beforeResolve()/afterEach()。导航守卫经常用于登录拦截、权限设计等。

路由元信息

一种方便我们给“路由规则”添加自定义属性的方式,并且可以在$route上进行访问。在“路由规则”这样配置 {path,component,meta:{}}。通常路由元信息可以配置导航守卫实现权限设计

其它路由小技巧

路由别名

{path:'/home', alias:'/h' },路由别名的作用是给复杂的path取一个方便记忆的“小名”

过渡行为

在外面包裹一个动画,让页面切换时更加温和

数据获取

使用watch监听route的变化,当route变化成功后调接口

滚动行为

使用 new VueRouter({scrollBehavior}) 这个选项精确地控制页面的滚动位置

#proxy/reflect


Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。

Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。

Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,更优雅。它的方法与 Proxy 是对应的。

基本用法

var proxy = new Proxy(target, handler)

target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。

lettarget={name:'ZhangSan',age:28}lethandler={get:function(target,key){console.log('get: '+key);returntarget[key];// 不是target.key},set:function(target,key,value){console.log('set: '+key);target[key]=value;}}letproxy=newProxy(target,handler)proxy.name// 实际执行 handler.getproxy.age=25// 实际执行 handler.set

常用方法

// 在判断代理对象是否拥有某个属性handler.has()// 在读取代理对象的某个属性时触发handler.get()// 在给代理对象的某个属性赋值时触发。handler.set()// 在删除代理对象的某个属性时触发该操作handler.deleteProperty()// 在获取代理对象的所有属性键时触发handler.ownKeys()// 在调用一个目标对象为函数的代理对象时触发handler.apply()

实际运用

私有属性保护

letproxy=newProxy({},{get(target,key,receiver){if(key.startsWith('_')){thrownewErro(`Invalid attempt to get private    "${key}"`);}console.log('Get:'+key);returntarget[key];}});letobj=Object.create(proxy);obj.name// Get: name

参数校验

letvalidator={set:function(obj,prop,value){if(prop==='age'){if(!Number.isInteger(value)){thrownewTypeError('The age is not an integer');}if(value>200){thrownewRangeError('The age seems invalid');}}obj[prop]=value;}};letproxy=newProxy({},validator)proxy.age='oppps'// 报错

Reflect

ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上

Reflect 对象对某些方法的返回结果进行了修改,使其更合理。

Reflect 对象使用函数的方式实现了 Object 的命令式操作。

方法

Reflect.get(target,name,receiver)Reflect.set(target,name,value,receiver)Reflect.apply(target,thisArg,args)Reflect.construct(target,args[,newTarget])Reflect.defineProperty(target,name,desc)Reflect.deleteProperty(target,name)Reflect.has(target,name)Reflect.ownKeys(target)Reflect.preventExtensions(target)Reflect.isExtensible(target)Reflect.getOwnPropertyDescriptor(target,name)Reflect.getPrototypeOf(target)Reflect.setPrototypeOf(target,prototype)

eg:

letexam={name:"Tom",age:24,getinfo(){returnthis.name+this.age;}}Reflect.get(exam,'name');// "Tom"// 当 target 对象中存在 name 属性的 getter 方法, getter 方法的 this 会绑定 // receiverletreceiver={name:"Jerry",age:20}Reflect.get(exam,'info',receiver);// Jerry20

与proxy组合使用

Reflect 对象的方法与 Proxy 对象的方法是一一对应的。所以 Proxy 对象的方法可以通过调用 Reflect 对象的方法获取默认行为,然后进行额外操作。

letexam={name:"Tom",age:24}lethandler={get:function(target,key){console.log("getting "+key);returnReflect.get(target,key);},set:function(target,key,value){console.log("setting "+key+" to "+value)Reflect.set(target,key,value);}}letproxy=newProxy(exam,handler)proxy.name="Jerry"proxy.name// setting name to Jerry// getting name// "Jerry"

运用

实现观察者模式

// 定义 Set 集合constqueuedObservers=newSet();// 把观察者函数都放入 Set 集合中constobserve=fn=>queuedObservers.add(fn);// observable 返回原始对象的代理,拦截赋值操作constobservable=obj=>newProxy(obj,{set});functionset(target,key,value,receiver){// 获取对象的赋值操作constresult=Reflect.set(target,key,value,receiver);// 执行所有观察者queuedObservers.forEach(observer=>observer());// 执行赋值操作returnresult;}

// 原对象letobj={name:'steve',age:20}// data 是我们要拦截的 原对象// dataProxy 是我们新生成的 拦截对象letdataProxy=newProxy(obj,{get(target,key,receiver){// target 就是我们的原对象,objconsole.log("target >>> ",target===obj);// key 就是操作的健console.log("key >>> ",key);// receiver 就是 dataProxy,拦截对象console.log("receiver >>> ",receiver);console.log(`当前值为:${target[key]}`);returntarget[key];},});// 使用拦截器dataProxy.name;// 打印结果:// -> 当前值为:steve// -> "steve"

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343