前端面试必掌握问题@令狐张豪
什么是闭包,优缺点?
闭包实际上就是一个嵌套函数,在一个函数内定义的一个函数。作为闭包的必要条件,内部函数应该访问外部函数中声明的私有变量、参数或者其他内部函数。当上述的两个条件实现后,此时如果在外部函数外调用这个内部函数,他就是成为了闭包函数。
【实例】 本例是一个经典的闭包结构
<script>
function fn(x) { //外部函数
var a = x; //外部函数的局部变量,并把参数值传递给它
var b = function () { //内部函数
return a; //内部函数访问外部函数的局部变量
}
a++; //访问后,动态更新外部函数的变量
return b; //外部函数返回内部函数
}
var c = fn(5);
console.log(c())
</script>
闭包的优点
- 方便调用上下文的局部变量
- 加强了封装性,可以达到对变量的保护作用
- 逻辑连续,当闭包作为另一个函数调用参数时,避免脱离当前逻辑而单独编写额外逻辑
闭包的缺点
- 由于闭包是驻留在内存中,会增大内存使用量,使用不当很容易造成内存泄露,降低程序的性能
三栏布局,都有几种?
- 浮动布局
- 弹性盒子
- 定位布局
- 网格布局
- 表格布局
什么是原型链?原型链怎么实现继承?
定义原型
原型链.PNG原型:声明函数时js会自动生成prototype,prototype会生成一个空对象(也就是原型对象),原型对象的constructor默认为你声明的对象
原型链
原型链:从实例往上寻找构造这个实例的相关联的对象,然后在这个关联的对象在往上查找创建它的上一级的原型对象,以此类推,一直到object.prototype终止,object.prototype是原型链的顶端。
任何一个实例对象通过原型链找到它上面的原型对象,这个原型对象上的方法和属性都被实例共享,这就是原型链的原理
原型链实现继承的几种方法
借用构造函数继承
function SuperType () {
this.colors = ["red", "blue", "green"]
}
function SubType () {
// 继承了SuperType
SuperType.call(this)
// 只能继承构造函数上的属性
}
- 注意:
- 函数复用性不高
- 只能继承实例上的属性,原型上的方法不可见
组合继承(伪经典继承)
function SuperType (name) {
this.name = name
this.colors = ["red", "blue", "green"]
}
SuperType.prototype.sayName = function () {
alert(this.name)
}
function SubType (name, age) {
// 继承属性
SuperType.call(this, name) // 第二次调用SuperType()
this.age = age
}
//继承方法
SubType.prototype = new SuperType() // 第一次调用 SuperType()
Subtype.prototype.sayAge = function () {
alert(this.age)
}
- 注意:
- 组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点,成为js中最常用的继承方式。
原型式继承
function object (o) {
function F(){}
F.prototype = o
return new F()
}
- 注意:
- 可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制,而复制的副本还可以得到进一步的改造
问题还是包含引用类型的属性都会被共享。
寄生式继承
function createAnother (original) {
var clone = object(original) // 通过调用函数创建一个新对象
clone.sayHi = function () { // 以某种方式来增强这个对象
alert("hi")
}
return clone // 返回这个对象
}
- 注意
- 基于某个对象或者某些信息创建一个对象,然后增强对象,最后返回对象,为了解决组合继承模式由于多次调用超类型构造函数而导致的低效率问题,可以将这个模式与组合继承一起使用
寄生组合式继承
function inheritPrototype (subType, superType) {
var prototype = object(superType.prototype) // 创建对象
prototype.constructor = subType // 增强对象
subType.prototype = prototype // 指定对象
}
function SuperType (name) {
this.name = name
this.colors = {"red", "blue", "green"}
}
SuperType.prototype.sayName = function () {
alert(this.name)
}
function SubType (name, age) {
SuperType.call(this, name)
this.age = age
}
inheritPrototype(SubType, SuperType)
- 注意
- 高效率只调用了一次构造函数,集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效方式
什么是深拷贝什么是浅拷贝?
在了解深拷贝和浅拷贝之前我们先来了解一下堆和栈
堆stack 和 栈heap
什么是堆内存&&什么是栈内存?
- stack:为自动分配的内存空间,它由系统自动释放;
- heap:为动态分配的内存空间,它大小不一定,也不会自动释放
深拷贝和浅拷贝
使用场景:
深拷贝:在复杂对象里,对象的属性也是对象的时候;
浅拷贝:只复制一层对象,当对象的属性是引用类型时,实质上复制的是其引用,当引用指向的值发生变化的时候,原对象属性值也跟着变化;
浅拷贝
将原对象/原数组的引用,直接赋给新对象/新数组,新对象/数组只是原对象的一个引用
let obj={a:1,arr:[2,3]};
let shallowObj=shallowCopy(obj);
function shallowCopy(srcObj){
var dest={};
for(let prop in srcObj){
console.log(prop);
if(srcObj.hasOwnProperty(prop)){
dest[prop]=srcObj[prop]
}
}// end of loop
return dest;
}
// 当一个对象属性的引用值改变时,会导致另一个也改变;
shallowObj.arr[1]=5;
深拷贝
深拷贝就是将原有对象重新拷贝一份,不论是修改哪一部分的值,都不会对原有对象造成影响。拷贝的永远是值,而不是引用。
你是怎么做响应式布局的?
什么是响应式布局?
简而言之,就是一个网站能够兼容多个终端——而不是为每个终端做一个特定的版本。
令狐张豪.jpg
优点:
- 面对不同分辨率设备灵活性强
- 能够快捷解决多设备显示适应问题
缺点:
- 兼容各种设备工作量大,效率低下
- 代码累赘,会出现隐藏无用的元素,加载时间加长
- 其实这是一种折中性质的设计解决方案,多方面因素影响而达不到最佳效果
- 一定程度上改变了网站原有的布局结构,会出现用户混淆的情况
响应式设计——设置 Meta 标签
大多数移动浏览器将HTML页面放大为宽的视图(viewport)以符合屏幕分辨率。你可以使用视图的meta标签来进行重置。下面的视图标签告诉浏览器,使用设备的宽度作为视图宽度并禁止初始的缩放。在<head>标签里加入这个meta标签
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
viewport
通俗的讲,移动设备上的viewport就是设备的屏幕上能用来显示我们的网页的那一块区域,在具体一点,就是浏览器上(也可能是一个app中的webview)用来显示网页的那部分区域
content属性值
- width:可视区域的宽度,值可为数字或关键词device-width(device-width:定义输出设备的屏幕可见宽度。)
- height:同width
- intial-scale:页面首次被显示是可视区域的缩放级别,取值1.0则页面按实际尺寸显示,无任何缩放
- maximum-scale=1.0, minimum-scale=1.0;可视区域的缩放级别,
- maximum-scale用户可将页面放大的程序,1.0将禁止用户放大到实际尺寸之上。
- user-scalable:是否可对页面进行缩放,no 禁止缩放
你是怎么用rem做自适应的?
什么是rem?
说到rem自然就会想到em,我们知道em是相对于父元素的字体大小的单位,那么rem则是相对于根元素也就是<html>元素的字体大小的单位。
如何用rem解决移动端适配
script方法
<script>
window.addEventListener('orientationchange', setRem);
window.addEventListener('resize', setRem);
setRem();
function setRem() {
var html = document.querySelector('html');
var width = html.getBoundingClientRect().width;
html.style.fontSize = width / 16 + 'px';
};
</script>
style方法
<style>
// sass写法
@function rem($px) {
@return $px / 46.875 + rem;
}
//less写法
@rem: 46.875rem;
//stylus自行解决
?
</style>
用法:
如:元素宽300px,高500px;
传入参数即可:width:rem(300); height:rem(500);
vue的双向数据绑定
所谓双向数据绑定, 无非就是数据层和视图层中的数据同步, 在写入数据时视图层实时的跟着更新
实现mvvm的双向绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。就必须要实现以下几点:
- 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
- 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
- 实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
-
mvvm入口函数,整合以上三者
令狐张豪.png
说说vuex
什么是vuex?
是专门一个为vue.js应用程序开发的状态管理模式
vuex的运行机制
前端小学生.jpg- 在vue组件里面,通过dispatch来触发actions提交修改数据的操作。
- 然后再通过actions的commit来触发mutations来修改数据。
- mutations接收到commit的请求,就会自动通过Mutate来修改state(数据中心里面的数据状态)里面的数据。
- 最后由store触发每一个调用它的组件的更新
如何使用promise封装axios?
class Http {
//request 方法
request(params) {
return new Promise((resolve, reject) => {
Axios({
method: params.type || "get", //请求的方式
url: params.url, //请求的地址
data: params.data, //post 传的参数
headers: params.headers //headers 请求头
})
.then(res => { //请求成功触发
if (res.data.code === 0) {
resolve(res);
} else {
resolve(res);
}
})
.catch(err => {
reject(err.statusText); ///请求失败触发
});
});
}
}
解释
- 首先通过http类封装axios
- 在http类中通过request方法return一个promise
- 通过promise传递两个参数(resolve和reject)
- 设置axios请求的方式、请求的地址、传递的参数
- 通过判断code===0返回成功,反之请求失败触发
浏览器兼容问题如何解决?
Normalize.css
不同浏览器的默认样式存在差异,可以使用 Normalize.css 抹平这些差异。当然,你也可以定制属于自己业务的 reset.css
<link href="https://cdn.bootcss.com/normalize/7.0.0/normalize.min.css" rel="stylesheet">
html5shiv.js
解决 ie9 以下浏览器对 html5 新增标签不识别的问题。
<script type="text/javascript" src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
respond.js
解决 ie9 以下浏览器不支持 CSS3 Media Query 的问题。
<script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
git常用指令
初始化git
git init
关联库
git remote add origin
克隆别人的项目
git clone
编写完之后提交到库里(更新)
- 先合并 git pull
- 再查状态 git status
- 给你新写的内容加入缓存区 git add .
- 起别名 git commit -m 'linghu'
- 给他提交到库里 git push