面试题整理(三)

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

请输入:

//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 会造成渲染错误。

  • 它也可以用于强制替换元素/组件而不是重复使用它。当你遇到如下场景时它可能会很有用:
    完整地触发组件的生命周期钩子
    触发过渡


  {{ text }}

当 text 发生改变时, 会随时被更新,因此会触发过渡。

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。这个类似 Vue 1.x 的 track-by="$index"。

这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性:

建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。

4.VUE的data是对象还是函数有什么不同?

答:

// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: ''
})

当我们定义这个 组件时,你可能会发现它的 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而言,“另一个地方”指的是包含在当前页面的