前端基础:
1.讲讲对web标准以及w3c的理解与认识
web可以简单分为:结构、表现、行为。三部分独立开来使其模块化
w3c是对web做出规范,使代码更严谨,做出来的网页更易使用,维护。
w3c做出的规范可分为如下:
结构上:(标签规范对页面的搜索权重有很大关系,写的越规范网站在搜索排名越靠前)
- 标签闭合、标签小写、不乱嵌套
表现、行为上:
- 使用外链的css和js脚本,提高页面渲染效率。
- 少使用行内样式,类名要做到见名知意
2.说明ajax请求的时候post和get的区别
发送机制
1、get请求会将参数跟在URL后面进行参数传递,而post请求则是作为http消息的实体内容发送给web服务器;
2、get提交的数据限制是1024字节,这种显示是来自特定浏览器和服务器对它的限制。如ie的URL长度限制是2083字节,火狐理论上没有长度限制。注意这个限制是URL 的整个长度,而不是参数的长度。
3、get方式请求的数据会被浏览器缓存起来。因为其他人可以从浏览器的历史记录中读取到这些数据,比如:账号或者密码等。在某种情况下,get方式会带来严重的安全问题,而post相对来说可以避免这些问题。
在服务端的区别
1、客户端请求使用get时,服务端使用Request.QueryString来获取,而客户端使用post请求时,服务端使用Request.Form来获取。
2、post用于创建资源,资源的内容会被编入http请示的内容中,例如,处理订货表单等。
3、当请求无副作用时(如进行搜索),使用get方法,当请求有副作用时(如添加数据),则用post方法。
3.如何对网站的文件和资源进行优化,主流的解决方案包括哪些?
1. 文件合并
2. 文件最小化/文件压缩
3. 使用 CDN 托管
4. 缓存的使用(多个域名来提供缓存)
5. 其他
4.闭包是什么?有什么特性?对页面有什么影响?
“官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
通俗的讲:就是函数a的内部函数b,被函数a外部的一个变量引用的时候,就创建了一个闭包。
闭包的特性:
①.封闭性:外界无法访问闭包内部的数据,如果在闭包内声明变量,外界是无法访问的,除非闭包主动向外界提供访问接口;
②.持久性:一般的函数,调用完毕之后,系统自动注销函数,而对于闭包来说,在外部函数被调用之后,闭包结构依然保存在
系统中,闭包中的数据依然存在,从而实现对数据的持久使用。
优点:
① 减少全局变量。
② 减少传递函数的参数量
③ 封装;
缺点:
使用闭包会占有内存资源,过多的使用闭包会导致内存溢出等.
5.https页面下直接请求http会遇到什么问题?
在https页面下的带有相对路径的请求都会与页面的协议保持一致。如果想在https页面下发送http的请求,如果只把链接写死成为http的绝对路径是不够的,这样会导致http的请求与总页面https的请求的session不一致。
为什么呢?原因是https的请求中服务器发回的cookie是标记为"secure"的,而http的请求时非"secure","由于在服务器端secure"的cookie不会兼容非"secure"的,所以当http的请求携带着同一jsessionid的cookie到达服务器时,服务器拒绝非"secure",进而返回的结果是一个新的非"secure"的cookie,于是两个session就不同了。
怎么解决呢?由原因分析可知,两个session不同,更具体说是cookie的状态不同。那么办法是,在接收到第一个https请求的响应后 到 发送下面的http请求之前,将cookie去"secure"状态,但是又要保证jsessionid不变。具体操作可以新建一个cookie(新建的是非"secure"状态),然后赋予同一个jessionid,然后加入response中。
6.js继承的方式及其优缺点
推荐文章:传送门
人家讲得已经很清楚了,案例跟优缺点都整理出来了,推荐阅读
7.解释ajax的工作原理
1、创建ajax对象(XMLHttpRequest/ActiveXObject(Microsoft.XMLHttp))
2、打开链接 open(请求方式,'请求路径',同步/异步)
3、发送 send()
4、当ajax对象完成第四步(onreadystatechange)数据接收完成,判断对象状态码(readystate) 4 HTTP响应完全接收 在判断http响应状态(status)200-300之间或者304(缓存)执行回调函数 获取的数据转成字符串格式(responseText)
具体代码参考网上...
JS部分:
1.打印以下执行结果
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
},0)
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end')
考察的是代码执行顺序问题,定时器和主程序属于宏任务,Promise中then的回调属于微任务,在每个宏任务里面都会先执行完微任务再去执行下一个宏任务。
本题执行逻辑应该是主线程中console.log('script start');碰到定时器,把这个宏任务扔事件队列里面,继续向下,碰到Promise.then异步任务,继续扔队列里面,执行console.log('script end');再去执行当前宏任务里面的微任务,即Promise.then回调函数,最后执行第二个宏任务定时器.
执行结果:
script start
script end
promise1
promise2
setTimeout
2.打印以下执行结果
const promise = new Promise((resolve,reject) => {
console.log(1);
resolve();
console.log(2);
});
promise.then(()=>{
console.log(3);
});
console.log(4);
Promise 构造函数是同步执行的,promise.then 中的函数是异步执行的。所以
执行结果:
1
2
4
3
3.打印以下执行结果
var funcs = [];
for(var i=0;i<10;i++) {
funcs.push(function() {
console.log(i);
});
}
funcs.forEach(function(func) {
func();
});
基础题,匿名函数中的 i 值只有在函数调用执行的时候才会去寻找对应变量的,for循环结束后执行func()时,此时i变量已经变为10(最后有个i++),所以循环打印了十次10,想依次打印0-9的话可以使用立即执行函数解决或者使用ES6的块级作用域let声明变量i.
4.解释0.1 + 0.2 !== 0.3
js中number类型的数字都是采用64位的双精度浮点数进行存储的,其中1个符号位(0正1负),11个指数位,52个尾数位。0.1用二进制表示如下:0.000110011001100110011001100110011001100110011001100110011...
如上可以看出0.1二进制表示时尾数是超过52位的, 所以52位之后的会被舍去,这就有了浮点数存储的精度丢失问题。
5.写出下列执行结果:
setTimeout(()=>{
console.log('b')
new Promise((resolve,reject)=>{
console.log('c');
resolve()
}).then(res => {
console.log('f')
})
},0);
new Promise((resolve,reject) => {
console.log('d')
resolve()
}).then(res => {
console.log('a')
setTimeout(()=>{
console.log('e')
},0)
})
原理同第一题,答案为:dabcfe
6.写出以下程序运行结果:
let test1 = () => {
console.log(this);
};
function test2() {
console.log(this);
};
class t {
test() {
console.log(this);
}
}
let test3 = t.prototype.test;
test1.call(null);
test2.call(null);
test3.call(null);
先给出正确答案:
/// window ; window ; null (非严格模式),
/// window ; null ; null (严格模式)
当call或apply的第一个参数为null || undefined时 this指向window ||global
call方法的参数,应该是一个对象。如果参数为空、null和undefined,则默认传入全局对象
类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。
顺便解释一下call的用法,apply类似,参数不同而已:
call方法
语法:call(thisobj,[argq,arg2])
定义:调用一个对象的一个方法,以另一个对象替换当前对象
说明:
call方法可以用来代替一个对象调用一个方法,call方法可以将一个函数的对象上下文从初始化改为新的对象,也就是括号里面的原本的对象改为call()前面的对象、即用thisobj代替call前面的东西,最终用thisobj这个对象去执行call前面的方法。
如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
其实call才是函数调用最原始的方式,现在我们使用的直接myFunc(args)调用函数的方式其实就是call调用的语法糖
7.使用js实现随机选取10–100之间的10个数字,存入一个数组,并排序
let iArray = [];
function getRandom(istart, iend){
var iChoice =iend - istart + 1;
return Math.floor(Math.random() * iChoice + istart);
}
/*Math.random()就是获取 0-1 之间的随机数(永远获取不到 1)*/
for(let i=0; i<10; i++){
let result= getRandom(10,100);
iArray.push(result);
}
iArray.sort(function(a,b){
return a>b;
});
console.log(iArray);
8.输出以下运行结果:
let foo=1;
function bar(){
foo=10;
return;
functionfoo(){}
}
bar();
console.log(foo); //1
为什么是1而不是10先分析一下每步流程:
第一步:varfoo=1;全局变量foo被初始化赋值成1。
第二步:执行bar();方法。
第三步:bar()方法里,函数声明functionfoo(){}优先处理,这里JavaScript解析语法时(在运行之前)函数优先于一切。所以foo被初始化赋值为function(){};
第四步:执行foo=10;这里制造了一个假象,认为没有用var声明指向的是外层foo=1;。其实不是。而是先在自身函数体里找有没有foo声明,找到之前声明的functionfoo(){};赋值成10,只是局部变量的值改写。
第五步:输出foo,这时找的是全局变量varfoo=1;输出1。
CSS部分:
1.分别写出box的高度
<style>
.box {
line-height: 24px;
background-color: lightblue;
}
.box span {
line-height: 48px;
border: 1px solid;
}
</style>
<div class="box">
<span>content...</span>
</div>
考察的是inline box模型,它的工作就是包裹每行文字,一个没有设置height属性的div的高度就是由一个一个line boxes的高度堆积而成的,撑开div高度的是line-height不是文字内容.MDN 文档中对line-height的描述如下:
line-height CSS 属性用于设置多行元素的空间量, 比如文本。对于块级元素, 它指定元素行盒(line boxes)的最小高度。 对于非替代的inline元素, 它用于计算行盒(line box)的高度。
适用元素 all elements. It also applies to ::first-letter and ::first-line.
如上面最终显示 .box 容器 height = 48px;
1.
将<span>标签里的内容 改为<span>content ... <br> content ... </span> 后,
.box 的height = 96px(48px*2)。
2.
将<span>标签里的内容 改为content ... <br><span> content ... </span> 后,
.box 的height = 48 + 24 = 72px。
2.css实现0.5像素的边框
半像素边框当然不是简单地把1px改为0.5px,浏览器中最小的像素单位为1像素,是不能识别0.5个像素,
1.设置目标元素作为定位参照
box{position:relative}
2.给目标元素添加一个伪元素before或者after,并设置绝对定位
.box:before{content:""; position:absolute;}
3.给伪元素添加1px边框
border:1px solid #000;
4.设置伪元素的宽高为目标元素的2倍
width:200%; height:200%;
5.缩小0.5倍(缩放到原来大小)
transform-origin: 0 0;
transform:scale(0.5,0.5);
6.把border 边框在线框内绘制
box-sizing:border-box;
Vue框架部分:
1.vue组件中的data为什么必须是个函数
简单地说,对象是引用数据类型,那你每次导入这个组件的时候,其实引用的是同一个内存地址,componentA改变了引用这块地址的数据后,componentB中的这块地址对应的数据也会被改变。
那是因为在js中,函数的{}才有独立的作用域,对象的{},if(){}是不构成作用域的.
函数里面,每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的,这样的话,不管你同时引入同一个组件多少次,他们之间的组件属性都是独立的,互不干扰。
2.解释vue的响应式原理
vue的响应式核心是Object.defineProperty实现的.
Object.defineProperty(obj, key, {
set:function(){},
get:function(){}
})
被Object.defineProperty绑定过的数据会被监听到,改变这个对象的时候会触发get和set事件,这也是vue的model层和view层通信的基础,其实vue中的Observer就是基于Object.defineProperty来实现的。
Observer是数据的观察者,与model层直接通信,当数据更改时,它会通知dep(专门管理数据监听依赖的东西),dep会收集到它所需要的依赖,当get的时候,收集订阅者(这里的订阅者其实是观察者模式下的订阅者,简单地说,就是它需要知道具体get什么订阅者,什么是观察者模式和发布订阅者设计模式,具体网上很多相关资料解释),把他添加到依赖,比如,watch和computed都依赖一个data进行监听或者计算,那么dep会将这两个不同的依赖收集。当set的时候会发布更新,通知watcher更新view层。一个属性可能有多个依赖,每个响应式数据都有一个Dep来管理它的依赖。
每一个依赖又是依靠一个中介的角色来通知变化从而更新视图的,因此watcher能通知变化去执行各自的代码,当然它也能区分自己所属的依赖,比如自己是属于data还是watch还是computed.
放两张网上的图可以更明了的说明他们之间的关系,也可以看看大佬们的文章,讲的更仔细,想学会这种设计模式还是要撸源码,应付面试已经够了。
原图链接
3.解释v-model的原理
v-model本质上就是一个语法糖,实现原理其实就是上面说过的数据绑定加上底层的input事件监听,通过v-bind绑定一个数据传给子组件,子组件里面的model默认用value属性接受,然后子组件监听数据发生变化,emit触发父组件的input事件,通过触发事件来进行传值,实现了父子组件数据的双向绑定。
4.设计模式-发布订阅者模式
定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖他的对象都会得到通知。
观察者模式是由具体目标(发布者/被观察者)调度的,而发布/订阅模式是由独立的调度中心进行调度,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会(相当于一个中介的角色,一个全局的Event,发布者不需要知道具体要通知谁,订阅者也不需要知道具体是谁通知的);可以说发布订阅模式是观察者模式进一步解耦,在实际中被大量运用的一种模式。
5.vue.js如何做单元测试?
单元测试:按空间切割,对每个组件进行测试
比如,我要测试日期输入框,那么我编写的测试用例应该包括以下部分:
- 默认日期是否为当天
- 当用户选择日期范围,data是否会做相应改变
- ...
E2E测试:按时间切割,对每个流程进行测试
比如,我要测试搜索功能,那么我编写的测试用例应该模拟以下步骤:
- 打开主页
- 点击菜单跳转到详情页
- 输入搜索条件
- 点击搜索
- 查看搜索结果是否与预期一致
vue init webpack test
6.vue v-on绑定多个方法
1.v-on绑定多个方法:<p v-on="{click:dbClick,mousemove:MouseClick}"></p>
2.一个事件绑定多个函数:<p @click="one(),two()">点击</p>
7.举例vue中常用的修饰符
.lazy:
v-modeil不用多说,输入框改变,这个数据就会改变,lazy这个修饰符会在光标离开input框才会更新数据:
<input type="text" v-model.lazy="value">
.trim:
输入框过滤首尾的空格:
<input type="text" v-model.trim="value">
.number:
先输入数字就会限制输入只能是数字,先字符串就相当于没有加number,注意,不是输入框不能输入字符串,是这个数据是数字:
<input type="text" v-model.number="value">
.stop:
阻止事件冒泡,相当于调用了event.stopPropagation()方法。这个应该不需要解释:
<button @click.stop="test">test</button>
.prevent:
阻止默认行为,相当于调用了event.preventDefault()方法,比如表单的提交、a标签的跳转就是默认事件:
<a @click.prevent="test">test</a>
.self:
只有元素本身触发时才触发方法,就是只有点击元素本身才会触发。比如一个div里面有个按钮,div和按钮都有事件,我们点击按钮,div绑定的方法也会触发,如果div的click加上self,只有点击到div的时候才会触发,变相的算是阻止冒泡:
<div @click.self="test"></div>
.once:
只能用一次,无论点击几次,执行一次之后都不会再执行:
<div @click.once="test"></div>
.capture:
事件的完整机制是捕获-目标-冒泡,事件触发是目标往外冒泡,比如:
<div @click="test(1)"> <button @click="test(2)">test</button></div>
顺序是2 1,capture的作用就是让这个顺序相反:
<div @click.capture="test(1)"> <button @click="test(2)">test</button></div>
先1 后2。
.passive:
其实我不怎么理解,官网解释说可以提升移动端的性能,查了查,大概解释就是每次滚动都会有一个默认事件触发,加了这个就是告诉浏览器,不需要查询,不需要触发这个默认事件preventDefault:
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
.native:
组件绑定当前组件的事件是不会触发的,需要用native才能触发:
<My-component @click="shout(3)"></My-component>
鼠标.left、.reight、.middle:
就是鼠标点击的时候就触发:
<button @click.right="test">test</button>
.keyCode:
监听按键的指令,具体可以查看vue的键码对应表:
<input type="text" @keyup.enter="test(1)">
<button @click.enter="test(1)">test</button>
注意,只有你点击过一次或者聚焦到这个输入框才能使用键盘触发。
.exact:
系统修饰键,只有按着这个键然后用鼠标点击才会触发,官网解释:
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
但是我试了一下没有用。
.sync
对prop进行双向绑定,个人暂时用不习惯:
//父组件
<fa-comp :fatest.sync="test"></fa-comp>
//子组件
this.$emit('update:fatest,sontest);
8.scope局部样式的实现原理
当你在单个组件的style标签的内部加上了scoped,在编译的时候就会给当前组件的html标签加上一个data-v-hash的属性并且属性的值是哈希值,我们的样式中用选择器去选到对应的哈希值,从而达到但组件样式的局部化.
9. vue和react当中key的作用
使用key的场景:同级别同类型的节点的时候需要加key
虚拟DOM其实就是把标签的信息,属性,text这些内容以对象的形式储存起来显示
diff算法:在数据发生更新改变的时候,diff算法会对比DOM(对象)前后状态,然后找出差异,只更新差异部分的内容
key的作用除了防止报错以外,写一个类似于无重复的ID的key值让diff算法高效的定位和识别到DOM对象的定位,与DOM对象内容的前后状态是否改变
......
持续更新~
webpack篇
1. 谈谈你对webpack的理解?
webpack是一个打包模块化js的工具,在webpack里一切文件皆模块,通过loader转换文件,通过plugin注入钩子,最后输出由多个模块组合成的文件,webpack专注构建模块化项目。WebPack可以看做是模块的打包机器:它做的事情是,分析你的项目结构,找到js模块以及其它的一些浏览器不能直接运行的拓展语言,例如:Scss,TS等,并将其打包为合适的格式以供浏览器使用。
2.说说webpack与grunt、gulp的不同?
三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。
grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。
webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。
所以,从构建思路来说,gulp和grunt需要开发者将整个前端构建过程拆分成多个Task
,并合理控制所有Task
的调用关系;webpack需要开发者找到入口,并需要清楚对于不同的资源应该使用什么Loader做何种解析和加工
对于知识背景来说,gulp更像后端开发者的思路,需要对于整个流程了如指掌 webpack更倾向于前端开发者的思路
3.什么是bundle,什么是chunk,什么是module?
- bundle:是由webpack打包出来的文件
- chunk:代码块,一个chunk由多个模块组合而成,用于代码的合并和分割
- module:是开发中的单个模块,在webpack的世界,一切皆模块,一个模块对应一个文件,webpack会从配置的entry中递归开始找出所有依赖的模块
4.什么是Loader?什么是Plugin?
1)Loaders是用来告诉webpack如何转化处理某一类型的文件,并且引入到打包出的文件中
2)Plugin是用来自定义webpack打包过程的方式,一个插件是含有apply方法的一个对象,通过这个方法可以参与到整个webpack打包的各个流程(生命周期)。
5.有哪些常见的Loader?他们是解决什么问题的?
- file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
- url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
- source-map-loader:加载额外的 Source Map 文件,以方便断点调试
- image-loader:加载并且压缩图片文件
- babel-loader:把 ES6 转换成 ES5
- css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
- style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
- eslint-loader:通过 ESLint 检查 JavaScript 代码
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。