面试题整理(三)

2020-03-04  本文已影响0人  宋乐怡

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
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;
});
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的劣势是兼容性

当然,Proxy的劣势就是兼容性问题,而且无法用polyfill磨平,因此Vue的作者才声明需要等到下个大版本(3.0)才能用Proxy重写。

3.VUE为什需要key?

答:
因为它是 Vue 识别节点的一个通用机制,key 并不仅与 v-for 特别关联。

<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的区别的?

答:

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处理程序的事件对象包含以下三方面的重要信息:

接收到消息后,验证发送窗口的来源是非常重要的。就像给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。

上一篇 下一篇

猜你喜欢

热点阅读