18.2.28阿里前端实习生内推面补坑

接到电话在外面,在路边面了15分钟,可以说发挥的烂透了。。。但是面试的小姐姐的声音巨好听........这里记录下答的不好的点,现在补上。o(╥﹏╥)o o(╥﹏╥)o o(╥﹏╥)o

一.js中遍历一个数组有多少种方法?

1.普通for循环:

for(j = 0; j < arr.length; j++) {
   
} 

2.for循环优化版

for(j = 0,len=arr.length; j < len; j++) {
   
}

提前用临时变量把长度存起来,避免每次循环都要进行获取数组长度的操作,当数组较大时优化效果才会比较明显。这种方法基本上是所有循环遍历方法中性能最高的一种

3.for循环弱化版

for(j = 0; arr[j]!=null; j++) {
   
}

这种方法严格上也属于for循环,只不过是没有使用length判断,而使用变量本身判断
实际上,这种方法的性能要远远小于普通for循环

4.forEach循环

数组自带的方法,使用频率较高,实际上性能比普通for循环弱。

需要注意的一点是函数里面的上下文是Array,如果在里面用async函数里面遍历数组,在forEach里面对元素进行await声明,就会报错。

await 的执行上下文必须是 async 函数

// 错误写法
async function forDemo() {
    let arr = [1, 2, 3, 4];
    arr.forEach(item => {
        let tmp = await item;
        console.log(tmp);
    });
}

forDemo();
// 正确写法
async function forDemo01() {
    let arr = [1, 2, 3, 4];
    for (let i = 0; i < arr.length; i ++) {
        let tmp = await arr[i];
        console.log(tmp);
    }
}

forDemo01();

关于forEach的详细,看文档:链接

5.forEach变种

Array.prototype.forEach.call(arr,function(el){  
   
});

由于foreach是Array型自带的,对于一些非这种类型的,无法直接使用的伪数组(如HTMLCollection,NodeList),所以才有了这个变种,使用这个变种可以让类似的数组拥有forEach功能。

实际性能要比普通foreach弱

6.for in 和for of循环

这个当时其实关键点答出来了,o(╥﹏╥)o,我知道这个of和in的区别,当时答出了in会遍历出自定义属性,但是感觉没说好~~~~~o(╥﹏╥)o

先是in:

for(j in arr) {
   
}

这个循环很多人爱用,但实际上,经分析测试,在众多的循环遍历方式中
它的效率是最低的

然后for of,这个是es6新增的

for(let value of arr) {  
   
});

先用具体一个例子,看看两个区别,我们定义一个数组
let aArray = ['a',123,{a:'1',b:'2'}]

in循环:
这里写图片描述

of循环:
这里写图片描述

现在好像没什么区别,我们再给数组增加一个属性,aArray.add = 'momoda'
我们现在遍历这个数组的key,正常来说是0,1,2.。。。
但是:
这里写图片描述
如图把add也遍历出来了,这样可能在一些场景里不适合,但是用of
这里写图片描述

可以看到,of遍历的是键值对中的"value值",而且不会遍历出新增的自定义属性。

那么结论:

  1. 推荐在循环对象属性的时候,使用for...in,在遍历数组的时候的时候使用for...of。

  2. for...in循环出的是key,需要Array[key]访问value,for...of循环出的是value,但是不会遍历出新增属性。

  3. 注意,for...of是ES6新引入的特性。修复了ES5引入的for...in的不足

  4. for...of不能循环普通的对象,需要通过和Object.keys()搭配使用
    这里写图片描述
    注意别把数组里面key和对象的key搞混淆了,数组里面的key就是下标。

for...of不能循环遍历普通对象,对普通对象的属性遍历推荐使用for...in

如果实在想用for...of来遍历普通对象的属性的话,可以通过和Object.keys()搭配使用,先获取对象的所有key的数组
然后遍历:

var foo = {
    name:'zmz',
    age:18,
    locate:{
    country:'china',
    Planet:'earth'
    }
}
for(var key of Object.keys(foo)){
    //使用Object.keys()方法获取对象key的数组
    console.log(key+": "+student[key]);
}

for...of文档:链接

7.map()

这个最气,js map的功能是和python的一样的,当时外面玩了几天状态不在线,大脑都是懵了,这个竟然都没说出来,气o(╥﹏╥)o。
参数:
callback
生成新数组元素的函数,使用三个参数:
currentValue
callback 的第一个参数,数组中正在处理的当前元素。
index
callback 的第二个参数,数组中正在处理的当前元素的索引。
array
callback 的第三个参数,map 方法被调用的数组。
thisArg
可选的。执行 callback 函数时 使用的this 值。
和foreach差不多,
这里写图片描述
其实二题的答案面试官就是想让用map做,map和forEach的不同就是map有返回值,可以把结果返回出来,这些可以在函数做一些计算处理。

另外一点注意的就是不管是forEach还是map在IE6-8下都不兼容(不兼容的情况下在Array.prototype上没有这两个方法),那么需要我们自己封装一个都兼容的方法:

Array.prototype.myForEach = function myForEach(callback,context){  
    context = context || window;  
    if('forEach' in Array.prototye) {  
        this.forEach(callback,context);  
        return;  
    }  
    //IE6-8下自己编写回调函数执行的逻辑  
    for(var i = 0,len = this.length; i < len;i++) {  
        callback && callback.call(context,this[i],i,this);  
    }  
} 

总结

以上大概就是现在能想到的所有方法,欢迎补充,性能方便经测试在chrome环境下,for.....in循环最慢。优化后的普通for循环最快。

二.把数组中元素转为字符串?(提示map?)

var arr = [1,2,3,4];
var ans = arr.map(function(value){
    return value.toString();
})

map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。

map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值(包括 undefined)组合起来形成一个新数组。 callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。

map文档:链接

三.rem布局原理

谈到rem的话,就要和em一起说说。

rem和em两个都是css的单位,并且也都是相对单位,现有的em,css3才引入的rem。

em作为font-size的单位时,其代表父元素的字体大小,em作为其他属性单位时,代表自身字体大小——MDN

关于em的一个题目:

<div class="p1">
	<div class="s1">1</div>
  	<div class="s2">1</div>
</div>
<div class="p2">
	<div class="s5">1</div>
  	<div class="s6">1</div>
</div>


.p1 {font-size: 16px; line-height: 32px;}
.s1 {font-size: 2em;}
.s2 {font-size: 2em; line-height: 2em;}

.p2 {font-size: 16px; line-height: 2;}
.s5 {font-size: 2em;}
.s6 {font-size: 2em; line-height: 2em;}

答案:
p1:font-size: 16px; line-height: 32px
s1:font-size: 32px; line-height: 32px
s2:font-size: 32px; line-height: 64px

  • p1 无需解释
  • s1 em作为字体单位,相对于父元素字体大小;line-height继承父元素计算值
  • s2 em作为行高单位时,相对于自身字体大小

p2:font-size: 16px; line-height: 32px
s5:font-size: 32px; line-height: 64px
s6:font-size: 32px; line-height: 64px

  • p2 line-height: 2自身字体大小的两倍
  • s5 数字无单位行高,继承原始值,s5的line-height继承的2,自身字体大小的两倍
  • s6 无需解释

rem作用于非根元素时,相对于根元素字体大小;rem作用于根元素字体大小时,相对于其出初始字体大小——MDN

/* 作用于根元素,相对于原始大小(16px),所以html的font-size为32px*/
html {font-size: 2rem}

/* 作用于非根元素,相对于根元素字体大小,所以为64px */
p {font-size: 2rem}

rem布局的本质是等比缩放,一般是基于宽度,试想一下如果UE图能够等比缩放,那该多么美好啊

假设我们将屏幕宽度平均分成100份,每一份的宽度用x表示,x = 屏幕宽度 / 100,如果将x作为单位,x前面的数值就代表屏幕宽度的百分比

p {width: 50x} /* 屏幕宽度的50% */ 

如果想要页面元素随着屏幕宽度等比变化,我们需要上面的x单位,不幸的是css中并没有这样的单位,幸运的是在css中有rem,通过rem这个桥梁,可以实现神奇的x

通过上面对rem的介绍,可以发现,如果子元素设置rem单位的属性,通过更改html元素的字体大小,就可以让子元素实际大小发生变化

html {font-size: 16px}
p {width: 2rem} /* 32px*/

html {font-size: 32px}
p {width: 2rem} /*64px*/

如果让html元素字体的大小,恒等于屏幕宽度的1/100,那1rem和1x就等价了

html {fons-size: width / 100}
p {width: 50rem} /* 50rem = 50x = 屏幕宽度的50% */ 

如何让html字体大小一直等于屏幕宽度的百分之一呢? 可以通过js来设置,一般需要在页面dom ready、resize和屏幕旋转中设置

document.documentElement.style.fontSize = document.documentElement.clientWidth / 100 + 'px'; 

上面提到想让页面元素随着页面宽度变化,需要一个新的单位x,x等于屏幕宽度的百分之一,css3带来了rem的同时,也带来了vw和vh

vw —— 视口宽度的 1/100;vh —— 视口高度的 1/100 —— MDN

这个其实等价于上面提到的x,
根据定义可以发现1vw=1x,有了vw我们完全可以绕过rem这个中介了,下面两种方案是等价的,可以看到vw比rem更简单,毕竟rem是为了实现vw

/* rem方案 */
html {fons-size: width / 100}
p {width: 15.625rem}
/* vw方案 */
p {width: 15.625vw}
vw还可以和rem方案结合,这样计算html字体大小就不需要用js了
html {fons-size: 1vw} /* 1vw = width / 100 */
p {width: 15.625rem}

当然越高级的东西,兼容性就越不好了,vw要求的版本就比rem更高一点。
另外,在使用弹性布局时,一般会限制最大宽度,比如在pc端查看我们的页面,此时vw就无法力不从心了,因为除了width有max-width,其他单位都没有,而rem可以通过控制html根元素的font-size最大值,而轻松解决这个问题。

最后一句:rem是弹性布局的一种实现方式,弹性布局可以算作响应式布局的一种,但响应式布局不是弹性布局,弹性布局强调等比缩放,100%还原;响应式布局强调不同屏幕要有不同的显示。在很多场景都有不同情景要求。

四.vue原理

传统的MV*模型。我们需要编写代码,将从服务器获取的数据进行“渲染”,展现到视图上。每当数据有变更时,我们会再次进行渲染,从而更新视图,使得视图与数据保持一致,而另一方面,页面也会通过用户的交互,产生状态、数据的变化,这个时候,我们则编写代码,将视图对数据的更新同步到数据,以致于同步到后台服务器.这两种过程都是单向的。

VueJS 则使用 ES5 提供的 Object.defineProperty() 方法,监控对数据的操作,从而可以自动触发数据同步。并且,由于是在不同的数据上触发同步,可以精确的将变更发送给绑定的视图,而不是对所有的数据都执行一次检测。

数据与视图的绑定与同步,最终体现在对数据的读写处理过程中,也就是 Object.defineProperty() 定义的数据 set、get 函数中。Vue 中对于的函数为 defineReactive:

Vue 双向数据绑定实现

function defineReactive(obj, key, value) {
    var dep = new Dep()
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            if (Dep.target) {
                dep.depend()
            }
            return value
        },
        set: function reactiveSetter(newVal) {
            if (value === newVal) {
                return
            } else {
                value = newVal
                dep.notify()
            }
        }
    })
}

在对数据进行读取时,如果当前有 Watcher(对数据的观察者吧,watcher 会负责将获取的新数据发送给视图),那将该 Watcher 绑定到当前的数据上(dep.depend(),dep 关联当前数据和所有的 watcher 的依赖关系),是一个检查并记录依赖的过程。而在对数据进行赋值时,如果数据发生改变,则通知所有的 watcher(借助 dep.notify())。这样,即便是我们手动改变了数据,框架也能够自动将数据同步到视图。

这里写图片描述

数据绑定关系的识别过程

Vue 和 AngularJS 中,都是通过在 HTML 中添加指令的方式,将视图元素与数据的绑定关系进行声明。例如:

<form id="test">
  <input type="text" v-model="name">
</form>

以上的 HTML 代码表示该 input 元素与 name 数据进行绑定。在 JS 代码中可以这样进行初始化:

var vm = new Vue({
  el: '#test',
  data: {
    name: 'luobo'
  }
})

代码正确执行后,页面上 input 元素对应的位置会显示上面代码中给出的初始值:luobo。

由于双向数据绑定已经建立,因此:

  • 执行 vm.name = 'mickey' 后,页面上 input 也会更新为显示: mickey
  • 在页面文本框中修改内容为:tang,则通过vm.name 获取的值为:"tang"

那么初始化的过程中,Vue 是如何识别出这种绑定关系的呢?

通过分析源码,在初始化过程中(new Vue() 执行时),主要执行两个步骤:

  • compile
  • link

compile 过程中,对于给定的目标元素进行解析,识别出所有绑定在元素(通过 el 属性传入)上的指令。
link 过程中,建立这些指令与对应数据(通过 data 属性传入初始值)的绑定关系,并以数据的初始值进行渲染。绑定关系建立后,就可以双向同步数据了。

posted @ 2018-03-03 11:04  Lawliet__zmz  阅读(272)  评论(0编辑  收藏  举报