1.实现一个简单的双向绑定
2.VUE对于数组不能更新问题的处理、defineProperty的缺陷?
3.VUE为什需要key?
4.VUE的data是对象还是函数有什么不同?
5.localhost和127.0.0.1的区别的?
6.浏览器缓存机制?强制缓存,协商缓存?
7.iframe跨框架通信,跨文档消息传递?
8.ES6新特性有哪些?
9.let,const,var对比
10.vuex
1.实现一个简单的双向绑定
//html
<main>
<p>请输入:</p>
<input type="text" id="input">
<p id="p"></p>
</main>
//js
const obj = {};
Object.defineProperty(obj, 'text', {
get: function() {
console.log('get val');
},
set: function(newVal) {
console.log('set val:' + newVal);
document.getElementById('input').value = newVal;
document.getElementById('p').innerHTML = newVal;
}
});
const input = document.getElementById('input');
input.addEventListener('keyup', function(e){
obj.text = e.target.value;
})
2.Object.defineProperty的缺陷?和proxy的对比?
https://juejin.im/post/5acd0c8a6fb9a028da7cdfaf
答:
Object.defineProperty缺陷:
(1)由于 JavaScript 的限制,Vue 不能检测以下数组的变动:当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValu
(2)当你修改数组的长度时,例如:vm.items.length = newLength
(3)有时你可能需要为已有对象赋值多个新属性,比如使用 Object.assign() 或 _.extend(),VUE2.0不能监听对象属性的变化,除非对对象深遍历。
第一个问题:
以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将在响应式系统内触发状态更新:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
你也可以使用 vm.$set
实例方法,该方法是全局方法 Vue.set
的一个别名:
vm.$set(vm.items, indexOfItem, newValue)
VUE官方文档里面写了Vue是可以检测到数组变化的,但是只有以下7种法:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
作者用一些方法hack了以上7中操作
const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
const arrayAugmentations = [];
aryMethods.forEach((method)=> {
// 这里是原生Array的原型方法
let original = Array.prototype[method];
// 将push, pop等封装好的方法定义在对象arrayAugmentations的属性上
// 注意:是属性而非原型属性
arrayAugmentations[method] = function () {
console.log('我被改变啦!');
// 调用对应的原生方法并返回结果
return original.apply(this, arguments);
};
});
let list = ['a', 'b', 'c'];
// 将我们要监听的数组的原型指针指向上面定义的空数组对象
// 别忘了这个空数组的属性上定义了我们封装好的push等方法
list.__proto__ = arrayAugmentations;
list.push('d'); // 我被改变啦! 4
// 这里的list2没有被重新定义原型指针,所以就正常输出
let list2 = ['a', 'b', 'c'];
list2.push('d'); // 4
第二个问题
为了解决第二类问题,你可以使用 splice:
vm.items.splice(newLength)
第三个问题
我们实现双向绑定时多次用遍历的方法遍历对象的属性,defineProperty的第三个缺点,只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。如果属性值也是对象,那就需要深度遍历,显然能劫持一个完整的对象是更好的选择。
Object.keys(value).forEach(key => this.convert(key, value[key]));
有时你可能需要为已有对象赋值多个新属性,比如使用 Object.assign() 或 _.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,不要像这样:
Object.assign(vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
你应该这样做:
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
Proxy
- proxy可以直接监听整个对象而非属性,并返回一个新对象。不管操作还是底层都远强于defineProperty
将上文的例子用proxy改写
const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};
const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key === 'text') {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
},
});
input.addEventListener('keyup', function(e) {
newObj.text = e.target.value;
});
- proxy可以直接监听数组的变化
const list = document.getElementById('list');
const btn = document.getElementById('btn');
// 渲染列表
const Render = {
// 初始化
init: function(arr) {
const fragment = document.createDocumentFragment();
for (let i = 0; i < arr.length; i++) {
const li = document.createElement('li');
li.textContent = arr[i];
fragment.appendChild(li);
}
list.appendChild(fragment);
},
// 我们只考虑了增加的情况,仅作为示例
change: function(val) {
const li = document.createElement('li');
li.textContent = val;
list.appendChild(li);
},
};
// 初始数组
const arr = [1, 2, 3, 4];
// 监听数组
const newArr = new Proxy(arr, {
get: function(target, key, receiver) {
console.log(key);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key !== 'length') {
Render.change(value);
}
return Reflect.set(target, key, value, receiver);
},
});
// 初始化
window.onload = function() {
Render.init(arr);
}
// push数字
btn.addEventListener('click', function() {
newArr.push(6);
});
很显然,Proxy不需要那么多hack(即使hack也无法完美实现监听)就可以无压力监听数组的变化,我们都知道,标准永远优先于hack。
- proxy有13种拦截方法,Object.defineProperty可没有
get()
set()
has()
deleteProperty()
ownKeys()
getOwnPropertyDescriptor()
defineProperty()
preventExtensions()
getPrototypeOf()
isExtensible()
setPrototypeOf()
apply()
construct()
proxy的劣势是兼容性
当然,Proxy的劣势就是兼容性问题,而且无法用polyfill磨平,因此Vue的作者才声明需要等到下个大版本(3.0)才能用Proxy重写。
3.VUE为什需要key?
答:
因为它是 Vue 识别节点的一个通用机制,key 并不仅与 v-for 特别关联。
-
key 的特殊属性主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
-
有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。
-
它也可以用于强制替换元素/组件而不是重复使用它。当你遇到如下场景时它可能会很有用:
完整地触发组件的生命周期钩子
触发过渡
<transition>
<span :key="text">{{ text }}</span>
</transition>
当 text 发生改变时,<span> 会随时被更新,因此会触发过渡。
当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。这个类似 Vue 1.x 的 track-by="$index"。
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性:
<div v-for="item in items" v-bind:key="item.id">
<!-- 内容 -->
</div>
建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
4.VUE的data是对象还是函数有什么不同?
答:
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
当我们定义这个 <button-counter> 组件时,你可能会发现它的 data 并不是像这样直接提供一个对象:
data: {
count: 0
}
取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function () {
return {
count: 0
}
}
5.localhost和127.0.0.1的区别的?
答:
-
localhost的解释是本地服务器,127.0.0.1在Windows系统的正确解释是本机地址。Windows通过本机的host文件,自动将localhost解析成127.0.0.1
-
localhost不经网络传输,所以不受网卡和防火墙等相关的网络限制。用localhost访问时,相当于系统带着本机当前的用户权限去访问,127 相当于本机通过网络再去访问本机。
6.浏览器缓存机制?强制缓存,协商缓存?
https://juejin.im/entry/5ad86c16f265da505a77dca4
7.iframe跨框架通信,跨文档消息传递
答:跨文档消息传递(cross-document-messaging)简称XDM,指的是在来自不同域的页面间传递消息。
XDM的核心是postMessage()方法。在H5中,除了XDM部分之外的其他部分也会提到这个方法,但都是为了同一个目的:向另一个地方传递数据。对于XDM而言,“另一个地方”指的是包含在当前页面的<iframe>元素,或者由当前页面弹出的窗口。
postMessage()接受两个参数:一条消息和一个表示消息接收方来自哪个域的字符串(目的地)。第二个参数对保证安全通信非常重要,可以防止浏览器把消息发送到不安全的地方。
所有支持XDM的浏览器都支持iframe的contentWindow属性
var iframeWidow = document.getElementById("myframe").contentWindow;
iframeWindow.postMessage("A secret","http://www.wrox.com");
最后一行代码尝试向内嵌框架中发送一条消息,并指定框架中的文档必须来源于"http://www.abc.com"域。如果来源匹配,消息会传递到框架中,否则,postMessage()什么也不做。这一限制可以避免窗口中的位置在你不知道的情况下发生改变。如果传递给postMessage()的第二个参数是'*',则表示可以把消息发送给来自任何域的文档,但是我们不推荐这种做法。
收到XDM消息时,会触发window对象的message事件。这个事件是以异步形式触发的,因此从发送消息到接收消息(触发接受窗口的message事件)可能要经过一段时间的延迟。触发message事件后,传递给onmessage处理程序的事件对象包含以下三方面的重要信息:
- data:作为postMessage()第一个参数传入的字符串数据。
- origin:发送消息的文档所在的域,例如"http://www.wrox.com"
- source:发送消息的文档的window对象的代理。这个代理对象主要用于在发送上一条消息的窗口中调用postMessage()方法。如果发送消息的窗口和接受消息的窗口来自同一个域,那这个对象就是window。
接收到消息后,验证发送窗口的来源是非常重要的。就像给postMessage()方法执行第二个参数,以确保浏览器不会把消息发送给未知的页面一样,在onmessage处理程序中检测消息来源可以确保传入的消息来自已知的页面。
基本的检测模式如下:
EventUtil.addHandler(window,"message",function(event){
// 确保发送消息的域是已知的域
if(event.origin == "http://www.wrox.com");
//处理接收到的数据
processMessage(event.data);
//可选,向来源窗口发送回执
event.source.postMessage("Recieved","http://p2p.poster.com");
})
还要提醒大家,event.source大多数情况下只是window对象的代理,并非实际的window对象,换句话说,不能通过这个代理对象访问window对象的其他任何信息。记住,只通过这个代理调用postMessage()就好,这个方法永远存在,永远可以调用。
XDM还有一些怪异之处。
使用postMessage()时,最好还是只传字符串。如果想传入结构化的数据,最佳选择是先在要传入的数据上调用JSON.stringify(),通过postMessage()传入得到的字符串,然后再在onmessage事件处理程序中调用JSON.parse()。
通过内嵌框架加载其他域的内容时,使用XDM是非常方便的。有了XDM包含<iframe>的页面可以确保自身不受恶意内容的侵扰,因为它只通过XDM与嵌入的框架通信。而XDM也可以在来自相同域的页面间使用。
支持XDM的浏览器有IE8+、FireFox3.5+、Safari4+、Opera、Chrome、iOS版Safari及Andriod版Webkit。XDM已经作为一个规范独立出来,现在他的名字叫 Web Messaging。
8.ES6新特性有哪些?
答:
1.不一样的变量声明const和let
2.模板字符串
3.箭头函数
4.函数参数默认值
5.对象和数组的解构赋值
6.扩展运算符
7.Symbol 基本数据类型
8.for...of和for...in
9.class
10.Map 、Set、weakMap、weakSet数据结构
11.proxy和Reflect(和object.defineProperty 的对比参考上面第二题)
12.字符串,数组扩展API
13.Module(和CommonJs的差异)
14.Promise,Async
9.let,const,var对比
答:
var
在ES6之前,js声明变量就用var关键字,在js里是没有块级作用域的概念的,意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。
function num(count) {
for (var i = 0; i < count; i++) {
console.log(i)
}
console.log(i); //0,1,2,3,4
}
num(5); //5
在Java,C++等语言中,i 在for循环结束就会销毁,但是js中,变量i 是定义在num这个函数的活动对象中,因此从它有定义开始,就可以在函数内部随处访问到。
即使错误地重新声明同一个变量,也不会改变它的值。
function num(count) {
for (var i = 0; i < count; i++) {
/* console.log(i) */
}
var i;
console.log(i); // 5
}
num(5);
js不会告诉你是否多次声明了同一个变量,遇到这种情况,它只会对后续的声明视而不见。(不过,它会执行后续声明中的变量初始化)。
function num(count) {
for (var i = 0; i < count; i++) {
console.log(i) // 0 1 2 3 4
}
var i = 9;
/* console.log(i); // 9 */
}
num(5);
function num(count) {
for (var i = 0; i < count; i++) {
/* console.log(i) // 0 1 2 3 4 */
}
var i = 9;
console.log(i); // 9
}
num(5);
匿名函数可以用来模仿块级作用域并避免这个问题。
function num(count) {
(function() {
for (var i = 0; i < count; i++) {
console.log(i) // 0 1 2 3 4
}
})()
console.log(i); // Uncaught ReferenceError: i is not defined
}
num(5);
重写后的num()函数,在for循环外部包裹一层私有作用域,在匿名函数中的任何变量都会在匿名函数执行结束后立即销毁!,因此保证了变量i只能在循环中使用。
而在这个匿名函数中可以访问count,是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量。
使用匿名函数的好处:
- 在一个有很多开发人员共同合作的项目中, 能够避免命名冲突;
- 我们应该尽量少像全局添加变量和函数,造成内存污染。
- 这种做法可以减少闭包占用内存的问题,因为没指向匿名函数的引用,只要函数执行完毕,就可以立即销毁作用域链了。
以上,《JS高编》P184
let
ES6新增了let命令。let与var的区别如下:
1.let声明的变量只在let命令所在的代码块有效。
2.var变量会发生“变量提升”的现象,即变量可以在声明之前使用,值为undefined。let命令改变了语法行为,他所声明的变量一定要在声明之后才能使用。
console.log(foo); // undefined
var foo = 2;
console.log(bar); // eReferenceError
let bar = 2;
3.let存在暂时性死区TDZ。只要块级作用域内存在let或const命令,它所声明的变量就绑定这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferrenceError
let tmp;
}
4.let不允许在相同作用域内重复声明同一个变量
//报错
function() {
let a = 1;
let a = 2;
}
//报错
function() {
let a = 1;
var a = 3;
}
//报错
function() {
var a = 1;
let a = 9;
}
const
- const声明的是一个只读的常量,一旦声明,必须立即初始化,不能留到以后赋值。
2.与let相同点:作用域与let相同,也同样存在暂时性死区,不可重复声明。
3.const实际保证的是变量指向的内存地址不可变,所以用const定义对象要格外小心。对象的数据结构完全不受控制。如果想将对象冻结,可以使用
const Foo = Object.freeze({});
- ES6为了逐步将全局变量与顶层对象的属性隔离,规定let ,const,class命令声明的变量不属于顶层对象的属性。但是为了保证旧代码的兼容性,var和function命令声明的全局变量依旧是顶层对象的属性,可以用window 访问。
- vuex
答:
当遇到以下两种情况时,vue比较难处理,组件间传值无论如何也不是个好办法。
- 多个视图依赖同一个状态
- 来自不同视图的行为需要变更同一状态
Vuex是专门为Vue应用程序开发的状态管理模式。
适合中大型应用, 如果规模较小的项目,可以使用store模式就够了。
组件如何获取store中的状态?
Vuex的核心就是store,是一个容器,里面放着应用中共享的state。
Vuex的存储是响应式的,组件从store中读取数据的时候,若store中的状态发生变化,组件也会更新。所以最简单的方法就是从计算属性中获取。
还可以使用mapState获取多个。
Vuex允许定义getters,相当于store中的计算属性,假如很多组件需要对一些数据进行同样的处理后使用,那么可以将这个处理的逻辑写进个getter,组件中通过store.getters.xxx访问。getter可以传参。
组件如何更改store中的状态?
不能直接更改store 中的状态, 改变store中状态的唯一方法就是commit mutation。
在store里声明一些mutation,在组件中使用store.commit('mutationName')触发。
mutation也可以带payload
mutation必须是同步函数。
如何异步处理呢?
action
action其实也是提交mutation的方式。
在store中定义actions,action里commit mutation ,action也可以触发其他的action。
在组件里通过store.dispatch('actionName')触发
状态太多,store变得非常庞大怎么办?
Module
Vuex允许将store分割成模块, 每个模块拥有自己的state,mutation,getters,actions,还可以定义自己的命名空间通过namespaced:true。
网友评论