在我们找工作时经常会因为面试问题而头痛,本文就来为大家收集一些前端面试的题目和答案,希望对大家有一定的帮助。
一、CSS实现图片自适应宽高
img { max-width: 100%; max-height: 100%;}
二、什么是flex,写出常见属性,以及作用
Flex即:Flexible Box,弹性布局,用来为盒状模型提供最大的灵活性。可以实现类似垂直居中布局。
.box{ display: flex;}.box{ display: inline-flex;}// Webkit内核的浏览器,必须加上-webkit前缀.box{ display: -webkit-flex; /* Safari */ display: flex;}
设为Flex布局以后,子元素的float、clear和vertical-align属性将失效
-
采用Flex布局的元素,称为Flex容器(flex contAIner)
-
所有子元素自动成为容器成员,称为Flex项目(flex item)
容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。
容器有6个属性
1、flex-direction 决定主轴的方向
.box { flex-direction: row | row-reverse | column | column-reverse; } // row(默认值):主轴为水平方向,起点在左端 // row-reverse:主轴为水平方向,起点在右端 // column:主轴为垂直方向,起点在上沿 // column-reverse:主轴为垂直方向,起点在下沿
2、flex-wrap 定义,如果一条轴线排不下,如何换行
.box{ flex-wrap: nowrap | wrap | wrap-reverse; } // nowrap(默认):不换行 // wrap:换行,第一行在上方 // wrap-reverse:换行,第一行在下方
3、flex-flow 是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap
.box { flex-flow: <flex-direction> <flex-wrap>; }
4、justify-content 定义了项目在主轴上的对齐方式
.box { justify-content: flex-start | flex-end | center | space-between | space-around; } // 具体对齐方式与轴的方向有关。下面假设主轴为从左到右 // flex-start(默认值):左对齐 // flex-end:右对齐 // center: 居中 // space-between:两端对齐,项目之间的间隔都相等。 // space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
5、align-items 定义项目在交叉轴上如何对齐
.box { align-items: flex-start | flex-end | center | baseline | stretch; } // 具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下 // flex-start:交叉轴的起点对齐。 // flex-end:交叉轴的终点对齐。 // center:交叉轴的中点对齐。 // baseline: 项目的第一行文字的基线对齐。 // stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
6、align-content 定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
.box { align-content: flex-start | flex-end | center | space-between | space-around | stretch; } // flex-start:与交叉轴的起点对齐。 // flex-end:与交叉轴的终点对齐。 // center:与交叉轴的中点对齐。 // space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。 // space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。 // stretch(默认值):轴线占满整个交叉轴。
项目有6个属性
1、order 定义项目的排列顺序。数值越小,排列越靠前,默认为0。
.item { order: <integer>;}
2、flex-grow 定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大
.item { flex-grow: <number>; /* default 0 */}
3、flex-shrink 定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。负值对该属性无效
.item { flex-shrink: <number>; /* default 1 */ }
4、flex-basis 在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
.item { flex-basis: <length> | auto; /* default auto */ }
5、flex 是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。
.item { flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ] } // 该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)
6、align-self 允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch
.item { align-self: auto | flex-start | flex-end | center | baseline | stretch; } // 除了auto,其他都与align-items属性完全一致
三、BFC是什么?
BFC即Block Formatting Contexts (块级格式化上下文),它属于普通流,即:元素按照其在 HTML 中的先后位置至上而下布局,在这个过程中,行内元素水平排列,直到当行被占满然后换行,块级元素则会被渲染为完整的一个新行。
除非另外指定,否则所有元素默认都是普通流定位,也可以说,普通流中元素的位置由该元素在 HTML 文档中的位置决定。
可以把 BFC 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部。
只要元素满足下面任一条件即可触发 BFC 特性
-
body 根元素
-
浮动元素:float 除 none 以外的值
-
绝对定位元素:position (absolute、fixed)
-
display 为 inline-block、table-cells、flex
-
overflow 除了 visible 以外的值 (hidden、auto、scroll)
四、前端鉴权是怎么实现的?
参考下:
五、vue双向绑定是什么?手写一个vue双向绑定。
vue数据双向绑定是通过数据劫持Object.defineProperty( )结合发布者-订阅者模式的方式来实现的
实现过程:
1、首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。
2、如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。
3、因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。
4、我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。
代码实现
1、实现一个Observer
-
如果要对所有属性都进行监听的话,那么可以通过递归方法遍历所有属性值,并对其进行Object.defineProperty( )处理。
-
思路分析中,需要创建一个可以容纳订阅者的消息订阅器Dep,订阅器Dep主要负责收集订阅者,然后再属性变化的时候执行对应订阅者的更新函数。所以显然订阅器需要有一个容器,这个容器就是list,植入消息订阅器
function defineReactive(data, key, val) { observe(val); // 递归遍历所有子属性 var dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function() { if (是否需要添加订阅者) { dep.addSub(watcher); // 在这里添加一个订阅者 } return val; }, set: function(newVal) { if (val === newVal) { return; } val = newVal; console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”'); dep.notify(); // 如果数据变化,通知所有订阅者 } }); } function Dep () { this.subs = []; } Dep.prototype = { addSub: function(sub) { this.subs.push(sub); }, notify: function() { this.subs.forEach(function(sub) { sub.update(); }); } };
我们将订阅器Dep添加一个订阅者设计在getter里面,这是为了让Watcher初始化进行触发,因此需要判断是否要添加订阅者。
在setter函数里面,如果数据变化,就会去通知所有订阅者,订阅者们就会去执行对应的更新的函数。
2、实现Watcher
订阅者Watcher在初始化的时候需要将自己添加进订阅器Dep中
在订阅者Watcher初始化的时候触发对应的get函数去执行添加订阅者操作,获取对应的属性值就可以触发
在Dep.target上缓存下订阅者,添加成功后再将其去掉就可以了
function Watcher(vm, exp, cb) { this.cb = cb; this.vm = vm; this.exp = exp; this.value = this.get(); // 将自己添加到订阅器的操作 } Watcher.prototype = { update: function() { this.run(); }, run: function() { var value = this.vm.data[this.exp]; var oldVal = this.value; if (value !== oldVal) { this.value = value; this.cb.call(this.vm, value, oldVal); } }, get: function() { Dep.target = this; // 缓存自己 var value = this.vm.data[this.exp] // 强制执行监听器里的get函数 Dep.target = null; // 释放自己 return value; } };
对监听器Observer也做个稍微调整,主要是对应Watcher类原型上的get函数
function defineReactive(data, key, val) { observe(val); // 递归遍历所有子属性 var dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function() { if (Dep.target) {. // 判断是否需要添加订阅者 dep.addSub(Dep.target); // 在这里添加一个订阅者 } return val; }, set: function(newVal) { if (val === newVal) { return; } val = newVal; console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”'); dep.notify(); // 如果数据变化,通知所有订阅者 } }); } Dep.target = null;
来个模板
<body> <h1 id="name">{{name}}</h1></body>
将Observer和Watcher关联起来
function SelfVue (data, el, exp) { this.data = data; observe(data); el.innerHTML = this.data[exp]; // 初始化模板数据的值 new Watcher(this, exp, function (value) { el.innerHTML = value; }); return this; }
在页面上new以下SelfVue类,就可以实现数据的双向绑定
<body> <h1 id="name">{{name}}</h1> </body> <script src="js/observer.js"></script> <script src="js/watcher.js"></script> <script src="js/index.js"></script> <script type="text/JavaScript"> var ele = document.querySelector('#name'); var selfVue = new SelfVue({ name: 'hello world' }, ele, 'name'); window.setTimeout(function () { console.log('name值改变了'); selfVue.data.name = 'canfoo'; }, 2000); </script>
还有一个细节问题,我们在赋值的时候是这样的形式 ' selfVue.data.name = 'canfoo' ' 而我们理想的形式是' selfVue.name = 'canfoo' '为了实现这样的形式。
我们需要在new SelfVue的时候做一个代理处理,让访问selfVue的属性代理为访问selfVue.data的属性,实现原理还是使用Object.defineProperty( )对属性值再包一层。
function SelfVue (data, el, exp) { var self = this; this.data = data; Object.keys(data).forEach(function(key) { self.proxyKeys(key); // 绑定代理属性 }); observe(data); el.innerHTML = this.data[exp]; // 初始化模板数据的值 new Watcher(this, exp, function (value) { el.innerHTML = value; }); return this; } SelfVue.prototype = { proxyKeys: function (key) { var self = this; Object.defineProperty(this, key, { enumerable: false, configurable: true, get: function proxyGetter() { return self.data[key]; }, set: function proxySetter(newVal) { self.data[key] = newVal; } }); } }
3、实现Compile
实现一个解析器Compile来做解析和绑定工作
-
.解析模板指令,并替换模板数据,初始化视图
-
将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器
首先需要获取到dom元素,然后对含有dom元素上含有指令的节点进行处理
可以先建一个fragment片段,将需要解析的dom节点存入fragment片段里再进行处理
function nodeToFragment (el) { var fragment = document.createDocumentFragment(); var child = el.firstChild; while (child) { // 将Dom元素移入fragment中 fragment.appendChild(child); child = el.firstChild } return fragment; }
遍历各个节点,对含有相关指定的节点进行特殊处理。咱们先处理最简单的情况,只对带有 '{{变量}}' 这种形式的指令进行处理
function compileElement (el) { var childNodes = el.childNodes; var self = this; [].slice.call(childNodes).forEach(function(node) { var reg = /{{(.*)}}/; var text = node.textContent; if (self.isTextNode(node) && reg.test(text)) { // 判断是否是符合这种形式{{}}的指令 self.compileText(node, reg.exec(text)[1]); } if (node.childNodes && node.childNodes.length) { self.compileElement(node); // 继续递归遍历子节点 } }); }, function compileText (node, exp) { var self = this; var initText = this.vm[exp]; this.updateText(node, initText); // 将初始化的数据初始化到视图中 new Watcher(this.vm, exp, function (value) { // 生成订阅器并绑定更新函数 self.updateText(node, value); }); }, function (node, value) { node.textContent = typeof value == 'undefined' ? '' : value; }
将解析器Compile与监听器Observer和订阅者Watcher关联起来,我们需要再修改一下类SelfVue函数
function SelfVue (options) { var self = this; this.vm = this; this.data = options; Object.keys(this.data).forEach(function(key) { self.proxyKeys(key); }); observe(this.data); new Compile(options, this.vm); return this;}
原文链接: vue的双向绑定原理及实现
实现代码地址:
v1:https://github.com/canfoo/self-vue/tree/master/v1
v2:https://github.com/canfoo/self-vue/tree/master/v2
v3:https://github.com/canfoo/self-vue/tree/master/v3
六、什么是mvvm?
MVVM分为Model、View、ViewModel三者。
Model 代表数据模型,数据和业务逻辑都在Model层中定义;
-
View 代表UI视图,负责数据的展示;
-
ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;
-
Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。
这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom。