面试题整理(三)

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
  1. const声明的是一个只读的常量,一旦声明,必须立即初始化,不能留到以后赋值。

2.与let相同点:作用域与let相同,也同样存在暂时性死区,不可重复声明。

3.const实际保证的是变量指向的内存地址不可变,所以用const定义对象要格外小心。对象的数据结构完全不受控制。如果想将对象冻结,可以使用

const Foo = Object.freeze({});
  1. ES6为了逐步将全局变量与顶层对象的属性隔离,规定let ,const,class命令声明的变量不属于顶层对象的属性。但是为了保证旧代码的兼容性,var和function命令声明的全局变量依旧是顶层对象的属性,可以用window 访问。
  1. 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。

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

推荐阅读更多精彩内容