杭州端点一面"/>
杭州端点一面
目录
1、vue双向绑定
2、Promise了解过吗?promise.all解释一些,为什么可以链式回调
3、let和var的区别
4、深拷贝和浅拷贝区别和实现方式
5、父子组件传值
6、了解webpack吗?loader是干什么的
7、跨域方法
9v-model实现
10改变this指向
11、实现数组map
12、了解过every吗?
13、token验证
14vue生命周期
15、vue2响应式原理
16CORS
17说一下instanceof内部实现
18说一下this
19{}==={}?
20Object和new()区别
21说一下闭包
22、说一下原型链
23、原生js继承你知道哪些,说下优缺点
24、事件冒泡
25缓存
26xss
27crfs
28es6新方法知道哪些
29promise
30Vuex
1、vue双向绑定
Vue是采用数据劫持结合发布者订阅者模式的方式,通过object.defineproperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发响应的监听回调
首先要对数据进行劫持监听,我们需要设置一个监听器observer来监听所有的属性,如果属性发生变化,就需要告诉订阅者watcher看是否需要更新。因为订阅者有多个,所以需要一个消息订阅器Dep来专门收集这些订阅者,然后在监听器和订阅者之间进行统一管理。接着我们还需要一个指令解析器compiler,对每个节点元素进行扫描和解析,将相关指令(v-model,v-on)对应初始化为一个订阅者watcher,并替换模板数据 或绑定相应的函数,此时当订阅者watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图
2、Promise了解过吗?promise.all解释一些,为什么可以链式回调
promise是异步编程的一种解决方案,从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果,promise有三种状态,pending(等待状态),fulfilled(成功态),rejected(失败状态),状态一旦改变,就不会改变,创造promise实例后,它会立即执行
promise是用来解决回调地狱的
promise.all有一个失败,promise失败,返回值是第一个失败的promise结果
promise.race类似all,有任意一个完成就算完成
promise俗称链式调用,它是ES6中最重要的特性之一。简单的说可以不停的then调用嵌套再调用(异步之后,链式调用方式执行回调),这种操作方式称为promise
promise包含两个参数:resolve,reject
resolve:将promise的状态设置为完成状态(resolved),此时then方法捕捉变化,执行成功的回调
reject:将promise状态设置为无效(rejected),then方法执行失败回调
3、let和var的区别
用var声明的变量的作用域是它当前的执行上下文,即如果是在任何函数外面,则是全局执行上下文,如果实在函数里面,则是当前函数执行上下文。换句话说,var声明的变量的作用域只能是全局或者整个函数块
而let声明的变量的作用域则是它当前所处代码块,即它的作用域可以是全局或者整个函数块,也可以是if,while,switch等用{}限定的代码块
另外,var和let的作用域规则都是一样的,其声明的变量只在其声明的块或子块中可用
let声明的变量的作用域可以比var声明的变量的作用域有更小的限定范围,更灵活
var允许在同一作用域中重复声明,而let不允许在同一作用域中重复声明,否则会抛出异常
var在全局环境声明变量,会在全局对象里创建一个属性,而let在全局环境里声明变量,则不会在全局对象里创建一个属性
var声明的变量在执行上下文创建阶段就会被创建和初始化,因此对于执行阶段来说,可以在声明之前使用
而let声明的变量在执行上下文创建阶段只会被创建而不会被初始化,因此对于执行阶段来说,如果在其定义执行前使用,相当于使用了未被初始化的变量,会报错
4、深拷贝和浅拷贝区别和实现方式
深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体而不是引用
假设B复制了A,修改A的时候,看B是否发生变化
如果B跟着变化了,说明时浅拷贝,修改堆内存中的同一个值
如果B没有变,说明时深拷贝,修改堆内存中的不同的值
浅拷贝只是增加了一个指针指向已经存在的内存地址
深拷贝是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存
浅拷贝的实现:
var a = [1, 2, 3, 4, 5];
var b = a;
a[0] = 2
console.log(a);
console.log(b);//因为b浅拷贝a, ab指向同一个内存地址(堆内存中存的值)
//b会随着a的变化而变化
//[2, 2, 3, 4, 5]
//[2, 2, 3, 4, 5]
深拷贝的实现:
function deepClone(obj){var newObj = obj instanceof Array ? []:{};if(typeof obj !== 'object'){return obj;}else{for(var i in obj){newObj[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i];}}return newObj;}var a = [1, 2, 4, 6, "a", "12", [1, 2]];var b = deepClone(a);a[3] = 7;console.log(a);console.log(b);
//b对象并没有因为a对象的改变而改变,因为b深拷贝a
[ 1, 2, 4, 7, 'a', '12', [ 1, 2 ] ]
[ 1, 2, 4, 6, 'a', '12', [ 1, 2 ] ]
5、父子组件传值
父向子组件传递数据是通过props,子向父组件传递数据是通过$emit
通过父链/子链进行数据传递(parent和children)
通过ref也可以访问组件实例
依赖注入:provide/inject
6、了解webpack吗?loader是干什么的
webpack是一个模块打包器,webpack在打包的过程中,会分析各个文件之间的依赖关系,然后生成一个依赖图并用文件的形式保存下来,未来浏览器运行代码的时候就可以读取这个文件,就知道了各个代码块之间的关联以及如何调用了。
打包是webpack最核心的功能,webpack其他所有的功能都是为了让打包这个功能更好。我们从一个简单的HTML页面介绍了通过webpack对模块进行打包,既保留了单个模块的可维护性,又减少了页面的http请求,减少了页面加载时间,从而增加了页面的显示速度,让整个应用体验更好。
loader让webpack能够去处理那些非JavaScript文件,loader可以将所有类型的文件转换为webpack能够处理的有效模块,然后你就可以利用webpack的打包能力,对它们进行处理。本质上,webpack loader将所有类型的文件,转换为应用程序的依赖图(和最终的bundle)可以直接引用的模块
webpack需要loader预处理,因为它只认识js。
所以loader就是个搞预处理工作的,它的职业是人类世界的翻译官
loader作为一个翻译官,它的工作就是把不同类型的资源(所有不是js的),翻译给webpack这个大文盲;
因为webpack的母语是js(只认识js),如果没有loader帮它,webpack就无法顺利开展它的本职工作(打包)
而loader作为翻译官,它的手里有很多字典,比如s-loader,url-loader,vue-loader,file-loader等,真是个文化人啊
当loader遇到不同国家(类型)的文件的时候,就需要拿出不同的字典,即安装的不同的loader,webpack不自带loader,需要npm安装,这也就是所谓的预处理的含义
7、跨域方法
跨域就是一个域通过某种方式请求另一个域的数据,跨域是指浏览器不能执行其他网站的脚本,它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制
同源策略限制了以下行为:
cookie、localstorage和indexDB无法读取
DOM和js对象无法获取
ajax请求发送不出去
所谓同源就是指域名、协议、端口号均相同
解决方法:1、jsonp,缺陷是只能实现get请求,工作原理是利用script没有跨域限制的漏洞,除了script还有img,link标签不受跨域影响
cors,cors支持所有类型的http请求,jsonp的优势在于支持老式浏览器
8、cookie和localstorage
cookie数据始终在同源的http请求中携带,即cookie在浏览器和服务器之间来回传递,并且存储大小限制也不同,cookie数据不能超过4k,同时每次HTTP请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识。而sessionStorage和localSTorage不会自动把数据发送给服务器,仅在本地保存,sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M甚至更大
数据有效期不同,sessionSTorage仅在当前窗口关闭前有效,自然不能长期保存,localstorage始有效,窗口或浏览器关闭也一直保存,因此用作持久数据,cookie只在设置的cookie期限之前一直有效,即使窗口或浏览器关闭
作用域不同,sessionStorage不在不同的浏览器窗口共享,即使是同一个界面,localStorage在所有的同源窗口都是共享的,cookie也是在所有同源窗口都是共享的
9v-model实现
10改变this指向
call,apply,bind
apply和call的用法基本一致,唯一的区别在于,当函数需要传递多个变量的时候,apply可以直接接收一个数组作为参数输入,而call则是接受一系列的单独变量
和call方法很相似,第一个参数是this,从第二个参数开始接收的参数列表,区别在于bind方法返回值是函数以及bind接收的参数列表的使用,bind方法不会立即执行,而是返回一个改变了上下文this后的函数,而原函数printName中的this并没有被改变,依旧指向全局对象window
call和apply和bind存在的区别
bind返回对应函数,便于稍后调用,apply,call则是立即调用
11、实现数组map
12、了解过every吗?
every和some用法:
共同点:
1、只能遍历数组
2、符合条件即可跳出循环(可跳出循环)返回布尔类型
3、用法相同,接收三个参数:item(当前项),index(当前索引),arr(数组本身)
不同点:
every:一项为false就返回false,全为true则返回true
some:一项为true则返回true,全为false则返回false
every全true返回true,some一true返回true
13、token验证
在第一次登录的时候前端调用后端的接口,把用户名和密码传给后端
后端收到请求,验证用户名和密码,验证成功后,返回给前端一个token值
前端收到后端传给的token值,将token存储在本地localstorage和vuex中。
前端每次路由跳转的时候,就判断localstorage中是否有token,如果没有就跳转到登录页面,如果有就跳转到响应页面
分装-公用的请求接口方法,每次请求调用后端接口,都在请求头中带上token
后端判断请求头中是否有token,如果有token就拿到token并验证token,验证成功返回数据,验证失败(例如token过期),就返回给前端一个状态码,一般是401,请求头中没有token也返回401
如果前端拿到后台返回状态码是401,就清除token并跳转登录页面
14vue生命周期
创建前:此时还没有初始化好data和methods属性
创建后:此时data和methods已经在内存中创建完成,但是还没有开始编译模板
挂载前:此时已经完成了模板的编译,但是还没有挂载到页面中显示
挂载:此时将已经编译好的模板,挂载到了页面指定的容器中显示
更新前:数据更新前,此时data中的状态值是最新的,但是页面上显示的数据还是旧的,因为此时没有开始重新渲染DOM节点
更新后,data中的状态值和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了
销毁前,实例扔完全可用
销毁后,事件监听被移除,子实例也会被销毁
15、vue2响应式原理
Vue是采用数据劫持结合发布者订阅者模式的方式,通过object.defineproperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调
首先要对数据进行劫持监听,我们需要设置一个监听器observer来监听所有的属性,如果属性发生变化,就需要告诉订阅者watcher看是否需要更新。因为订阅者有多个,所以需要一个消息订阅器Dep来专门收集这些订阅者,然后在监听器和订阅者之间进行统一管理。接着我们还需要一个指令解析器compiler,对每个节点元素进行扫描和解析,将相关指令(v-model,v-on)对应初始化为一个订阅者watcher,并替换模板数据 或绑定相应的函数,此时当订阅者watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图
16CORS
跨域资源共享 CORS 详解 - 阮一峰的网络日志 (ruanyifeng)
允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制
CORS需要浏览器和服务器同时支持,目前,所有浏览器都支持该功能,IE浏览器不能低于IE10.
整个CORS通信过程,都是浏览器自动完成,不需要用户参与,对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样,浏览器一旦发现AJAX请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信
浏览器将CORS请求分成两类:简单请求和非简单请求
CORS与JSOP的使用目的相同,但是比JSOP更强大
JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSOP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据
17说一下instanceof内部实现
深入理解instanceof实现原理_wyhstars的博客-CSDN博客_instanceof实现原理
在JavaScript中,有7种基本类型(包括6中原始类型以及对象类型)。
在某些场景下,可能会需要我们做类型的判断,通常我们使用typeof。但是typeof在进行类型判断时有局限---typeof对于对象来说,除了函数都会显示object。因此,我们可以考虑使用instanceof来进行类型判断,因为instanceof内部机制是通过原型链来实现的
18说一下this
普通函数this指向window对象(严格模式下undefined),作为某个对象的方法调用时,this指向该对象,被间接调用时,指向call/apply/bind的第一个参数,若第一个参数为null,参考第一条,普通函数作为构造函数,用new关键字创造实例时,this指向新创建的实例
箭头函数由外部非箭头函数的this决定,继承当前上下文的this关键字
普通函数可以作为构造函数来使用,用new关键字来创建对象实例
箭头函数不能作为构造函数
箭头函数不绑定arguments
19{}==={}?
20Object和new()区别
21说一下闭包
JavaScript语言的特别之处就在于函数内部可以直接读取全局变量,但是在函数外部无法获取函数内部的局部变量
出于种种原因,有时候需要获取内部的局部变量,正常情况下是办不到的,只有通过变通的方法才能实现,那就是在函数内部,再定义一个函数
JavaScript语言特有的链式作用域结构,子对象会一级一级地向上寻找父对象的变量,所以,父对象的所有变量,子对象都是可见的,反之则不成立
闭包就是能够读取其他函数内部变量的函数,闭包的用途一个是可以读取函数内部的变量,另一个就是让这些变量始终保存在内存中。由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能会导致内存泄漏,解决的方法是在退出函数之前,将不使用的局部变量全部删掉
闭包会在父函数外部,改变父函数内部变量的值,所以,如果你把父函数当做对象使用,把闭包当做它的共用方法,把内部变量当做它的私有属性,这时一定要小心,不要随便改变父函数的内部变量的值。
22、说一下原型链
一、原型
这个图像生动的表达了js中的构造函数、实例对象以及原型对象的关系
每个对象都有__proto__属性,并且指向它的原型对象
每个构造函数都有它的prototype原型对象
prototype原型对象里的constructor指向它的构造函数
new一个构造函数会形成它的实例对象
二、原型链
从概念上来看,每个对象都有一个原型,这个原型还可以有它自己的原型,以此类推,形成一个原型链
那么什么时候会用到一个原型链呢?当我们查找特定属性的时候,我们先去这个对象里找,如果没有的话就去它的原型对象里面去,如果还有没有的话再去原型对象的原型对象里去寻找,这个操作就是被委托在整个原型链上
如果没找到,会一直找下去吗?原型链是有终点的,不会一直找下去,当Object.prototype.__proto__===null时,查找结束,返回undefined
23、原生js继承你知道哪些,说下优缺点
原生js实现继承的三种方式_sunlando的博客-CSDN博客
1、构造函数继承
原理:利用call,apply,bind等方法,在构造函数内部去调用别的构造函数,为己所用。
缺点:原型对象的函数无法复用
function Parent (name,age) {this.name = namethis.age = age}function Child (name,age,gender){Parent.call(this,name,age)this.gender = gender}const ming = new Child('ming',18,'男')console.log(ming);
2、原型链继承
优点:实现了原型对象内的函数复用
缺点:虽然得到了属性值,但无法在实例对象内灵活的传参,根本不能用
function Parent (name,age) {this.name = namethis.age = age}Parent.prototype.sayName = function (){console.log(this.name);}function Child (name,age,gender){this.gender = gender}Child.prototype = new Parent('gio',19)Child.prototype.constructor = Childconst ming = new Child('ming',18,'男')ming.sayName();console.log(ming.name);console.log(ming.age);console.log(ming.gender);
3、组合继承
原理:构造函数继承与原型链继承的组合使用
优势:既实现了原型对象内的函数复用,又可以在实例对象内灵活的传参
function Parent (name,age) {this.name = namethis.age = age}Parent.prototype.sayName = function(){console.log(this.name);}function Child (name,age,gender){Parent.call(this,name,age)this.gender = gender}Child.prototype = new Parent()Child.prototype.constructor = Child// 注意:单独设置的功能需要在继承操作后设置。Child.prototype.sayGender = function(){console.log(this.gender);}const ming = new Child('ming',18,'男')ming.sayName()ming.sayGender()console.log(ming.name);console.log(ming.age);console.log(ming.gender);
24、事件冒泡
事件流
当页面中放了一个按钮,你用鼠标点击这个按钮,虽然你只是点击了这个按钮,但其实你也同时点击了这个按钮的所有父级元素
事件流描述的是页面中接收事件的顺序
DOM事件流
DOM2级事件规定事件流包含三个阶段:
事件捕获阶段
处于目标阶段
事件冒泡阶段
事件冒泡:
即事件从下级传递给上级。如果同时给儿子和父亲定义了事件,那么点击儿子,触发儿子的事件之后,也会再触发父亲的事件。浏览器是默认事件冒泡行为的
但是在开发的过程中,我们大多数情况下不需要事件冒泡,我们想要的是点击儿子事件就只触发儿子事件,而不需要将父亲的事件触发,所以阻止上一级事件的触发就叫做阻止事件冒泡
阻止事件冒泡
event.cancelBubble = true;
或
event.stopPropagation()
25缓存
浏览器缓存是浏览器将用户请求过的静态资源(HTML,css,js),存储到电脑本地磁盘中,当浏览器再次访问时,就可以直接从本地加载了,不需要再去服务端请求了。
但是如果处理不当可能会导致服务端代码更新了,但是用户却还是老页面。缓存的优点是减少了冗余的数据传输,节省网费,减少服务器的负担,提升网站性能,加快客户端加载网页的速度
缓存流程
我们可以认为,浏览器里有一个专门存放缓存规则的一个数据库,也可以说是一个映射表,把缓存资源信息,同电脑磁盘中的实际文件的地址,对应起来。
浏览器第一次请求资源时
上面所说的缓存规则,就是声明所请求的资源,要采取哪种缓存策略,缓存多长时间等等,而这个规则,是在http的header中返回来的
3缓存规则
强缓存和协商缓存
强缓存
简单粗暴,如果资源没有过期,就取缓存,如果过期了,则请求服务器
看cache-control的值,图中max-age=31xxxx,就是说在这些秒内,都直接使用缓存,超过了就继续请求服务器。
cache-control的几个值含义:
max-age=xxx过期时间
no-cache不进行强缓存
所以,判断该资源是否命中强缓存,就看response中的cache-control的值,如果max-age=xxx秒,则命中强缓存。如果cache-control的值是no-cache,说明没命中强缓存,走协商缓存
强缓存的步骤:
1、第一次请求a.js缓存表中没有信息,直接请求后端服务器
2、后端服务器返回了a.js且http response header中cache-control为max-age=xxx,所以是强缓存规则,存入缓存表中
3、第二次请求a.js,缓存表中是max-age,那么命中强缓存,然后判断是否过期,如果没过期,直接读缓存的a.js,如果过期了,则执行协商缓存的步骤了。
协商缓存
触发条件:
1、cache-control的值为no-cache(不强缓存)
2、或者max-age过期了(强缓存,但总有过期的时候)
也就是说,不管怎样,都可能最后要进行协商缓存(no-store除外)
这个图,虽然强缓存命中,但是也有ETag和last-modified,这两个就是协商缓存的相关规则,
ETag:每个文件有一个,改动文件就变了
last-modified:文件修改时间
也就是说,每次http返回来response header中的ETag和Last-modified,在下次请求时request header就把这两个带上(但是名字改变了ETag->if-None-Match,Last-modified->if-Modified-Since),服务端把你带来的标识,资源目前的标识,进行对比,然后判断资源是否更改了
这个过程是循环往复的,即缓存表每次请求成功后都会更新规则
1第n次请求成功时:
2缓存表中更新该资源的ETag值
3第n+1次请求
从缓存表中取该资源最新的ETag,然后加在request header中,注意变名字了,由ETag-->If-None-Match
图
所以协商缓存步骤总结
1、请求资源时,把用户本地资源的ETag同时带到服务端,服务端和最新资源做对比
2、如果资源未改动,返回304,浏览器读取本地缓存
3、如果资源有改动,返回200,读取最新资源
26xss
XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但实际上也可以包括Java,VBScript,ActiveX,flash或者甚至是普通的HTML。攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作),私密网页内容,会话和cookie等各种内容
原理
HTML是一种超文本标记语言,通过将一些字符特殊地来区别文本和标记,例如,小于符号(<)被看作是HTML标签的开始,之前的字符是页面的标题等等。当动态页面中插入的内容含有这些特殊字符(如<)时,用户浏览器会将其误认为HTML标签,当这些HTML标签引入了一段JavaScript脚本时,这些脚本程序就会在用户浏览器中执行。所以,当这些特殊字符不能被动态页面检查或检查出现失误时,就将会产生XSS漏洞
特点:
与钓鱼攻击相比,XSS攻击所带来的危害更大,通常有如下特点:
①由于XSS攻击在用户当前使用的应用程序中执行,用户将会看到与其有关的个性化信息,如账户信息或“欢迎回来”消息,克隆的web站点不会显示个性化信息
类型
从攻击代码的工作方式可以分为三个类型:
(1)持久型跨站:最直接的危害类型,跨站代码存储在服务器(数据库)。
(2)非持久型跨站:反射型跨站脚本漏洞,最普遍的类型。用户访问服务器-跨站链接-返回跨站代码。
(3)DOM跨站(DOM XSS):DOM(document object model文档对象模型),客户端脚本处理逻辑导致的安全问题。
攻击类型
常用的XSS攻击手段和目的有:
1、盗用cookie,获取敏感信息。
2、利用植入Flash,通过crossdomain权限设置进一步获取更高权限;或者利用Java等得到类似的操作。
3、利用iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻击)用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作。
4、利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。
5、在访问量极大的一些页面上的XSS可以攻击一些小型网站,实现DDoS攻击的效果。
27crfs
28es6新方法知道哪些
1、let const
块级作用域,不具备变量提升,不能重复声明
const块级作用域,变量声明不提升,在声明时必须被赋值,声明时要大写,不可修改
2、箭头函数
不需要function关键字来创建函数,省略return关键字,继承当前上下文的this关键字
3、允许对函数参数设置默认值
4、支持二进制和八进制的字面量
5、对象和数组的解构
6、对象超类
7、for...in遍历一个迭代器(例如一个数组,输出数组的值“和for...of遍历对象属性
29promise
promise是异步编程的一种解决方案,从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果,promise有三种状态,pending(等待状态),fulfilled(成功态),rejected(失败状态),状态一旦改变,就不会改变,创造promise实例后,它会立即执行
promise是用来解决回调地狱问题的
promise.all有一个失败,promise失败,返回值是第一个失败的promise结果
promise.race类似all,有任意一个完成就算完成
————————————————
30Vuex
1、state:用于数据的存储,是store中的唯一的数据源
2、getter:如vue中的计算属性一样,基于state数据的二次包装,常用语数据的筛选和多个数据的相关性计算
3、mutation:类似函数,改变state数据的唯一途径,且不能用于处理异步事件
4、actions:类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作
5、models:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护;当项目庞大,状态非常多时,可以采用模块化管理模式。Vuex运行我们将store分割成模块(module)
更多推荐
杭州端点一面
发布评论