接到电话在外面,在路边面了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值”,而且不会遍历出新增的自定义属性。
那么结论:
推荐在循环对象属性的时候,使用for…in,在遍历数组的时候的时候使用for…of。
for…in循环出的是key,需要Array[key]访问value,for…of循环出的是value,但是不会遍历出新增属性。
注意,for…of是ES6新引入的特性。修复了ES5引入的for…in的不足
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 属性传入初始值)的绑定关系,并以数据的初始值进行渲染。绑定关系建立后,就可以双向同步数据了。