57 异步编程的实现方式
-
回调函数
- 优点:简单、容易理解
- 缺点:不利于维护,代码耦合高
-
事件监听(采用时间驱动模式,取决于某个事件是否发生):
- 优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
- 缺点:事件驱动型,流程不够清晰
-
发布/订阅(观察者模式)
- 类似于事件监听,但是可以通过‘消息中心’,了解现在有多少发布者,多少订阅者
-
Promise对象
- 优点:可以利用then方法,进行链式写法;可以书写错误时的回调函数;
- 缺点:编写和理解,相对比较难
-
Generator函数
- 优点:函数体内外的数据交换、错误处理机制
- 缺点:流程管理不方便
-
async函数
- 优点:内置执行器、更好的语义、更广的适用性、返回的是Promise、结构清晰。
- 缺点:错误处理机制
58 对原生Javascript了解程度
- 数据类型、运算、对象、Function、继承、闭包、作用域、原型链、事件、
RegExp
、JSON
、Ajax
、DOM
、BOM
、内存泄漏、跨域、异步装载、模板引擎、前端MVC
、路由、模块化、Canvas
、ECMAScript
59 Js动画与CSS动画区别及相应实现
-
CSS3
的动画的优点- 在性能上会稍微好一些,浏览器会对
CSS3
的动画做一些优化 - 代码相对简单
- 在性能上会稍微好一些,浏览器会对
-
缺点
- 在动画控制上不够灵活
- 兼容性不好
-
JavaScript
的动画正好弥补了这两个缺点,控制能力很强,可以单帧的控制、变换,同时写得好完全可以兼容IE6
,并且功能强大。对于一些复杂控制的动画,使用javascript
会比较靠谱。而在实现一些小的交互动效的时候,就多考虑考虑CSS
吧
60 JS 数组和对象的遍历方式,以及几种方式的比较
通常我们会用循环的方式来遍历数组。但是循环是 导致js 性能问题的原因之一。一般我们会采用下几种方式来进行数组的遍历
-
for in
循环 -
for
循环 -
forEach
- 这里的
forEach
回调中两个参数分别为value
,index
forEach
无法遍历对象- IE不支持该方法;
Firefox
和chrome
支持 forEach
无法使用break
,continue
跳出循环,且使用return
是跳过本次循环
- 这里的
-
这两种方法应该非常常见且使用很频繁。但实际上,这两种方法都存在性能问题
-
在方式一中,
for-in
需要分析出array
的每个属性,这个操作性能开销很大。用在key
已知的数组上是非常不划算的。所以尽量不要用for-in
,除非你不清楚要处理哪些属性,例如JSON
对象这样的情况 -
在方式2中,循环每进行一次,就要检查一下数组长度。读取属性(数组长度)要比读局部变量慢,尤其是当
array
里存放的都是DOM
元素,因为每次读取都会扫描一遍页面上的选择器相关元素,速度会大大降低
61 gulp是什么
-
gulp
是前端开发过程中一种基于流的代码构建工具,是自动化项目的构建利器;它不仅能对网站资源进行优化,而且在开发过程中很多重复的任务能够使用正确的工具自动完成 -
Gulp的核心概念:流
-
流,简单来说就是建立在面向对象基础上的一种抽象的处理数据的工具。在流中,定义了一些处理数据的基本操作,如读取数据,写入数据等,程序员是对流进行所有操作的,而不用关心流的另一头数据的真正流向
-
gulp正是通过流和代码优于配置的策略来尽量简化任务编写的工作
-
Gulp的特点:
- 易于使用:通过代码优于配置的策略,gulp 让简单的任务简单,复杂的任务可管理
- 构建快速 利用
Node.js
流的威力,你可以快速构建项目并减少频繁的IO
操作 - 易于学习 通过最少的
API
,掌握gulp
毫不费力,构建工作尽在掌握:如同一系列流管道
62 说一下Vue的双向绑定数据的原理
vue.js
则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应的监听回调
63 事件的各个阶段
-
1:捕获阶段 —> 2:目标阶段 —> 3:冒泡阶段
-
document
—>target
目标 ---->document
-
由此,
addEventListener
的第三个参数设置为true
和false
的区别已经非常清晰了true
表示该元素在事件的“捕获阶段”(由外往内传递时)响应事件false
表示该元素在事件的“冒泡阶段”(由内向外传递时)响应事件
64 let var const
let
- 允许你声明一个作用域被限制在块级中的变量、语句或者表达式
- let绑定不受变量提升的约束,这意味着let声明不会被提升到当前
- 该变量处于从块开始到初始化处理的“暂存死区”
var
- 声明变量的作用域限制在其声明位置的上下文中,而非声明变量总是全局的
- 由于变量声明(以及其他声明)总是在任意代码执行之前处理的,所以在代码中的任意位置声明变量总是等效于在代码开头声明
const
- 声明创建一个值的只读引用 (即指针)
- 基本数据当值发生改变时,那么其对应的指针也将发生改变,故造成
const
申明基本数据类型时 - 再将其值改变时,将会造成报错, 例如
const a = 3
;a = 5
时 将会报错 - 但是如果是复合类型时,如果只改变复合类型的其中某个
Value
项时, 将还是正常使用
65 快速的让一个数组乱序
var arr = [1,2,3,4,5,6,7,8,9,10];
arr.sort(function(){
return Math.random() - 0.5;
})
console.log(arr);
66 如何渲染几万条数据并不卡住界面
这道题考察了如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条都渲染出来,而应该一次渲染部分
DOM
,那么就可以通过requestAnimationFrame
来每16 ms
刷新一次
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul>控件</ul>
<script>
setTimeout(() => {
// 插入十万条数据
const total = 100000
// 一次插入 20 条,如果觉得性能不好就减少
const once = 20
// 渲染数据总共需要几次
const loopCount = total / once
let countOfRender = 0
let ul = document.querySelector("ul");
function add() {
// 优化性能,插入不会造成回流
const fragment = document.createDocumentFragment();
for (let i = 0; i < once; i++) {
const li = document.createElement("li");
li.innerText = Math.floor(Math.random() * total);
fragment.appendChild(li);
}
ul.appendChild(fragment);
countOfRender += 1;
loop();
}
function loop() {
if (countOfRender < loopCount) {
window.requestAnimationFrame(add);
}
}
loop();
}, 0);
</script>
</body>
</html>
67 希望获取到页面中所有的checkbox怎么做?
不使用第三方框架
var domList = document.getElementsByTagName(‘input’)
var checkBoxList = [];
var len = domList.length; //缓存到局部变量
while (len--) { //使用while的效率会比for循环更高
if (domList[len].type == ‘checkbox’) {
checkBoxList.push(domList[len]);
}
}
68 怎样添加、移除、移动、复制、创建和查找节点
创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
添加、移除、替换、插入
appendChild() //添加
removeChild() //移除
replaceChild() //替换
insertBefore() //插入
查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值
getElementById() //通过元素Id,唯一性
69 正则表达式
正则表达式构造函数
var reg=new RegExp(“xxx”)
与正则表达字面量var reg=//
有什么不同?匹配邮箱的正则表达式?
- 当使用
RegExp()
构造函数的时候,不仅需要转义引号(即”表示”),并且还需要双反斜杠(即`\`表示一个
)。使用正则表达字面量的效率更高
邮箱的正则匹配:
var regMail = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/;
70 Javascript中callee和caller的作用?
caller
是返回一个对函数的引用,该函数调用了当前函数;callee
是返回正在被执行的function
函数,也就是所指定的function
对象的正文
那么问题来了?如果一对兔子每月生一对兔子;一对新生兔,从第二个月起就开始生兔子;假定每对兔子都是一雌一雄,试问一对兔子,第n个月能繁殖成多少对兔子?(使用
callee
完成)
var result=[];
function fn(n){ //典型的斐波那契数列
if(n==1){
return 1;
}else if(n==2){
return 1;
}else{
if(result[n]){
return result[n];
}else{
//argument.callee()表示fn()
result[n]=arguments.callee(n-1)+arguments.callee(n-2);
return result[n];
}
}
}
.ready
原生
JS
的window.onload
与Jquery
的$(document).ready(function(){})
有什么不同?如何用原生JS实现Jq的ready
方法?
window.onload()
方法是必须等到页面内包括图片的所有元素加载完毕后才能执行。$(document).ready()
是DOM
结构绘制完毕后就执行,不必等到加载完毕
function ready(fn){
if(document.addEventListener) { //标准浏览器
document.addEventListener('DOMContentLoaded', function() {
//注销事件, 避免反复触发
document.removeEventListener('DOMContentLoaded',arguments.callee, false);
fn(); //执行函数
}, false);
}else if(document.attachEvent) { //IE
document.attachEvent('onreadystatechange', function() {
if(document.readyState == 'complete') {
document.detachEvent('onreadystatechange', arguments.callee);
fn(); //函数执行
}
});
}
};
的区别
addEventListener()
是符合W3C规范的标准方法;attachEvent()
是IE低版本的非标准方法addEventListener()
支持事件冒泡和事件捕获; - 而attachEvent()
只支持事件冒泡addEventListener()
的第一个参数中,事件类型不需要添加on
;attachEvent()
需要添加'on'
- 如果为同一个元素绑定多个事件,
addEventListener()
会按照事件绑定的顺序依次执行,attachEvent()
会按照事件绑定的顺序倒序执行
73 获取页面所有的checkbox
var resultArr= [];
var input = document.querySelectorAll('input');
for( var i = 0; i < input.length; i++ ) {
if( input[i].type == 'checkbox' ) {
resultArr.push( input[i] );
}
}
//resultArr即中获取到了页面中的所有checkbox
74 数组去重方法总结
方法一、利用ES6 Set去重(ES6中最常用)
function unique (arr) {
return Array.from(new Set(arr))
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
方法二、利用for嵌套for,然后splice去重(ES5中最常用)
function unique(arr){
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
arr.splice(j,1);
j--;
}
}
}
return arr;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}] //NaN和{}没有去重,两个null直接消失了
- 双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。
- 想快速学习更多常用的
ES6
语法
方法三、利用indexOf去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array .indexOf(arr[i]) === -1) {
array .push(arr[i])
}
}
return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
// [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}] //NaN、{}没有去重
新建一个空的结果数组,
for
循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push
进数组
方法四、利用sort()
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return;
}
arr = arr.sort()
var arrry= [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i-1]) {
arrry.push(arr[i]);
}
}
return arrry;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
// [0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", undefined] //NaN、{}没有去重
利用
sort()
排序方法,然后根据排序后的结果进行遍历及相邻元素比对
方法五、利用对象的属性不能相同的特点进行去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var arrry= [];
var obj = {};
for (var i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
arrry.push(arr[i])
obj[arr[i]] = 1
} else {
obj[arr[i]]++
}
}
return arrry;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", 15, false, undefined, null, NaN, 0, "a", {…}] //两个true直接去掉了,NaN和{}去重
方法六、利用includes
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array =[];
for(var i = 0; i < arr.length; i++) {
if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
array.push(arr[i]);
}
}
return array
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}] //{}没有去重
方法七、利用hasOwnProperty
function unique(arr) {
var obj = {};
return arr.filter(function(item, index, arr){
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}] //所有的都去重了
利用
hasOwnProperty
判断是否存在对象属性
方法八、利用filter
function unique(arr) {
return arr.filter(function(item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;
});
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]
方法九、利用递归去重
function unique(arr) {
var array= arr;
var len = array.length;
array.sort(function(a,b){ //排序后更加方便去重
return a - b;
})
function loop(index){
if(index >= 1){
if(array[index] === array[index-1]){
array.splice(index,1);
}
loop(index - 1); //递归loop,然后数组去重
}
}
loop(len-1);
return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]
方法十、利用Map数据结构去重
function arrayNonRepeatfy(arr) {
let map = new Map();
let array = new Array(); // 数组用于返回结果
for (let i = 0; i < arr.length; i++) {
if(map .has(arr[i])) { // 如果有该key值
map .set(arr[i], true);
} else {
map .set(arr[i], false); // 如果没有该key值
array .push(arr[i]);
}
}
return array ;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]
创建一个空
Map
数据结构,遍历需要去重的数组,把数组的每一个元素作为key
存到Map
中。由于Map
中不会出现相同的key
值,所以最终得到的就是去重后的结果
方法十一、利用reduce+includes
function unique(arr){
return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[]);
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr));
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]
方法十二、[…new Set(arr)]
[...new Set(arr)]
//代码就是这么少----(其实,严格来说并不算是一种,相对于第一种方法来说只是简化了代码)
75 (设计题)想实现一个对页面某个节点的拖曳?如何做?(使用原生JS)
- 给需要拖拽的节点绑定
mousedown
,mousemove
,mouseup
事件 mousedown
事件触发后,开始拖拽mousemove
时,需要通过event.clientX
和clientY
获取拖拽位置,并实时更新位置mouseup
时,拖拽结束- 需要注意浏览器边界的情况
76 Javascript全局函数和全局变量
全局变量
Infinity
代表正的无穷大的数值。NaN
指示某个值是不是数字值。undefined
指示未定义的值。
全局函数
decodeURI()
解码某个编码的URI
。decodeURIComponent()
解码一个编码的URI
组件。encodeURI()
把字符串编码为 URI。encodeURIComponent()
把字符串编码为URI
组件。escape()
对字符串进行编码。eval()
计算JavaScript
字符串,并把它作为脚本代码来执行。isFinite()
检查某个值是否为有穷大的数。isNaN()
检查某个值是否是数字。Number()
把对象的值转换为数字。parseFloat()
解析一个字符串并返回一个浮点数。parseInt()
解析一个字符串并返回一个整数。String()
把对象的值转换为字符串。unescape()
对由escape()
编码的字符串进行解码
77 使用js实现一个持续的动画效果
定时器思路
var e = document.getElementById('e')
var flag = true;
var left = 0;
setInterval(() => {
left == 0 ? flag = true : left == 100 ? flag = false : ''
flag ? e.style.left = ` ${left++}px` : e.style.left = ` ${left--}px`
}, 1000 / 60)
requestAnimationFrame
//兼容性处理
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
var e = document.getElementById("e");
var flag = true;
var left = 0;
function render() {
left == 0 ? flag = true : left == 100 ? flag = false : '';
flag ? e.style.left = ` ${left++}px` :
e.style.left = ` ${left--}px`;
}
(function animloop() {
render();
requestAnimFrame(animloop);
})();
使用css实现一个持续的动画效果
animation:mymove 5s infinite;
@keyframes mymove {
from {top:0px;}
to {top:200px;}
}
animation-name
规定需要绑定到选择器的keyframe
名称。animation-duration
规定完成动画所花费的时间,以秒或毫秒计。animation-timing-function
规定动画的速度曲线。animation-delay
规定在动画开始之前的延迟。animation-iteration-count
规定动画应该播放的次数。animation-direction
规定是否应该轮流反向播放动画
78 封装一个函数,参数是定时器的时间,.then执行回调函数
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
79 怎么判断两个对象相等?
obj={
a:1,
b:2
}
obj2={
a:1,
b:2
}
obj3={
a:1,
b:'2'
}
可以转换为字符串来判断
JSON.stringify(obj)==JSON.stringify(obj2);//true
JSON.stringify(obj)==JSON.stringify(obj3);//false
80 项目做过哪些性能优化?
- 减少
HTTP
请求数 - 减少
DNS
查询 - 使用
CDN
- 避免重定向
- 图片懒加载
- 减少
DOM
元素数量 - 减少
DOM
操作 - 使用外部
JavaScript
和CSS
- 压缩
JavaScript
、CSS
、字体、图片等 - 优化
CSS Sprite
- 使用
iconfont
- 字体裁剪
- 多域名分发划分内容到不同域名
- 尽量减少
iframe
使用 - 避免图片
src
为空 - 把样式表放在
link
中 - 把
JavaScript
放在页面底部
81 浏览器缓存
浏览器缓存分为强缓存和协商缓存。当客户端请求某个资源时,获取缓存的流程如下
- 先根据这个资源的一些
http header
判断它是否命中强缓存,如果命中,则直接从本地获取缓存资源,不会发请求到服务器; - 当强缓存没有命中时,客户端会发送请求到服务器,服务器通过另一些
request header
验证这个资源是否命中协商缓存,称为http
再验证,如果命中,服务器将请求返回,但不返回资源,而是告诉客户端直接从缓存中获取,客户端收到返回后就会从缓存中获取资源; - 强缓存和协商缓存共同之处在于,如果命中缓存,服务器都不会返回资源; 区别是,强缓存不对发送请求到服务器,但协商缓存会。
- 当协商缓存也没命中时,服务器就会将资源发送回客户端。
- 当
ctrl+f5
强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存; - 当
f5
刷新网页时,跳过强缓存,但是会检查协商缓存;
强缓存
Expires
(该字段是http1.0
时的规范,值为一个绝对时间的GMT
格式的时间字符串,代表缓存资源的过期时间)Cache-Control:max-age
(该字段是http1.1
的规范,强缓存利用其max-age
值来判断缓存资源的最大生命周期,它的值单位为秒)
协商缓存
Last-Modified
(值为资源最后更新时间,随服务器response返回)If-Modified-Since
(通过比较两个时间来判断资源在两次请求期间是否有过修改,如果没有修改,则命中协商缓存)ETag
(表示资源内容的唯一标识,随服务器response
返回)If-None-Match
(服务器通过比较请求头部的If-None-Match
与当前资源的ETag
是否一致来判断资源是否在两次请求之间有过修改,如果没有修改,则命中协商缓存)
82 WebSocket
由于
http
存在一个明显的弊端(消息只能有客户端推送到服务器端,而服务器端不能主动推送到客户端),导致如果服务器如果有连续的变化,这时只能使用轮询,而轮询效率过低,并不适合。于是WebSocket
被发明出来
相比与
http
具有以下有点
- 支持双向通信,实时性更强;
- 可以发送文本,也可以二进制文件;
- 协议标识符是
ws
,加密后是wss
; - 较少的控制开销。连接创建后,
ws
客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2~10
字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的4字节的掩码。而HTTP
协议每次通信都需要携带完整的头部; - 支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)
- 无跨域问题。
实现比较简单,服务端库如
socket.io
、ws
,可以很好的帮助我们入门。而客户端也只需要参照api
实现即可
83 尽可能多的说出你对 Electron 的理解
最最重要的一点,
electron
实际上是一个套了Chrome
的nodeJS
程序
所以应该是从两个方面说开来
Chrome
(无各种兼容性问题);NodeJS
(NodeJS
能做的它也能做)
84 深浅拷贝
浅拷贝
Object.assign
- 或者展开运算符
深拷贝
- 可以通过
JSON.parse(JSON.stringify(object))
来解决
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
该方法也是有局限性的
- 会忽略
undefined
- 不能序列化函数
- 不能解决循环引用的对象
85 防抖/节流
防抖
在滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作。可以通过函数防抖动来实现
// 使用 underscore 的源码来解释防抖动
/**
* underscore 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {boolean} immediate 设置为ture时,是否立即调用函数
* @return {function} 返回客户调用函数
*/
_.debounce = function(func, wait, immediate) {
var timeout, args, context, timestamp, result;
var later = function() {
// 现在和上一次时间戳比较
var last = _.now() - timestamp;
// 如果当前间隔时间少于设定时间且大于0就重新设置定时器
if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
} else {
// 否则的话就是时间到了执行回调函数
timeout = null;
if (!immediate) {
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function() {
context = this;
args = arguments;
// 获得时间戳
timestamp = _.now();
// 如果定时器不存在且立即执行函数
var callNow = immediate && !timeout;
// 如果定时器不存在就创建一个
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
// 如果需要立即执行函数的话 通过 apply 执行
result = func.apply(context, args);
context = args = null;
}
return result;
};
};
整体函数实现
对于按钮防点击来说的实现
- 开始一个定时器,只要我定时器还在,不管你怎么点击都不会执行回调函数。一旦定时器结束并设置为 null,就可以再次点击了
- 对于延时执行函数来说的实现:每次调用防抖动函数都会判断本次调用和之前的时间间隔,如果小于需要的时间间隔,就会重新创建一个定时器,并且定时器的延时为设定时间减去之前的时间间隔。一旦时间到了,就会执行相应的回调函数
节流
防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行
/**
* underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {object} options 如果想忽略开始函数的的调用,传入{leading: false}。
* 如果想忽略结尾函数的调用,传入{trailing: false}
* 两者不能共存,否则函数不能执行
* @return {function} 返回客户调用函数
*/
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
// 之前的时间戳
var previous = 0;
// 如果 options 没传则设为空对象
if (!options) options = {};
// 定时器回调函数
var later = function() {
// 如果设置了 leading,就将 previous 设为 0
// 用于下面函数的第一个 if 判断
previous = options.leading === false ? 0 : _.now();
// 置空一是为了防止内存泄漏,二是为了下面的定时器判断
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
// 获得当前时间戳
var now = _.now();
// 首次进入前者肯定为 true
// 如果需要第一次不执行函数
// 就将上次时间戳设为当前的
// 这样在接下来计算 remaining 的值时会大于0
if (!previous && options.leading === false) previous = now;
// 计算剩余时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果当前调用已经大于上次调用时间 + wait
// 或者用户手动调了时间
// 如果设置了 trailing,只会进入这个条件
// 如果没有设置 leading,那么第一次会进入这个条件
// 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
// 其实还是会进入的,因为定时器的延时
// 并不是准确的时间,很可能你设置了2秒
// 但是他需要2.2秒才触发,这时候就会进入这个条件
if (remaining <= 0 || remaining > wait) {
// 如果存在定时器就清理掉否则会调用二次回调
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
// 判断是否设置了定时器和 trailing
// 没有的话就开启一个定时器
// 并且不能不能同时设置 leading 和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};
};
86 谈谈变量提升?
当执行 JS 代码时,会生成执行环境,只要代码不是写在函数中的,就是在全局执行环境中,函数中的代码会产生函数执行环境,只此两种执行环境
- 接下来让我们看一个老生常谈的例子,
var
b() // call b
console.log(a) // undefined
var a = 'Hello world'
function b() {
console.log('call b')
}
变量提升
这是因为函数和变量提升的原因。通常提升的解释是说将声明的代码移动到了顶部,这其实没有什么错误,便于大家理解。但是更准确的解释应该是:在生成执行环境时,会有两个阶段。第一个阶段是创建的阶段,JS 解释器会找出需要提升的变量和函数,并且给他们提前在内存中开辟好空间,函数的话会将整个函数存入内存中,变量只声明并且赋值为 undefined
,所以在第二个阶段,也就是代码执行阶段,我们可以直接提前使用
在提升的过程中,相同的函数会覆盖上一个函数,并且函数优先于变量提升
b() // call b second
function b() {
console.log('call b fist')
}
function b() {
console.log('call b second')
}
var b = 'Hello world'
复制代码
var
会产生很多错误,所以在ES6
中引入了let
。let
不能在声明前使用,但是这并不是常说的let
不会提升,let
提升了,在第一阶段内存也已经为他开辟好了空间,但是因为这个声明的特性导致了并不能在声明前使用
87 什么是单线程,和异步的关系
-
单线程 - 只有一个线程,只能做一件事
-
原因 - 避免
DOM
渲染的冲突- 浏览器需要渲染
DOM
JS
可以修改DOM
结构JS
执行的时候,浏览器DOM
渲染会暂停- 两段 JS 也不能同时执行(都修改
DOM
就冲突了) webworker
支持多线程,但是不能访问DOM
- 浏览器需要渲染
-
解决方案 - 异步
88 是否用过 jQuery 的 Deferred
89 前端面试之hybrid
http://blog.poetries.top/2018/10/20/fe-interview-hybrid/(opens new window)
90 前端面试之组件化
http://blog.poetries.top/2018/10/20/fe-interview-component/(opens new window)
91 前端面试之MVVM浅析
http://blog.poetries.top/2018/10/20/fe-interview-mvvm/(opens new window)
92 实现效果,点击容器内的图标,图标边框变成border 1px solid red,点击空白处重置
const box = document.getElementById('box');
function isIcon(target) {
return target.className.includes('icon');
}
box.onClick = function(e) {
e.stopPropagation();
const target = e.target;
if (isIcon(target)) {
target.style.border = '1px solid red';
}
}
const doc = document;
doc.onclick = function(e) {
const children = box.children;
for(let i; i < children.length; i++) {
if (isIcon(children[i])) {
children[i].style.border = 'none';
}
}
}
93 请简单实现双向数据绑定mvvm
<input id="input"/>
const data = {};
const input = document.getElementById('input');
Object.defineProperty(data, 'text', {
set(value) {
input.value = value;
this.value = value;
}
});
input.onChange = function(e) {
data.text = e.target.value;
}
var instance = null;
class Storage {
static getInstance() {
if (!instance) {
instance = new Storage();
}
return instance;
}
setItem = (key, value) => localStorage.setItem(key, value),
getItem = key => localStorage.getItem(key)
}
95 说说event loop
首先,
js
是单线程的,主要的任务是处理用户的交互,而用户的交互无非就是响应DOM
的增删改,使用事件队列的形式,一次事件循环只处理一个事件响应,使得脚本执行相对连续,所以有了事件队列,用来储存待执行的事件,那么事件队列的事件从哪里被push
进来的呢。那就是另外一个线程叫事件触发线程做的事情了,他的作用主要是在定时触发器线程、异步HTTP
请求线程满足特定条件下的回调函数push
到事件队列中,等待js
引擎空闲的时候去执行,当然js引擎执行过程中有优先级之分,首先js引擎在一次事件循环中,会先执行js线程的主任务,然后会去查找是否有微任务microtask(promise)
,如果有那就优先执行微任务,如果没有,在去查找宏任务macrotask(setTimeout、setInterval)
进行执行
众所周知
JS
是门非阻塞单线程语言,因为在最初JS
就是为了和浏览器交互而诞生的。如果JS
是门多线程的语言话,我们在多个线程中处理DOM
就可能会发生问题(一个线程中新加节点,另一个线程中删除节点)
JS
在执行的过程中会产生执行环境,这些执行环境会被顺序的加入到执行栈中。如果遇到异步的代码,会被挂起并加入到Task
(有多种task
) 队列中。一旦执行栈为空,Event
Loop
就会从Task
队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说JS
中的异步还是同步行为
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
console.log('script end');
不同的任务源会被分配到不同的
Task
队列中,任务源可以分为 微任务(microtask
) 和 宏任务(macrotask
)。在ES6
规范中,microtask
称为jobs
,macrotask
称为task
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
new Promise((resolve) => {
console.log('Promise')
resolve()
}).then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
// script start => Promise => script end => promise1 => promise2 => setTimeout
以上代码虽然
setTimeout
写在Promise
之前,但是因为Promise
属于微任务而setTimeout
属于宏任务
微任务
process.nextTick
promise
Object.observe
MutationObserver
宏任务
script
setTimeout
setInterval
setImmediate
I/O
UI rendering
宏任务中包括了
script
,浏览器会先执行一个宏任务,接下来有异步代码的话就先执行微任务
所以正确的一次 Event loop 顺序是这样的
- 执行同步代码,这属于宏任务
- 执行栈为空,查询是否有微任务需要执行
- 执行所有微任务
- 必要的话渲染 UI
- 然后开始下一轮
Event loop
,执行宏任务中的异步代码
通过上述的
Event loop
顺序可知,如果宏任务中的异步代码有大量的计算并且需要操作DOM
的话,为了更快的响应界面响应,我们可以把操作DOM
放入微任务中
96 说说事件流
事件流分为两种,捕获事件流和冒泡事件流
- 捕获事件流从根节点开始执行,一直往子节点查找执行,直到查找执行到目标节点
- 冒泡事件流从目标节点开始执行,一直往父节点冒泡查找执行,直到查到到根节点
事件流分为三个阶段,一个是捕获节点,一个是处于目标节点阶段,一个是冒泡阶段
97 JavaScript 对象生命周期的理解
- 当创建一个对象时,
JavaScript
会自动为该对象分配适当的内存 - 垃圾回收器定期扫描对象,并计算引用了该对象的其他对象的数量
- 如果被引用数量为
0
,或惟一引用是循环的,那么该对象的内存即可回收
98 我现在有一个canvas
,上面随机布着一些黑块,请实现方法,计算canvas上有多少个黑块
https://www.jianshu.com/p/f54d265f7aa4
99 请手写实现一个promise
https://segmentfault.com/a/1190000013396601
100 说说从输入URL到看到页面发生的全过程,越详细越好
- 首先浏览器主进程接管,开了一个下载线程。
- 然后进行HTTP请求(DNS查询、IP寻址等等),中间会有三次捂手,等待响应,开始下载响应报文。
- 将下载完的内容转交给Renderer进程管理。
- Renderer进程开始解析css rule tree和dom tree,这两个过程是并行的,所以一般我会把link标签放在页面顶部。
- 解析绘制过程中,当浏览器遇到link标签或者script、img等标签,浏览器会去下载这些内容,遇到时候缓存的使用缓存,不适用缓存的重新下载资源。
- css rule tree和dom tree生成完了之后,开始合成render tree,这个时候浏览器会进行layout,开始计算每一个节点的位置,然后进行绘制。
- 绘制结束后,关闭TCP连接,过程有四次挥手
101 描述一下this
this
,函数执行的上下文,可以通过apply
,call
,bind
改变this
的指向。对于匿名函数或者直接调用的函数来说,this指向全局上下文(浏览器为window,NodeJS为global
),剩下的函数调用,那就是谁调用它,this
就指向谁。当然还有es6的箭头函数,箭头函数的指向取决于该箭头函数声明的位置,在哪里声明,this
就指向哪里
102 说一下浏览器的缓存机制
浏览器缓存机制有两种,一种为强缓存,一种为协商缓存
- 对于强缓存,浏览器在第一次请求的时候,会直接下载资源,然后缓存在本地,第二次请求的时候,直接使用缓存。
- 对于协商缓存,第一次请求缓存且保存缓存标识与时间,重复请求向服务器发送缓存标识和最后缓存时间,服务端进行校验,如果失效则使用缓存
协商缓存相关设置
Exprires
:服务端的响应头,第一次请求的时候,告诉客户端,该资源什么时候会过期。Exprires
的缺陷是必须保证服务端时间和客户端时间严格同步。Cache-control:max-age
:表示该资源多少时间后过期,解决了客户端和服务端时间必须同步的问题,If-None-Match/ETag
:缓存标识,对比缓存时使用它来标识一个缓存,第一次请求的时候,服务端会返回该标识给客户端,客户端在第二次请求的时候会带上该标识与服务端进行对比并返回If-None-Match
标识是否表示匹配。Last-modified/If-Modified-Since
:第一次请求的时候服务端返回Last-modified
表明请求的资源上次的修改时间,第二次请求的时候客户端带上请求头If-Modified-Since
,表示资源上次的修改时间,服务端拿到这两个字段进行对比
103 现在要你完成一个Dialog组件,说说你设计的思路?它应该有什么功能?
- 该组件需要提供
hook
指定渲染位置,默认渲染在body下面。 - 然后改组件可以指定外层样式,如宽度等
- 组件外层还需要一层
mask
来遮住底层内容,点击mask
可以执行传进来的onCancel
函数关闭Dialog
。 - 另外组件是可控的,需要外层传入
visible
表示是否可见。 - 然后
Dialog
可能需要自定义头head和底部footer
,默认有头部和底部,底部有一个确认按钮和取消按钮,确认按钮会执行外部传进来的onOk
事件,然后取消按钮会执行外部传进来的onCancel
事件。 - 当组件的
visible
为true
时候,设置body
的overflow
为hidden
,隐藏body
的滚动条,反之显示滚动条。 - 组件高度可能大于页面高度,组件内部需要滚动条。
- 只有组件的
visible
有变化且为ture
时候,才重渲染组件内的所有内容
104 caller
和callee
的区别
callee
caller
返回一个函数的引用,这个函数调用了当前的函数。
使用这个属性要注意
- 这个属性只有当函数在执行时才有用
- 如果在
javascript
程序中,函数是由顶层调用的,则返回null
functionName.caller: functionName
是当前正在执行的函数。
function a() {
console.log(a.caller)
}
callee
callee
放回正在执行的函数本身的引用,它是arguments
的一个属性
使用callee时要注意:
- 这个属性只有在函数执行时才有效
- 它有一个
length
属性,可以用来获得形参的个数,因此可以用来比较形参和实参个数是否一致,即比较arguments.length
是否等于arguments.callee.length
- 它可以用来递归匿名函数。
function a() {
console.log(arguments.callee)
}
105 ajax、axios、fetch区别
jQuery ajax
$.ajax({
type: 'POST',
url: url,
data: data,
dataType: dataType,
success: function () {},
error: function () {}
});
优缺点:
- 本身是针对
MVC
的编程,不符合现在前端MVVM
的浪潮 - 基于原生的
XHR
开发,XHR
本身的架构不清晰,已经有了fetch
的替代方案 JQuery
整个项目太大,单纯使用ajax
却要引入整个JQuery
非常的不合理(采取个性化打包的方案又不能享受CDN服务)
axios
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
优缺点:
- 从浏览器中创建
XMLHttpRequest
- 从
node.js
发出http
请求 - 支持
Promise API
- 拦截请求和响应
- 转换请求和响应数据
- 取消请求
- 自动转换
JSON
数据 - 客户端支持防止
CSRF/XSRF
fetch
try {
let response = await fetch(url);
let data = response.json();
console.log(data);
} catch(e) {
console.log("Oops, error", e);
}
优缺点:
fetcht
只对网络请求报错,对400
,500
都当做成功的请求,需要封装去处理fetch
默认不会带cookie
,需要添加配置项fetch
不支持abort
,不支持超时控制,使用setTimeout
及Promise.reject
的实现的超时控制并不能阻止请求过程继续在后台运行,造成了量的浪费fetch
没有办法原生监测请求的进度,而XHR可以
106 JavaScript的组成
-
JavaScript
由以下三部分组成:ECMAScript(核心):
JavaScript` 语言基础DOM
(文档对象模型):规定了访问HTML
和XML
的接口BOM
(浏览器对象模型):提供了浏览器窗口之间进行交互的对象和方法
107 检测浏览器版本版本有哪些方式?
- 根据
navigator.userAgent
UA.toLowerCase().indexOf('chrome')
- 根据
window
对象的成员'ActiveXObject' in window
108 介绍JS有哪些内置对象
- 数据封装类对象:
Object
、Array
、Boolean
、Number
、String
- 其他对象:
Function
、Arguments
、Math
、Date
、RegExp
、Error
- ES6新增对象:
Symbol
、Map
、Set
、Promises
、Proxy
、Reflect
109 说几条写JavaScript的基本规范
- 代码缩进,建议使用“四个空格”缩进
- 代码段使用花括号
{}
包裹 - 语句结束使用分号;
- 变量和函数在使用前进行声明
- 以大写字母开头命名构造函数,全大写命名常量
- 规范定义
JSON
对象,补全双引号 - 用
{}
和[]
声明对象和数组
110 如何编写高性能的JavaScript
- 遵循严格模式:
"use strict";
- 将js脚本放在页面底部,加快渲染页面
- 将js脚本将脚本成组打包,减少请求
- 使用非阻塞方式下载js脚本
- 尽量使用局部变量来保存全局变量
- 尽量减少使用闭包
- 使用
window
对象属性方法时,省略window
- 尽量减少对象成员嵌套
- 缓存
DOM
节点的访问 - 通过避免使用
eval()
和Function()
构造器 - 给
setTimeout()
和setInterval()
传递函数而不是字符串作为参数 - 尽量使用直接量创建对象和数组
- 最小化重绘(
repaint
)和回流(reflow
)
111 描述浏览器的渲染过程,DOM树和渲染树的区别
-
浏览器的渲染过程:
- 解析
HTML
构建DOM
(DOM树),并行请求css/image/js
CSS
文件下载完成,开始构建CSSOM
(CSS
树)CSSOM
构建结束后,和DOM
一起生成Render Tree
(渲染树)- 布局(
Layout
):计算出每个节点在屏幕中的位置 - 显示(
Painting
):通过显卡把页面画到屏幕上
- 解析
-
DOM
树 和 渲染树 的区别:DOM
树与HTML
标签一一对应,包括head
和隐藏元素- 渲染树不包括
head
和隐藏元素,大段文本的每一个行都是独立节点,每一个节点都有对应的css
属性
112 script 的位置是否会影响首屏显示时间
- 在解析
HTML
生成DOM
过程中,js
文件的下载是并行的,不需要DOM
处理到script
节点。因此,script
的位置不影响首屏显示的开始时间。 - 浏览器解析
HTML
是自上而下的线性过程,script
作为HTML
的一部分同样遵循这个原则 - 因此,
script
会延迟DomContentLoad
,只显示其上部分首屏内容,从而影响首屏显示的完成时间
113 解释JavaScript中的作用域与变量声明提升
-
JavaScript
作用域:- 在
Java
、C
等语言中,作用域为for语句、if
语句或{}
内的一块区域,称为作用域; - 而在
JavaScript
中,作用域为function(){}
内的区域,称为函数作用域。
- 在
-
JavaScript
变量声明提升:- 在
JavaScript
中,函数声明与变量声明经常被JavaScript
引擎隐式地提升到当前作用域的顶部。 - 声明语句中的赋值部分并不会被提升,只有名称被提升
- 函数声明的优先级高于变量,如果变量名跟函数名相同且未赋值,则函数声明会覆盖变量声明
- 如果函数有多个同名参数,那么最后一个参数(即使没有定义)会覆盖前面的同名参数
- 在
114 JavaScript有几种类型的值?,你能画一下他们的内存图吗
- 原始数据类型(
Undefined
,Null
,Boolean
,Number
、String
)-- 栈 - 引用数据类型(对象、数组和函数)-- 堆
- 两种类型的区别是:存储位置不同:
- 原始数据类型是直接存储在栈(
stack
)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据; - 引用数据类型存储在堆(
heap
)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能; - 引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。
- 当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
115 JavaScript如何实现一个类,怎么实例化这个类
-
构造函数法(
this
+prototype
) – 用new
关键字 生成实例对象- 缺点:用到了
this
和prototype
,编写复杂,可读性差
- 缺点:用到了
function Mobile(name, price){
this.name = name;
this.price = price;
}
Mobile.prototype.sell = function(){
alert(this.name + ",售价 $" + this.price);
}
var iPhone7 = new Mobile("iPhone7", 1000);
iPhone7.sell();
Object.create
法 – 用Object.create()
生成实例对象- 缺点:不能实现私有属性和私有方法,实例对象之间也不能共享数据
var Person = {
firstname: "Mark",
lastname: "Yun",
age: 25,
introduce: function(){
alert('I am ' + Person.firstname + ' ' + Person.lastname);
}
};
var person = Object.create(Person);
person.introduce();
// Object.create 要求 IE9+,低版本浏览器可以自行部署:
if (!Object.create) {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
-
极简主义法(消除
this
和prototype
) – 调用createNew()
得到实例对象- 优点:容易理解,结构清晰优雅,符合传统的"面向对象编程"的构造
var Cat = {
age: 3, // 共享数据 -- 定义在类对象内,createNew() 外
createNew: function () {
var cat = {};
// var cat = Animal.createNew(); // 继承 Animal 类
cat.name = "小咪";
var sound = "喵喵喵"; // 私有属性--定义在 createNew() 内,输出对象外
cat.makeSound = function () {
alert(sound); // 暴露私有属性
};
cat.changeAge = function(num){
Cat.age = num; // 修改共享数据
};
return cat; // 输出对象
}
};
var cat = Cat.createNew();
cat.makeSound();
ES6
语法糖class
– 用new
关键字 生成实例对象
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var point = new Point(2, 3);
116 Javascript如何实现继承
构造函数绑定:使用
call
或apply
方法,将父对象的构造函数绑定在子对象上
function Cat(name,color){
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
- 实例继承:将子对象的 prototype 指向父对象的一个实例
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
拷贝继承:如果把父对象的所有属性和方法,拷贝进子对象
function extend(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
原型继承:将子对象的
prototype
指向父对象的prototype
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
ES6
语法糖extends:class ColorPoint extends Point {}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
117 Javascript作用链域
- 全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节
- 如果当前作用域没有找到属性或方法,会向上层作用域查找,直至全局函数,这种形式就是作用域链
118 介绍 DOM 的发展
DOM
:文档对象模型(Document Object Model
),定义了访问HTML和XML文档的标准,与编程语言及平台无关DOM0
:提供了查询和操作Web文档的内容API。未形成标准,实现混乱。如:document.forms['login']
DOM1
:W3C提出标准化的DOM,简化了对文档中任意部分的访问和操作。如:JavaScript中的Document
对象DOM2
:原来DOM基础上扩充了鼠标事件等细分模块,增加了对CSS的支持。如:getComputedStyle(elem, pseudo)
DOM3
:增加了XPath模块和加载与保存(Load and Save
)模块。如:XPathEvaluator
119 介绍DOM0,DOM2,DOM3事件处理方式区别
-
DOM0级事件处理方式:
btn.onclick = func;
btn.onclick = null;
-
DOM2级事件处理方式:
btn.addEventListener('click', func, false);
btn.removeEventListener('click', func, false);
btn.attachEvent("onclick", func);
btn.detachEvent("onclick", func);
-
DOM3级事件处理方式:
eventUtil.addListener(input, "textInput", func);
eventUtil
是自定义对象,textInput
是DOM3级事件
事件的三个阶段
- 捕获、目标、冒泡
120 介绍事件“捕获”和“冒泡”执行顺序和事件的执行次数
-
按照W3C标准的事件:首是进入捕获阶段,直到达到目标元素,再进入冒泡阶段
-
事件执行次数(DOM2-addEventListener):元素上绑定事件的个数
- 注意1:前提是事件被确实触发
- 注意2:事件绑定几次就算几个事件,即使类型和功能完全一样也不会“覆盖”
-
事件执行顺序:判断的关键是否目标元素
- 非目标元素:根据W3C的标准执行:捕获->目标元素->冒泡(不依据事件绑定顺序)
- 目标元素:依据事件绑定顺序:先绑定的事件先执行(不依据捕获冒泡标准)
- 最终顺序:父元素捕获->目标元素事件1->目标元素事件2->子元素捕获->子元素冒泡->父元素冒泡
- 注意:子元素事件执行前提 事件确实“落”到子元素布局区域上,而不是简单的具有嵌套关系
在一个DOM上同时绑定两个点击事件:一个用捕获,一个用冒泡。事件会执行几次,先执行冒泡还是捕获?
- 该DOM上的事件如果被触发,会执行两次(执行次数等于绑定次数)
- 如果该DOM是目标元素,则按事件绑定顺序执行,不区分冒泡/捕获
- 如果该DOM是处于事件流中的非目标元素,则先执行捕获,后执行冒泡
事件的代理/委托
-
事件委托是指将事件绑定目标元素的到父元素上,利用冒泡机制触发该事件
-
优点:
- 可以减少事件注册,节省大量内存占用
- 可以将事件应用于动态添加的子元素上
-
缺点: 使用不当会造成事件在不应该触发时触发
-
示例:
-
ulEl.addEventListener('click', function(e){
var target = event.target || event.srcElement;
if(!!target && target.nodeName.toUpperCase() === "LI"){
console.log(target.innerHTML);
}
}, false);
W3C事件的 target 与 currentTarget 的区别?
target
只会出现在事件流的目标阶段currentTarget
可能出现在事件流的任何阶段- 当事件流处在目标阶段时,二者的指向相同
- 当事件流处于捕获或冒泡阶段时:
currentTarget
指向当前事件活动的对象(一般为父级)
如何派发事件(dispatchEvent)?(如何进行事件广播?)
- W3C: 使用
dispatchEvent
方法 - IE: 使用
fireEvent
方法
var fireEvent = function(element, event){
if (document.createEventObject){
var mockEvent = document.createEventObject();
return element.fireEvent('on' + event, mockEvent)
}else{
var mockEvent = document.createEvent('HTMLEvents');
mockEvent.initEvent(event, true, true);
return !element.dispatchEvent(mockEvent);
}
}
121 什么是函数节流?介绍一下应用场景和原理?
- 函数节流(
throttle
)是指阻止一个函数在很短时间间隔内连续调用。 只有当上一次函数执行后达到规定的时间间隔,才能进行下一次调用。 但要保证一个累计最小调用间隔(否则拖拽类的节流都将无连续效果) - 函数节流用于
onresize
,onscroll
等短时间内会多次触发的事件 - 函数节流的原理:使用定时器做时间节流。 当触发一个事件时,先用
setTimout
让这个事件延迟一小段时间再执行。 如果在这个时间间隔内又触发了事件,就clearTimeout
原来的定时器, 再setTimeout
一个新的定时器重复以上流程。
函数节流简单实现:
function throttle(method, context) {
clearTimeout(methor.tId);
method.tId = setTimeout(function(){
method.call(context);
}, 100); // 两次调用至少间隔 100ms
}
// 调用
window.onresize = function(){
throttle(myFunc, window);
}
122 区分什么是“客户区坐标”、“页面坐标”、“屏幕坐标”
- 客户区坐标:鼠标指针在可视区中的水平坐标(
clientX
)和垂直坐标(clientY
) - 页面坐标:鼠标指针在页面布局中的水平坐标(
pageX
)和垂直坐标(pageY
) - 屏幕坐标:设备物理屏幕的水平坐标(
screenX
)和垂直坐标(screenY
)
如何获得一个DOM元素的绝对位置?
elem.offsetLef
t:返回元素相对于其定位父级左侧的距离elem.offsetTop
:返回元素相对于其定位父级顶部的距离elem.getBoundingClientRect()
:返回一个DOMRect
对象,包含一组描述边框的只读属性,单位像素
123 解释一下这段代码的意思
[].forEach.call($$("*"), function(el){
el.style.outline = "1px solid #" + (~~(Math.random()*(1<<24))).toString(16);
})
- 解释:获取页面所有的元素,遍历这些元素,为它们添加1像素随机颜色的轮廓(outline)
-
$$(sel)
// $$函数被许多现代浏览器命令行支持,等价于 document.querySelectorAll(sel)
-
[].forEach.call(NodeLists)
// 使用 call 函数将数组遍历函数 forEach 应到节点元素列表
-
el.style.outline = "1px solid #333"
// 样式 outline 位于盒模型之外,不影响元素布局位置
-
(1<<24)
// parseInt(“ffffff”, 16) == 16777215 == 2^24 - 1 // 1<<24 == 2^24 == 16777216
-
Math.random()*(1<<24)
// 表示一个位于 0 到 16777216 之间的随机浮点数
-
~~Math.random()*(1<<24)
//~~
作用相当于 parseInt 取整
-
(~~(Math.random()*(1<<24))).toString(16)
// 转换为一个十六进制-
124 Javascript垃圾回收方法
- 标记清除(mark and sweep)
- 这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”
- 垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
引用计数(reference counting)
在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间
125 请解释一下 JavaScript 的同源策略
- 概念:同源策略是客户端脚本(尤其是Javascript)的重要的安全度量标准。它最早出自Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议
- 指一段脚本只能读取来自同一来源的窗口和文档的属性
为什么要有同源限制?
-
我们举例说明:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。
-
缺点
- 现在网站的JS都会进行压缩,一些文件用了严格模式,而另一些没有。这时这些本来是严格模式的文件,被 merge后,这个串就到了文件的中间,不仅没有指示严格模式,反而在压缩后浪费了字节
126 如何删除一个cookie
- 将时间设为当前时间往前一点
var date = new Date();
date.setDate(date.getDate() - 1);//真正的删除
setDate()
方法用于设置一个月的某一天
expires
的设置
document.cookie = 'user='+ encodeURIComponent('name') + ';expires = ' + new Date(0)
127 页面编码和被请求的资源编码如果不一致如何处理
- 后端响应头设置
charset
- 前端页面
<meta>
设置charset
128 把<script>
放在</body>
之前和之后有什么区别?浏览器会如何解析它们?
- 按照HTML标准,在
</body>
结束后出现<script>
或任何元素的开始标签,都是解析错误 - 虽然不符合HTML标准,但浏览器会自动容错,使实际效果与写在
</body>
之前没有区别 - 浏览器的容错机制会忽略
<script>
之前的</body>
,视作<script>
仍在 body 体内。省略</body>
和</html>
闭合标签符合HTML标准,服务器可以利用这一标准尽可能少输出内容
129 JavaScript 中,调用函数有哪几种方式
- 方法调用模式
Foo.foo(arg1, arg2);
- 函数调用模式
foo(arg1, arg2);
- 构造器调用模式
(new Foo())(arg1, arg2);
call/applay
调用模式Foo.foo.call(that, arg1, arg2);
bind
调用模式Foo.foo.bind(that)(arg1, arg2)();
130 简单实现 Function.bind 函数
if (!Function.prototype.bind) {
Function.prototype.bind = function(that) {
var func = this, args = arguments;
return function() {
return func.apply(that, Array.prototype.slice.call(args, 1));
}
}
}
// 只支持 bind 阶段的默认参数:
func.bind(that, arg1, arg2)();
// 不支持以下调用阶段传入的参数:
func.bind(that)(arg1, arg2);
131 列举一下JavaScript数组和对象有哪些原生方法?
数组:
arr.concat(arr1, arr2, arrn);
arr.join(",");
arr.sort(func);
arr.pop();
arr.push(e1, e2, en);
arr.shift();
unshift(e1, e2, en);
arr.reverse();
arr.slice(start, end);
arr.splice(index, count, e1, e2, en);
arr.indexOf(el);
arr.includes(el);
// ES6
对象:
object.hasOwnProperty(prop);
object.propertyIsEnumerable(prop);
object.valueOf();
object.toString();
object.toLocaleString();
Class.prototype.isPropertyOf(object);
的区别?
slice
“读取”数组指定的元素,不会对原数组进行修改
- 语法:
arr.slice(start, end)
start
指定选取开始位置(含)end
指定选取结束位置(不含)
splice
- “操作”数组指定的元素,会修改原数组,返回被删除的元素
- 语法:
arr.splice(index, count, [insert Elements])
index
是操作的起始位置count = 0
插入元素,count > 0
删除元素[insert Elements]
向数组新插入的元素
133 MVVM
MVVM 由以下三个内容组成
View
:界面Model
:数据模型ViewModel
:作为桥梁负责沟通View
和Model
- 在 JQuery 时期,如果需要刷新 UI 时,需要先取到对应的 DOM 再更新 UI,这样数据和业务的逻辑就和页面有强耦合
- 在 MVVM 中,UI 是通过数据驱动的,数据一旦改变就会相应的刷新对应的 UI,UI 如果改变,也会改变对应的数据。这种方式就可以在业务处理中只关心数据的流转,而无需直接和页面打交道。ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下,View 和 Model 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel
- 在 MVVM 中,最核心的也就是数据双向绑定,例如 Angluar 的脏数据检测,Vue 中的数据劫持
脏数据检测
- 当触发了指定事件后会进入脏数据检测,这时会调用 $digest 循环遍历所有的数据观察者,判断当前值是否和先前的值有区别,如果检测到变化的话,会调用 $watch 函数,然后再次调用 $digest 循环直到发现没有变化。循环至少为二次 ,至多为十次
- 脏数据检测虽然存在低效的问题,但是不关心数据是通过什么方式改变的,都可以完成任务,但是这在 Vue 中的双向绑定是存在问题的。并且脏数据检测可以实现批量检测出更新的值,再去统一更新 UI,大大减少了操作 DOM 的次数
数据劫持
Vue
内部使用了Obeject.defineProperty()
来实现双向绑定,通过这个函数可以监听到set
和get
的事件
var data = { name: 'yck' }
observe(data)
let name = data.name // -> get value
data.name = 'yyy' // -> change value
function observe(obj) {
// 判断类型
if (!obj || typeof obj !== 'object') {
return
}
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
function defineReactive(obj, key, val) {
// 递归子属性
observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log('get value')
return val
},
set: function reactiveSetter(newVal) {
console.log('change value')
val = newVal
}
})
}
以上代码简单的实现了如何监听数据的 set 和 get 的事件,但是仅仅如此是不够的,还需要在适当的时候给属性添加发布订阅
<div>
{{name}}
</div>
在解析如上模板代码时,遇到
{name}
就会给属性name
添加发布订阅
// 通过 Dep 解耦
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
// sub 是 Watcher 实例
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
// 全局属性,通过该属性配置 Watcher
Dep.target = null
function update(value) {
document.querySelector('div').innerText = value
}
class Watcher {
constructor(obj, key, cb) {
// 将 Dep.target 指向自己
// 然后触发属性的 getter 添加监听
// 最后将 Dep.target 置空
Dep.target = this
this.cb = cb
this.obj = obj
this.key = key
this.value = obj[key]
Dep.target = null
}
update() {
// 获得新值
this.value = this.obj[this.key]
// 调用 update 方法更新 Dom
this.cb(this.value)
}
}
var data = { name: 'yck' }
observe(data)
// 模拟解析到 `{{name}}` 触发的操作
new Watcher(data, 'name', update)
// update Dom innerText
data.name = 'yyy'
接下来,对 defineReactive 函数进行改造
function defineReactive(obj, key, val) {
// 递归子属性
observe(val)
let dp = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
console.log('get value')
// 将 Watcher 添加到订阅
if (Dep.target) {
dp.addSub(Dep.target)
}
return val
},
set: function reactiveSetter(newVal) {
console.log('change value')
val = newVal
// 执行 watcher 的 update 方法
dp.notify()
}
})
}
以上实现了一个简易的双向绑定,核心思路就是手动触发一次属性的 getter 来实现发布订阅的添加
Proxy 与 Obeject.defineProperty 对比
-
Obeject.defineProperty
虽然已经能够实现双向绑定了,但是他还是有缺陷的。- 只能对属性进行数据劫持,所以需要深度遍历整个对象
- 对于数组不能监听到数据的变化
虽然
Vue
中确实能检测到数组数据的变化,但是其实是使用了hack
的办法,并且也是有缺陷的
134 WEB应用从服务器主动推送Data到客户端有那些方式
AJAX
轮询html5
服务器推送事件(new EventSource(SERVER_URL)).addEventListener("message", func);
- html5 Websocket
(new WebSocket(SERVER_URL)).addEventListener("message", func);
135 继承
- 原型链继承,将父类的实例作为子类的原型,他的特点是实例是子类的实例也是父类的实例,父类新增的原型方法/属性,子类都能够访问,并且原型链继承简单易于实现,缺点是来自原型对象的所有属性被所有实例共享,无法实现多继承,无法向父类构造函数传参。
- 构造继承,使用父类的构造函数来增强子类实例,即复制父类的实例属性给子类,构造继承可以向父类传递参数,可以实现多继承,通过
call
多个父类对象。但是构造继承只能继承父类的实例属性和方法,不能继承原型属性和方法,无法实现函数服用,每个子类都有父类实例函数的副本,影响性能 - 实例继承,为父类实例添加新特性,作为子类实例返回,实例继承的特点是不限制调用方法,不管是new 子类()还是子类()返回的对象具有相同的效果,缺点是实例是父类的实例,不是子类的实例,不支持多继承
- 拷贝继承:特点:支持多继承,缺点:效率较低,内存占用高(因为要拷贝父类的属性)无法获取父类不可枚举的方法(不可枚举方法,不能使用
for in
访问到) - 组合继承:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
- 寄生组合继承:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
136 this指向
1. this 指向有哪几种
-
默认绑定:全局环境中,
this
默认绑定到window
-
隐式绑定:一般地,被直接对象所包含的函数调用时,也称为方法调用,
this
隐式绑定到该直接对象 -
隐式丢失:隐式丢失是指被隐式绑定的函数丢失绑定对象,从而默认绑定到
window
。显式绑定:通过call()
、apply()
、bind()
方法把对象绑定到this
上,叫做显式绑定 -
new
绑定:如果函数或者方法调用之前带有关键字new
,它就构成构造函数调用。对于this
绑定来说,称为new
绑定- 构造函数通常不使用
return
关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值 - 如果构造函数使用
return
语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果 - 如果构造函数显式地使用
return
语句返回一个对象,那么调用表达式的值就是这个对象
- 构造函数通常不使用
2. 改变函数内部 this 指针的指向函数(bind,apply,call的区别)
apply
:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.apply(A, arguments)
;即A对象应用B对象的方法call
:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.call(A, args1,args2)
;即A对象调用B对象的方法bind
除了返回是函数以外,它的参数和call
一样
3. 箭头函数
- 箭头函数没有
this
,所以需要通过查找作用域链来确定this
的值,这就意味着如果箭头函数被非箭头函数包含,this
绑定的就是最近一层非箭头函数的this
, - 箭头函数没有自己的
arguments
对象,但是可以访问外围函数的arguments
对象 - 不能通过
new
关键字调用,同样也没有new.target
值和原型
137 判断是否是数组
Array.isArray(arr
Object.prototype.toString.call(arr) === '[Object Array]'
arr instanceof Array
array.constructor === Array
138 加载
1. 异步加载js的方法
defer
:只支持IE如果您的脚本不会改变文档的内容,可将defer
属性加入到<script>
标签中,以便加快处理文档的速度。因为浏览器知道它将能够安全地读取文档的剩余部分而不用执行脚本,它将推迟对脚本的解释,直到文档已经显示给用户为止async
:HTML5
属性,仅适用于外部脚本;并且如果在IE中,同时存在defer
和async
,那么defer
的优先级比较高;脚本将在页面完成时执行
2. 图片的懒加载和预加载
- 预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
- 懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数
两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
139 垃圾回收
找出那些不再继续使用的变 量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间), 周期性地执行这一操作。
标记清除
先所有都加上标记,再把环境中引用到的变量去除标记。剩下的就是没用的了
引用计数
跟踪记录每 个值被引用的次数。清除引用次数为0的变量 ⚠️会有循环引用问题 。循环引用如果大量存在就会导致内存泄露。
140 有四个操作会忽略enumerable为false的属性
for...in
循环:只遍历对象自身的和继承的可枚举的属性。Object.keys()
:返回对象自身的所有可枚举的属性的键名。JSON.stringify()
:只串行化对象自身的可枚举的属性。Object.assign()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
141 属性的遍历
ES6 一共有 5 种方法可以遍历对象的属性。
(1)for…in
for...in
循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
(2)Object.keys(obj)
Object.keys
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames
返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols
返回一个数组,包含对象自身的所有 Symbol 属性的键名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys
返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
- 首先遍历所有数值键,按照数值升序排列。
- 其次遍历所有字符串键,按照加入时间升序排列。
- 最后遍历所有 Symbol 键,按照加入时间升序排列。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]
上面代码中,
Reflect.ownKeys
方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性2和10,其次是字符串属性b和a,最后是Symbol
属性。
142 为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图片
- 能够完成整个 HTTP 请求+响应(尽管不需要响应内容)
- 触发 GET 请求之后不需要获取和处理数据、服务器也不需要发送数据
- 跨域友好
- 执行过程无阻塞
- 相比 XMLHttpRequest 对象发送 GET 请求,性能上更好
- GIF的最低合法体积最小(最小的BMP文件需要74个字节,PNG需要67个字节,而合法的GIF,只需要43个字节)
143 在输入框中如何判断输入的是一个正确的网址
function isUrl(url) {
try {
new URL(url);
return true;
}catch(err){
return false;
}}
四、jQuery
1 你觉得jQuery或zepto源码有哪些写的好的地方
jquery
源码封装在一个匿名函数的自执行环境中,有助于防止变量的全局污染,然后通过传入window
对象参数,可以使window
对象作为局部变量使用,好处是当jquery
中访问window
对象的时候,就不用将作用域链退回到顶层作用域了,从而可以更快的访问window
对象。同样,传入undefined
参数,可以缩短查找undefined
时的作用域链
(function( window, undefined ) {
//用一个函数域包起来,就是所谓的沙箱
//在这里边var定义的变量,属于这个函数域内的局部变量,避免污染全局
//把当前沙箱需要的外部变量通过函数参数引入进来
//只要保证参数对内提供的接口的一致性,你还可以随意替换传进来的这个参数
window.jQuery = window.$ = jQuery;
})( window );
- jquery将一些原型属性和方法封装在了jquery.prototype中,为了缩短名称,又赋值给了jquery.fn,这是很形象的写法
- 有一些数组或对象的方法经常能使用到,jQuery将其保存为局部变量以提高访问速度
- jquery实现的链式调用可以节约代码,所返回的都是同一个对象,可以提高代码效率
2 jQuery 的实现原理
(function(window, undefined) {})(window);
jQuery
利用JS
函数作用域的特性,采用立即调用表达式包裹了自身,解决命名空间和变量污染问题window.jQuery = window.$ = jQuery;
- 在闭包当中将 jQuery 和 $ 绑定到 window 上,从而将 jQuery 和 $ 暴露为全局变量
3 jQuery.fn
的 init
方法返回的 this
指的是什么对象
- jQuery.fn 的 init 方法 返回的 this 就是 jQuery 对象
- 用户使用 jQuery() 或 $() 即可初始化 jQuery 对象,不需要动态的去调用 init 方法
4 jQuery.extend 与 jQuery.fn.extend 的区别
$.fn.extend()
和$.extend()
是jQuery
为扩展插件提拱了两个方法$.extend(object)
; // 为jQuery添加“静态方法”(工具方法)
$.extend({
min: function(a, b) { return a < b ? a : b; },
max: function(a, b) { return a > b ? a : b; }
});
$.min(2,3); // 2
$.max(4,5); // 5
- $.extend([true,] targetObject, object1[, object2]); // 对targt对象进行扩展
var settings = {validate:false, limit:5};
var options = {validate:true, name:"bar"};
$.extend(settings, options); // 注意:不支持第一个参数传 false
// settings == {validate:true, limit:5, name:"bar"}
$.fn.extend(json)
; // 为jQuery添加“成员函数”(实例方法)
$.fn.extend({
alertValue: function() {
$(this).click(function(){
alert($(this).val());
});
}
});
$("#email").alertValue();
的实现原理是什么,如何实现深拷贝
- 浅拷贝(只复制一份原始对象的引用)
var newObject = $.extend({}, oldObject);
- 深拷贝(对原始对象属性所引用的对象进行进行递归拷贝)
var newObject = $.extend(true, {}, oldObject);
6 jQuery 的队列是如何实现的
- jQuery 核心中有一组队列控制方法,由
queue()/dequeue()/clearQueue()
三个方法组成。 - 主要应用于
animate()
,ajax
,其他要按时间顺序执行的事件中
var func1 = function(){alert('事件1');}
var func2 = function(){alert('事件2');}
var func3 = function(){alert('事件3');}
var func4 = function(){alert('事件4');}
// 入栈队列事件
$('#box').queue("queue1", func1); // push func1 to queue1
$('#box').queue("queue1", func2); // push func2 to queue1
// 替换队列事件
$('#box').queue("queue1", []); // delete queue1 with empty array
$('#box').queue("queue1", [func3, func4]); // replace queue1
// 获取队列事件(返回一个函数数组)
$('#box').queue("queue1"); // [func3(), func4()]
// 出栈队列事件并执行
$('#box').dequeue("queue1"); // return func3 and do func3
$('#box').dequeue("queue1"); // return func4 and do func4
// 清空整个队列
$('#box').clearQueue("queue1"); // delete queue1 with clearQueue
的区别
bind()
直接绑定在目标元素上live()
通过冒泡传播事件,默认document
上,支持动态数据delegate()
更精确的小范围使用事件代理,性能优于 liveon()
是最新的1.9
版本整合了之前的三种方式的新事件绑定机制
8 是否知道自定义事件
- 事件即“发布/订阅”模式,自定义事件即“消息发布”,事件的监听即“订阅订阅”
- JS 原生支持自定义事件,示例:
document.createEvent(type); // 创建事件
event.initEvent(eventType, canBubble, prevent); // 初始化事件
target.addEventListener('dataavailable', handler, false); // 监听事件
target.dispatchEvent(e); // 触发事件
- jQuery 里的
fire
函数用于调用jQuery
自定义事件列表中的事件
9 jQuery 通过哪个方法和 Sizzle 选择器结合的
Sizzle
选择器采取Right To Left
的匹配模式,先搜寻所有匹配标签,再判断它的父节点jQuery
通过$(selecter).find(selecter);
和Sizzle
选择器结合
10 jQuery 中如何将数组转化为 JSON 字符串,然后再转化回来
// 通过原生 JSON.stringify/JSON.parse 扩展 jQuery 实现
$.array2json = function(array) {
return JSON.stringify(array);
}
$.json2array = function(array) {
// $.parseJSON(array); // 3.0 开始,已过时
return JSON.parse(array);
}
// 调用
var json = $.array2json(['a', 'b', 'c']);
var array = $.json2array(json);
11 jQuery 一个对象可以同时绑定多个事件,这是如何实现的
$("#btn").on("mouseover mouseout", func);
$("#btn").on({
mouseover: func1,
mouseout: func2,
click: func3
});
12 针对 jQuery 的优化方法
- 缓存频繁操作
DOM
对象 - 尽量使用
id
选择器代替class
选择器 - 总是从
#id
选择器来继承 - 尽量使用链式操作
- 使用时间委托
on
绑定事件 - 采用
jQuery
的内部函数data()
来存储数据 - 使用最新版本的
jQuery
13 jQuery 的 slideUp 动画,当鼠标快速连续触发, 动画会滞后反复执行,该如何处理呢
- 在触发元素上的事件设置为延迟处理:使用
JS
原生setTimeout
方法 - 在触发元素的事件时预先停止所有的动画,再执行相应的动画事件:
$('.tab').stop().slideUp();
14 jQuery UI 如何自定义组件
- 通过向
$.widget()
传递组件名称和一个原型对象来完成 $.widget("ns.widgetName", [baseWidget], widgetPrototype);
15 jQuery 与 jQuery UI、jQuery Mobile 区别
jQuery
是JS
库,兼容各种PC浏览器,主要用作更方便地处理DOM
、事件、动画、AJAX
jQuery UI
是建立在jQuery
库上的一组用户界面交互、特效、小部件及主题jQuery Mobile
以jQuery
为基础,用于创建“移动Web应用”的框架
16 jQuery 和 Zepto 的区别? 各自的使用场景
jQuery
主要目标是PC
的网页中,兼容全部主流浏览器。在移动设备方面,单独推出 `jQuery MobileZepto
从一开始就定位移动设备,相对更轻量级。它的
API基本兼容
jQuery`,但对PC浏览器兼容不理想
17 jQuery对象的特点
- 只有
JQuery
对象才能使用JQuery
方法 JQuery
对象是一个数组对象
五、Bootstrap
1 什么是Bootstrap?以及为什么要使用Bootstrap?
Bootstrap
是一个用于快速开发Web
应用程序和网站的前端框架。Bootstrap
是基于HTML
、CSS
、JAVASCRIPT
的
Bootstrap
具有移动设备优先、浏览器支持良好、容易上手、响应式设计等优点,所以Bootstrap
被广泛应用
2 使用Bootstrap时,要声明的文档类型是什么?以及为什么要这样声明?
- 使用
Bootstrap
时,需要使用HTML5
文档类型(Doctype
)。<!DOCTYPE html>
- 因为
Bootstrap
使用了一些HTML5
元素和CSS
属性,如果在Bootstrap
创建的网页开头不使用HTML5
的文档类型(Doctype
),可能会面临一些浏览器显示不一致的问题,甚至可能面临一些特定情境下的不一致,以致于代码不能通过W3C
标准的验证
3 什么是Bootstrap网格系统
Bootstrap
包含了一个响应式的、移动设备优先的、不固定的网格系统,可以随着设备或视口大小的增加而适当地扩展到12
列。它包含了用于简单的布局选项的预定义类,也包含了用于生成更多语义布局的功能强大的混合类
- 响应式网格系统随着屏幕或视口(
viewport
)尺寸的增加,系统会自动分为最多12
列。
4 Bootstrap 网格系统(Grid System)的工作原理
- (1)行必须放置在
.container class
内,以便获得适当的对齐(alignment
)和内边距(padding
)。 - (2)使用行来创建列的水平组。
- (3)内容应该放置在列内,且唯有列可以是行的直接子元素。
- (4)预定义的网格类,比如
.row
和.col-xs-4
,可用于快速创建网格布局。LESS
混合类可用于更多语义布局。 - (5)列通过内边距(
padding
)来创建列内容之间的间隙。该内边距是通过.rows
上的外边距(margin
)取负,表示第一列和最后一列的行偏移。 - (6)网格系统是通过指定您想要横跨的十二个可用的列来创建的。例如,要创建三个相等的列,则使用三个
.col-xs-4
5 对于各类尺寸的设备,Bootstrap设置的class前缀分别是什么
- 超小设备手机(
<768px
):.col-xs-*
- 小型设备平板电脑(
>=768px
):.col-sm-*
- 中型设备台式电脑(
>=992px
):.col-md-*
- 大型设备台式电脑(
>=1200px
):.col-lg-*
6 Bootstrap 网格系统列与列之间的间隙宽度是多少
间隙宽度为
30px
(一个列的每边分别是15px
)
7 如果需要在一个标题的旁边创建副标题,可以怎样操作
在元素两旁添加
<small>
,或者添加.small
的class
8 用Bootstrap,如何设置文字的对齐方式?
class="text-center"
设置居中文本class="text-right"
设置向右对齐文本class="text-left"
设置向左对齐文本
9 Bootstrap如何设置响应式表格?
增加
class="table-responsive"
10 使用Bootstrap创建垂直表单的基本步骤?
- (1)向父
<form>
元素添加role="form"
; - (2)把标签和控件放在一个带有
class="form-group"
的<div>
中,这是获取最佳间距所必需的; - (3)向所有的文本元素
<input>
、<textarea>
、<select>
添加class="form-control"
11 使用Bootstrap创建水平表单的基本步骤?
- (1)向父
<form>
元素添加class="form-horizontal"
; - (2)把标签和控件放在一个带有
class="form-group"
的<div>
中; - (3)向标签添加
class="control-label"
。
12 使用Bootstrap如何创建表单控件的帮助文本?
增加
class="help-block"
的span
标签或p
标签。
13 使用Bootstrap激活或禁用按钮要如何操作?
- 激活按钮:给按钮增加
.active
的class
- 禁用按钮:给按钮增加
disabled="disabled"
的属性
的class?
- (1)
.img-rounded
为图片添加圆角 - (2)
.img-circle
将图片变为圆形 - (3)
.img-thumbnail
缩略图功能 - (4)
.img-responsive
图片响应式 (将很好地扩展到父元素)
15 Bootstrap中有关元素浮动及清除浮动的class?
- (1)
class="pull-left"
元素浮动到左边 - (2)
class="pull-right"
元素浮动到右边 - (3)
class="clearfix"
清除浮动
16 除了屏幕阅读器外,其他设备上隐藏元素的class?
`class=“sr-only”``
17 Bootstrap如何制作下拉菜单?
- (1)将下拉菜单包裹在
class="dropdown"
的<div>
中; - (2)在触发下拉菜单的按钮中添加:
class="btn dropdown-toggle" id="dropdownMenu1" data-toggle="dropdown"
- (3)在包裹下拉菜单的ul中添加:
class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1"
- (4)在下拉菜单的列表项中添加:
role="presentation"
。其中,下拉菜单的标题要添加class="dropdown-header"
,选项部分要添加tabindex="-1"
。
18 Bootstrap如何制作按钮组?以及水平按钮组和垂直按钮组的优先级?
- (1)用
class="btn-group"
的<div>
去包裹按钮组;class="btn-group-vertical"
可设置垂直按钮组。 - (2)
btn-group
的优先级高于btn-group-vertical
的优先级。
19 Bootstrap如何设置按钮的下拉菜单?
在一个
.btn-group
中放置按钮和下拉菜单即可。
20 Bootstrap中的输入框组如何制作?
- (1)把前缀或者后缀元素放在一个带有
class="input-group"
中的<div>
中 - (2)在该
<div>
内,在class="input-group-addon"
的<span>
里面放置额外的内容; - (3)把
<span>
放在<input>
元素的前面或后面。
21 Bootstrap中的导航都有哪些?
- (1)导航元素:有
class="nav nav-tabs"
的标签页导航,还有class="nav nav-pills"
的胶囊式标签页导航; - (2)导航栏:
class="navbar navbar-default" role="navigation"
; - (3)面包屑导航:
class="breadcrumb"
22 Bootstrap中设置分页的class?
- 默认的分页:
class="pagination"
- 默认的翻页:
class="pager"
23 Bootstrap中显示标签的class?
class="label"
24 Bootstrap中如何制作徽章?
<span class="badge">26</span>
25 Bootstrap中超大屏幕的作用是什么?
设置
class="jumbotron"
可以制作超大屏幕,该组件可以增加标题的大小并增加更多的外边距
六、微信小程序
1 微信小程序有几个文件
WXSS (WeiXin Style Sheets)
是一套样式语言,用于描述WXML
的组件样式,js
逻辑处理,网络请求json
小程序设置,如页面注册,页面标题及tabBar
。app.json
必须要有这个文件,如果没有这个文件,项目无法运行,因为微信框架把这个作为配置文件入口,整个小程序的全局配置。包括页面注册,网络设置,以及小程序的window
背景色,配置导航条样式,配置默认标题。app.js
必须要有这个文件,没有也是会报错!但是这个文件创建一下就行 什么都不需要写以后我们可以在这个文件中监听并处理小程序的生命周期函数、声明全局变量。app.wxss
配置全局css
2 微信小程序怎样跟事件传值
给
HTML
元素添加data-*
属性来传递我们需要的值,然后通过e.currentTarget.dataset
或onload
的param
参数获取。但data -
名称不能有大写字母和不可以存放对象
3 小程序的 wxss 和 css 有哪些不一样的地方?
wxss
的图片引入需使用外链地址- 没有
Body
;样式可直接使用import
导入
4 小程序关联微信公众号如何确定用户的唯一性
使用
wx.getUserInfo
方法withCredentials
为true
时 可获取encryptedData
,里面有union_id
。后端需要进行对称解密
5 微信小程序与vue区别
- 生命周期不一样,微信小程序生命周期比较简单
- 数据绑定也不同,微信小程序数据绑定需要使用
{{}}
,vue
直接:
就可以 - 显示与隐藏元素,
vue
中,使用v-if
和v-show
控制元素的显示和隐藏,小程序中,使用wx-if
和hidden
控制元素的显示和隐藏 - 事件处理不同,小程序中,全用
bindtap(bind+event)
,或者catchtap(catch+event)
绑定事件,vue:
使用v-on:event
绑定事件,或者使用@event
绑定事件 - 数据双向绑定也不也不一样在
vue
中,只需要再表单元素上加上v-model
,然后再绑定data
中对应的一个值,当表单元素内容发生变化时,data
中对应的值也会相应改变,这是vue
非常 nice 的一点。微信小程序必须获取到表单元素,改变的值,然后再把值赋给一个data
中声明的变量。
七、webpack相关
1 打包体积 优化思路
- 提取第三方库或通过引用外部文件的方式引入第三方库
- 代码压缩插件
UglifyJsPlugin
- 服务器启用gzip压缩
- 按需加载资源文件
require.ensure
- 优化
devtool
中的source-map
- 剥离
css
文件,单独打包 - 去除不必要插件,通常就是开发环境与生产环境用同一套配置文件导致
2 打包效率
- 开发环境采用增量构建,启用热更新
- 开发环境不做无意义的工作如提取
css
计算文件hash等 - 配置
devtool
- 选择合适的
loader
- 个别
loader
开启cache
如babel-loader
- 第三方库采用引入方式
- 提取公共代码
- 优化构建时的搜索路径 指明需要构建目录及不需要构建目录
- 模块化引入需要的部分
3 Loader
编写一个loader
loader
就是一个node
模块,它输出了一个函数。当某种资源需要用这个loader
转换时,这个函数会被调用。并且,这个函数可以通过提供给它的this
上下文访问Loader API
。reverse-txt-loader
// 定义
module.exports = function(src) {
//src是原文件内容(abcde),下面对内容进行处理,这里是反转
var result = src.split('').reverse().join('');
//返回JavaScript源码,必须是String或者Buffer
return `module.exports = '${result}'`;
}
//使用
{
test: /.txt$/,
use: [
{
'./path/reverse-txt-loader'
}
]
},
4 说一下webpack的一些plugin,怎么使用webpack对项目进行优化
构建优化
- 减少编译体积
ContextReplacementPugin
、IgnorePlugin
、babel-plugin-import
、babel-plugin-transform-runtime
- 并行编译
happypack
、thread-loader
、uglifyjsWebpackPlugin
开启并行 - 缓存
cache-loader
、hard-source-webpack-plugin
、uglifyjsWebpackPlugin
开启缓存、babel-loader
开启缓存 - 预编译
dllWebpackPlugin && DllReferencePlugin
、auto-dll-webapck-plugin
性能优化
- 减少编译体积
Tree-shaking
、Scope Hositing
hash
缓存webpack-md5-plugin
- 拆包
splitChunksPlugin
、import()
、require.ensure
八、编程题
1 写一个通用的事件侦听器函数
// event(事件)工具集,来源:github.com/markyun
markyun.Event = {
// 视能力分别使用dom0||dom2||IE方式 来绑定事件
// 参数: 操作的元素,事件名称 ,事件处理程序
addEvent : function(element, type, handler) {
if (element.addEventListener) {
//事件类型、需要执行的函数、是否捕捉
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, function() {
handler.call(element);
});
} else {
element['on' + type] = handler;
}
},
// 移除事件
removeEvent : function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.datachEvent) {
element.detachEvent('on' + type, handler);
} else {
element['on' + type] = null;
}
},
// 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)
stopPropagation : function(ev) {
if (ev.stopPropagation) {
ev.stopPropagation();
} else {
ev.cancelBubble = true;
}
},
// 取消事件的默认行为
preventDefault : function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
// 获取事件目标
getTarget : function(event) {
return event.target || event.srcElement;
}
2 如何判断一个对象是否为数组
function isArray(arg) {
if (typeof arg === 'object') {
return Object.prototype.toString.call(arg) === '[object Array]';
}
return false;
}
3 冒泡排序
- 每次比较相邻的两个数,如果后一个比前一个小,换位置
var arr = [3, 1, 4, 6, 5, 7, 2];
function bubbleSort(arr) {
for (var i = 0; i < arr.length - 1; i++) {
for(var j = 0; j < arr.length - i - 1; j++) {
if(arr[j + 1] < arr[j]) {
var temp;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
console.log(bubbleSort(arr));
4 快速排序
采用二分法,取出中间数,数组每次和中间数比较,小的放到左边,大的放到右边
快速排序的思想很简单,整个排序过程只需要三步:
- (1)在数据集之中,找一个基准点
- (2)建立两个数组,分别存储左边和右边的数组
- (3)利用递归进行下次比较
var arr = [3, 1, 4, 6, 5, 7, 2];
function quickSort(arr) {
if(arr.length == 0) {
return []; // 返回空数组
}
var cIndex = Math.floor(arr.length / 2);
var c = arr.splice(cIndex, 1);
var l = [];
var r = [];
for (var i = 0; i < arr.length; i++) {
if(arr[i] < c) {
l.push(arr[i]);
} else {
r.push(arr[i]);
}
}
return quickSort(l).concat(c, quickSort(r));
}
console.log(quickSort(arr));
5 编写一个方法 求一个字符串的字节长度
- 假设:一个英文字符占用一个字节,一个中文字符占用两个字节
function GetBytes(str){
var len = str.length;
var bytes = len;
for(var i=0; i<len; i++){
if (str.charCodeAt(i) > 255) bytes++;
}
return bytes;
}
alert(GetBytes("你好,as"));
6 bind的用法,以及如何实现bind的函数和需要注意的点
bind
的作用与call
和apply
相同,区别是call
和apply
是立即调用函数,而bind
是返回了一个函数,需要调用的时候再执行。 一个简单的bind
函数实现如下
Function.prototype.bind = function(ctx) {
var fn = this;
return function() {
fn.apply(ctx, arguments);
};
};
7 实现一个函数clone
可以对
JavaScript
中的5种主要的数据类型,包括Number
、String
、Object
、Array
、Boolean
)进行值复
- 考察点1:对于基本数据类型和引用数据类型在内存中存放的是值还是指针这一区别是否清楚
- 考察点2:是否知道如何判断一个变量是什么类型的
- 考察点3:递归算法的设计
// 方法一:
Object.prototype.clone = function(){
var o = this.constructor === Array ? [] : {};
for(var e in this){
o[e] = typeof this[e] === "object" ? this[e].clone() : this[e];
}
return o;
}
//方法二:
/**
* 克隆一个对象
* @param Obj
* @returns
*/
function clone(Obj) {
var buf;
if (Obj instanceof Array) {
buf = []; //创建一个空的数组
var i = Obj.length;
while (i--) {
buf[i] = clone(Obj[i]);
}
return buf;
}else if (Obj instanceof Object){
buf = {}; //创建一个空对象
for (var k in Obj) { //为这个对象添加新的属性
buf[k] = clone(Obj[k]);
}
return buf;
}else{ //普通变量直接赋值
return Obj;
}
}
8 下面这个ul,如何点击每一列的时候alert其index
考察闭包
<ul id=”test”>
<li>这是第一条</li>
<li>这是第二条</li>
<li>这是第三条</li>
</ul>
// 方法一:
var lis=document.getElementById('2223').getElementsByTagName('li');
for(var i=0;i<3;i++)
{
lis[i].index=i;
lis[i].onclick=function(){
alert(this.index);
}
//方法二:
var lis=document.getElementById('2223').getElementsByTagName('li');
for(var i=0;i<3;i++)
{
lis[i].index=i;
lis[i].onclick=(function(a){
return function() {
alert(a);
}
})(i);
}
9 定义一个log方法,让它可以代理console.log的方法
可行的方法一:
function log(msg) {
console.log(msg);
}
log("hello world!") // hello world!
如果要传入多个参数呢?显然上面的方法不能满足要求,所以更好的方法是:
function log(){
console.log.apply(console, arguments);
};
10 输出今天的日期
以
YYYY-MM-DD
的方式,比如今天是2014年9月26日,则输出2014-09-26
var d = new Date();
// 获取年,getFullYear()返回4位的数字
var year = d.getFullYear();
// 获取月,月份比较特殊,0是1月,11是12月
var month = d.getMonth() + 1;
// 变成两位
month = month < 10 ? '0' + month : month;
// 获取日
var day = d.getDate();
day = day < 10 ? '0' + day : day;
alert(year + '-' + month + '-' + day);
11 用js实现随机选取10–100之间的10个数字,存入一个数组,并排序
var iArray = [];
funtion getRandom(istart, iend){
var iChoice = istart - iend +1;
return Math.floor(Math.random() * iChoice + istart;
}
for(var i=0; i<10; i++){
iArray.push(getRandom(10,100));
}
iArray.sort();
12 写一段JS程序提取URL中的各个GET参数
有这样一个
URL
:http://item.taobao.com/item.htm?a=1&b=2&c=&d=xxx&e
,请写一段JS程序提取URL中的各个GET参数(参数名和参数个数不确定),将其按key-value
形式返回到一个json
结构中,如{a:'1', b:'2', c:'', d:'xxx', e:undefined}
function serilizeUrl(url) {
var result = {};
url = url.split("?")[1];
var map = url.split("&");
for(var i = 0, len = map.length; i < len; i++) {
result[map[i].split("=")[0]] = map[i].split("=")[1];
}
return result;
}
13 写一个function
,清除字符串前后的空格
使用自带接口
trim()
,考虑兼容性:
if (!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+/, "").replace(/\s+$/,"");
}
}
// test the function
var str = " \t\n test string ".trim();
alert(str == "test string"); // alerts "true"
14 实现每隔一秒钟输出1,2,3…数字
for(var i=0;i<10;i++){
(function(j){
setTimeout(function(){
console.log(j+1)
},j*1000)
})(i)
}
15 实现一个函数,判断输入是不是回文字符串
function run(input) {
if (typeof input !== 'string') return false;
return input.split('').reverse().join('') === input;
}
16、数组扁平化处理
实现一个
flatten
方法,使得输入一个数组,该数组里面的元素也可以是数组,该方法会输出一个扁平化的数组
function flatten(arr){
return arr.reduce(function(prev,item){
return prev.concat(Array.isArray(item)?flatten(item):item);
},[]);
}
17、实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number、String、Object、Array、Boolean)进行值复制
Object.prototype.clone = function(){
var o = this.constructor === Array ? [] : {};
for(var e in this){
o[e] = typeof this[e] === "object" ? this[e].clone() : this[e];
}
return o;
}
九、其他
1 负载均衡
多台服务器共同协作,不让其中某一台或几台超额工作,发挥服务器的最大作用
http
重定向负载均衡:调度者根据策略选择服务器以302响应请求,缺点只有第一次有效果,后续操作维持在该服务器 dns负载均衡:解析域名时,访问多个ip
服务器中的一个(可监控性较弱)- 反向代理负载均衡:访问统一的服务器,由服务器进行调度访问实际的某个服务器,对统一的服务器要求大,性能受到 服务器群的数量
2 CDN
内容分发网络,基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。
3 内存泄漏
定义:程序中己动态分配的堆内存由于某种原因程序未释放或无法释放引发的各种问题。
js中可能出现的内存泄漏情况
结果:变慢,崩溃,延迟大等,原因:
- 全局变量
dom
清空时,还存在引用ie
中使用闭包- 定时器未清除
- 子元素存在引起的内存泄露
避免策略
- 减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;
- 注意程序逻辑,避免“死循环”之类的 ;
- 避免创建过多的对象 原则:不用了的东西要及时归还。
- 减少层级过多的引用
4 babel原理
ES6、7
代码输入 ->babylon
进行解析 -> 得到AST
(抽象语法树)->plugin
用babel-traverse
对AST
树进行遍历转译 ->得到新的AST
树->用babel-generator
通过AST
树生成ES5
代码
5 js自定义事件
三要素:
document.createEvent()
event.initEvent()
element.dispatchEvent()
// (en:自定义事件名称,fn:事件处理函数,addEvent:为DOM元素添加自定义事件,triggerEvent:触发自定义事件)
window.onload = function(){
var demo = document.getElementById("demo");
demo.addEvent("test",function(){console.log("handler1")});
demo.addEvent("test",function(){console.log("handler2")});
demo.onclick = function(){
this.triggerEvent("test");
}
}
Element.prototype.addEvent = function(en,fn){
this.pools = this.pools || {};
if(en in this.pools){
this.pools[en].push(fn);
}else{
this.pools[en] = [];
this.pools[en].push(fn);
}
}
Element.prototype.triggerEvent = function(en){
if(en in this.pools){
var fns = this.pools[en];
for(var i=0,il=fns.length;i<il;i++){
fns[i]();
}
}else{
return;
}
}
6 前后端路由差别
- 后端每次路由请求都是重新访问服务器
- 前端路由实际上只是
JS
根据URL
来操作DOM
元素,根据每个页面需要的去服务端请求数据,返回数据后和模板进行组合
十、综合
1 谈谈你对重构的理解
-
网站重构:在不改变外部行为的前提下,简化结构、添加可读性,而在网站前端保持一致的行为。也就是说是在不改变UI的情况下,对网站进行优化, 在扩展的同时保持一致的UI
-
对于传统的网站来说重构通常是:
- 表格(
table
)布局改为DIV+CSS
- 使网站前端兼容于现代浏览器(针对于不合规范的
CSS
、如对IE6有效的) - 对于移动平台的优化
- 针对于
SEO
进行优化
- 表格(
2 什么样的前端代码是好的
- 高复用低耦合,这样文件小,好维护,而且好扩展。
- 具有可用性、健壮性、可靠性、宽容性等特点
- 遵循设计模式的六大原则
3 对前端工程师这个职位是怎么样理解的?它的前景会怎么样
-
前端是最贴近用户的程序员,比后端、数据库、产品经理、运营、安全都近
- 实现界面交互
- 提升用户体验
- 基于NodeJS,可跨平台开发
-
前端是最贴近用户的程序员,前端的能力就是能让产品从 90分进化到 100 分,甚至更好,
-
与团队成员,
UI
设计,产品经理的沟通; -
做好的页面结构,页面重构和用户体验;
4 你觉得前端工程的价值体现在哪
- 为简化用户使用提供技术支持(交互部分)
- 为多个浏览器兼容性提供支持
- 为提高用户浏览速度(浏览器性能)提供支持
- 为跨平台或者其他基于webkit或其他渲染引擎的应用提供支持
- 为展示数据提供支持(数据接口)
5 平时如何管理你的项目
- 先期团队必须确定好全局样式(
globe.css
),编码模式(utf-8
) 等; - 编写习惯必须一致(例如都是采用继承式的写法,单样式都写成一行);
- 标注样式编写人,各模块都及时标注(标注关键样式调用的地方);
- 页面进行标注(例如 页面 模块 开始和结束);
CSS
跟HTML
分文件夹并行存放,命名都得统一(例如style.css
);JS
分文件夹存放 命名以该JS
功能为准的英文翻译。- 图片采用整合的
images.png png8
格式文件使用 - 尽量整合在一起使用方便将来的管理
- 规定全局样式、公共脚本
- 严格要求代码注释(html/js/css)
- 严格要求静态资源存放路径
- Git 提交必须填写说明
6 组件封装
目的:为了重用,提高开发效率和代码质量 注意:低耦合,单一职责,可复用性,可维护性 常用操作
- 分析布局
- 初步开发
- 化繁为简
- 组件抽象
7 Web 前端开发的注意事项
- 特别设置 meta 标签 viewport
- 百分比布局宽度,结合 box-sizing: border-box;
- 使用 rem 作为计算单位。rem 只参照跟节点 html 的字体大小计算
- 使用 css3 新特性。弹性盒模型、多列布局、媒体查询等
- 多机型、多尺寸、多系统覆盖测试
8 在设计 Web APP 时,应当遵循以下几点
- 简化不重要的动画/动效/图形文字样式
- 少用手势,避免与浏览器手势冲突
- 减少页面内容,页面跳转次数,尽量在当前页面显示
- 增强
Loading
趣味性,增强页面主次关系
9 你怎么看待 Web App/hybrid App/Native App?(移动端前端 和 Web 前端区别?)
-
Web App(HTML5):采用HTML5生存在浏览器中的应用,不需要下载安装
- 优点:开发成本低,迭代更新容易,不需用户升级,跨多个平台和终端
- 缺点:消息推送不够及时,支持图形和动画效果较差,功能使用限制(相机、GPS等)
-
Hybrid App(混合开发):UI WebView,需要下载安装
- 优点:接近 Native App 的体验,部分支持离线功能
- 缺点:性能速度较慢,未知的部署时间,受限于技术尚不成熟
-
Native App(原生开发):依托于操作系统,有很强的交互,需要用户下载安装使用
- 优点:用户体验完美,支持离线工作,可访问本地资源(通讯录,相册)
- 缺点:开发成本高(多系统),开发成本高(版本更新),需要应用商店的审核
10 页面重构怎么操作
网站重构:不改变UI的情况下,对网站进行优化,在扩展的同时保持一致的UI。
-
页面重构可以考虑的方面:
- 升级第三方依赖
- 使用
HTML5
、CSS3
、ES6
新特性 - 加入响应式布局
- 统一代码风格规范
- 减少代码间的耦合
- 压缩/合并静态资源
- 程序的性能优化
- 采用
CDN
来加速资源加载 - 对于
JS DOM
的优化 - HTTP服务器的文件缓存
十一、一些常见问题
- 自我介绍
- 面试完你还有什么问题要问的吗
- 你有什么爱好?
- 你最大的优点和缺点是什么?
- 你为什么会选择这个行业,职位?
- 你觉得你适合从事这个岗位吗?
- 你有什么职业规划?
- 你对工资有什么要求?
- 如何看待前端开发?
- 未来三到五年的规划是怎样的?
- 你的项目中技术难点是什么?遇到了什么问题?你是怎么解决的?
- 你们部门的开发流程是怎样的
- 你认为哪个项目做得最好?
- 说下工作中你做过的一些性能优化处理
- 最近在看哪些前端方面的书?
- 平时是如何学习前端开发的?
- 你最有成就感的一件事
- 你为什么要离开前一家公司?
- 你对加班的看法
- 你希望通过这份工作获得什么?
十二、HR面
你还有其他公司的Offer吗?
- 表明自己有三四个已经确认过的offer了(没有offer也要吹,但是不要透露具体公司)
- 但是第一意向还是本公司,如果薪资差距不大,会优先考虑本公司
- 再透露出,有一两个offer催得比较急,希望这边快点出结果
为什么从上一家公司离职?
- 因为公司搬家,通勤时间三个小时
- 因为离家远花费在路途上的时间过多,不如用来充电,因为加班多导致没有时间充电,无法提高等等
?
- 把加班分为紧急加班和长期加班
- 对于紧急加班,表示这是每个公司都会遇到的情况,自己愿意牺牲时间帮助公司和团队
- 对于长期加班,如果是自己长期加班那么会磨练自己的技能,提高自己的效率,如果是团队长期加班,自己会帮助团队找到问题,利用自动化工具或者更高效的协作流程来提高整个团队的效率,帮助大家摆脱加班
你对未来3-5年的职业规划
- 首先表示考虑过这个问题(有规划),如何谈一谈自己的现状(结合实际).
- 接着从工作本身出发,谈谈自己会如何出色完成本职工作,如何对团队贡献、如何帮助带领团队其他成员创造更多的价值、如何帮助团队扩大影响力.
- 最后从学习出发,谈谈自己会如何精进领域知识、如何通过提升自己专业能力,如何反哺团队.
如何与HR谈薪资
正确的回答可以这样,并且还能够反套路一下HR
- HR: 您期望的薪资是多少?
- 你: 就我的面试表现,贵公司最高可以给多少薪水?
如果经验不够老道的HR可能就真会说出一个报价(如25K)来,然后,你就可以很开心地顺着这个价慢慢地往上谈了。所以这种情况下,你最终的薪资肯定是大于25K的。当然,经验老道的HR会给你一句很官方的套话:
- HR: 您期望的薪资是多少?
- 你: 就我的面试表现,贵公司最高可以给多少薪水?
- HR: 这个暂且没法确定,要结合您几轮面试结果和用人部门的意见来综合评定。
虽然薪资很重要,但是我个人觉得这不是最重要的。我有以下建议:
- 如果你觉得你技术面试效果很好,可以报一个高一点的薪资,这样如果HR想要你,会找你商量的。
- 如果你觉得技术面试效果一般,但是你比较想进这家公司,可以报一个折中的薪资。
- 如果你觉得面试效果很好,但是你不想进这家公司,你可以适当“漫天要价”一下。
- 如果你觉得面试效果不好,但是你想进这家公司,你可以开一个稍微低一点的工资