开篇
本文参考文章《一名【合格】前端工程师的自检清单》, 并对其中的部分题目进行了解答,若有遗漏或错误之处望大家指出纠正,共同进步。(点击题目展开答案!)
此文章 Markdown 源文件地址:https://github.com/zxpsuper/blog…
一、JavaScript 基础
前端工程师吃饭的家伙,深度、广度一样都不能差。
变量和类型
1. JavaScript 规定了几种语言类型?
JavaScript 中的每一个值都有它自己的类型,JavaScript 规定了七种语言类型,他们是:
Undefined Null Boolean String Number Symbol Object
2. JavaScript 对象的底层数据结构是什么?
对象数据被存储于堆中 (如对象、数组、函数等,它们是通过拷贝和 new 出来的)。
引用类型的数据的地址指针是存储于栈中的,当我们想要访问引用类型的值的时候,需要先从栈中获得对象的地址指针,然后,在通过地址指针找到堆中的所需要的数据。
3. Symbol 类型在实际开发中的应用、可手动实现一个简单的 Symbol?
ES6
引入了一种新的原始数据类型 Symbol
,表示独一无二的值。
symbol
类型的 key
不能被 Object.keys
和 for..of
循环枚举。因此可当作私有变量使用。
let mySymbol = Symbol('key');
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {[mySymbol]: 'Hello!'
};
4. JavaScript 中的变量在内存中的具体存储形式
JavaScript
中的变量分为基本类型和引用类型:
基本类型: 保存在栈内存中的简单数据段,它们的值都有固定的大小,保存在栈空间,通过按值访问
引用类型: 保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,JavaScript
不允许直接访问堆内存中的位置,因此操作对象时,实际操作对象的引用
5. 基本类型对应的内置对象,以及他们之间的装箱拆箱操作
String(), Number(), Boolean()
装箱:就是把基本类型转变为对应的对象。装箱分为隐式和显示
// 隐式装箱:每当读取一个基本类型的值时,后台会创建一个该基本类型所对应的对象。// 在这个基本类型上调用方法,其实是在这个基本类型对象上调用方法。// 这个基本类型的对象是临时的,它只存在于方法调用那一行代码执行的瞬间,执行方法后立刻被销毁。let num=123;
num.toFixed(2); // '123.00'// 上方代码在后台的真正步骤为
var c = new Number(123);
c.toFixed(2);
c = null;
// 显式装箱: 通过内置对象 Boolean、Object、String 等可以对基本类型进行显示装箱。var obj = new String('123');
拆箱: 拆箱与装箱相反,把对象转变为基本类型的值。
Number([1]); //1
// 转换演变:[1].valueOf(); // [1];
[1].toString(); // '1';Number('1'); //1
6. 理解值类型和引用类型
JavaScript 中的变量分为基本类型和引用类型:
基本类型: 保存在栈内存中的简单数据段,它们的值都有固定的大小,保存在栈空间,通过按值访问
引用类型: 保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,JavaScript
不允许直接访问堆内存中的位置,因此操作对象时,实际操作对象的引用
7. null 和 undefined 的区别
-
Number
转换的值不同,Number(null)
输出为0
,Number(undefined)
输出为NaN
-
null
表示一个值被定义了,但是这个值是空值 -
undefined
表示缺少值,即此处应该有值,但是还没有定义
8. 至少可以说出三种判断 JavaScript 数据类型的方式,以及他们的优缺点,如何准确的判断数组类型
-
typeof
—— 返回给定变量的数据类型,可能返回如下字符串:'undefined'——Undefined 'boolean'——Boolean 'string'——String 'number'——Number 'symbol'——Symbol 'object'——Object / Null(Null 为空对象的引用)'function'——Function // 对于一些如 error() date() array()无法判断,都是显示 object 类型
-
instanceof
检测constructor.prototype
是否存在于参数object
的原型链上,是则返回true
,不是则返回false
。alert([1,2,3] instanceof Array) // true alert(new Date() instanceof Date) // true alert(function(){this.name="22";} instanceof Function) //true alert(function(){this.name="22";} instanceof function) //false // instanceof 只能用来判断两个对象是否属于实例关系,而不能判断一个对象实例具体属于哪种类型。
-
constructor
—— 返回对象对应的构造函数。alert({}.constructor === Object); => true alert([].constructor === Array); => true alert('abcde'.constructor === String); => true alert((1).constructor === Number); => true alert(true.constructor === Boolean); => true alert(false.constructor === Boolean); => true alert(function s(){}.constructor === Function); => true alert(new Date().constructor === Date); => true alert(new Array().constructor === Array); => true alert(new Error().constructor === Error); => true alert(document.constructor === HTMLDocument); => true alert(window.constructor === Window); => true alert(Symbol().constructor); => undefined // null 和 undefined 是无效的对象,没有 constructor,因此无法通过这种方式来判断。
-
Object.prototype.toString()
默认返回当前对象的[[Class]]
。这是一个内部属性,其格式为[object Xxx]
,是一个字符串,其中Xxx
就是对象的类型。Object.prototype.toString.call(new Date);//[object Date] Object.prototype.toString.call(new String);//[object String] Object.prototype.toString.call(Math);//[object Math] Object.prototype.toString.call(undefined);//[object Undefined] Object.prototype.toString.call(null);//[object Null] Object.prototype.toString.call('') ; // [object String] Object.prototype.toString.call(123) ; // [object Number] Object.prototype.toString.call(true) ; // [object Boolean] Object.prototype.toString.call(Symbol()); //[object Symbol] Object.prototype.toString.call(new Function()) ; // [object Function] Object.prototype.toString.call(new Date()) ; // [object Date] Object.prototype.toString.call([]) ; // [object Array] Object.prototype.toString.call(new RegExp()) ; // [object RegExp] Object.prototype.toString.call(new Error()) ; // [object Error] Object.prototype.toString.call(document) ; // [object HTMLDocument] Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用 // 比较全面
9. 可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用
隐式转换一般说的是 Boolean
的转换
在 if
语句中,null
,""
,undefinded
, 0
, false
都会被转化为 false
一般应用于对接口数据判空时使用
10. 出现小数精度丢失的原因,JavaScript 可以存储的最大数字、最大安全数字,JavaScript 处理大数字的方法、避免精度丢失的方法
- 精度丢失原因,说是
JavaScript
使用了IEEE 754
规范,二进制储存十进制的小数时不能完整的表示小数 - 能够表示的最大数字
Number.MAX_VALUE
等于1.7976931348623157e+308
, 最大安全数字Number.MAX_SAFE_INTEGER
等于9007199254740991
-
避免精度丢失
- 计算小数时,先乘
100
或1000
,变成整数再运算 - 如果值超出了安全整数,有一个最新提案,
BigInt
大整数,它可以表示任意大小的整数,注意只能表示整数,而不受安全整数的限制
- 计算小数时,先乘
原型和原型链
1. 理解原型设计模式以及 JavaScript 中的原型规则
A. 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性;B. 所有的引用类型(数组、对象、函数),都有一个 `__proto__` 属性(隐式原型),属性值是一个普通的对象;C. 所有的函数,都具有一个 `prototype`(显式原型),属性值也是一个普通对象;D. 所有的引用类型(数组、对象、函数),其隐式原型指向其构造函数的显式原型;`(obj._proto_ === Object.prototype)`;E. 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的 `__proto__`(即它的构造函数的 `prototype`)中去寻找;
2. instanceof 的底层实现原理,手动实现一个 instanceof
简单说就是判断实例对象的 __proto__
是不是强等于对象的 prototype
属性,如果不是继续往原型链上找,直到 __proto__
为 null
为止。
function instanceOf(obj, object) {//obj 表示实例对象,object 表示对象
var O = object.prototype;
obj = obj.__proto__;
while (true) {if (obj === null)
return false;
if (O === obj) // 这里重点:当 O 严格等于 obj 时,返回 true
return true;
obj = obj.__proto__;
}
}
3. 理解 JavaScript 的执行上下文栈,可以应用堆栈信息快速定位问题
执行上下文 就是当前 JavaScript
代码被解析和执行时所在环境的抽象概念,JavaScript
中运行任何的代码都是在执行上下文中运行。
执行上下文总共有三种类型:全局执行上下文, 函数执行上下文, Eval
函数执行上下文
执行栈,在其他编程语言中也被叫做调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。
4. 实现继承的几种方式以及他们的优缺点
详情请点击:《继承的几种实现方式》
5. 可以描述 new 一个对象的详细过程,手动实现一个 new 操作符
-
new
一个对象的详细过程:function Test() {} const test = new Test();
- 创建一个对象
const obj = {}
-
设置新对象的
constructor
属性为构造函数的名称,设置新对象的__proto__
属性指向构造函数的prototype
对象obj.constructor = Test; obj.__proto__ = Test.prototype;
- 使用新对象调用函数,函数中的 this 被指向新实例对象
Test.call(obj)
- 将初始化完毕的新对象地址,保存到等号左边的变量中
-
实现一个
new
操作符function myNew(Obj,...args){var obj = Object.create(Obj.prototype);// 使用指定的原型对象及其属性去创建一个新的对象 Obj.apply(obj,args); // 绑定 this 到 obj, 设置 obj 的属性 return obj; // 返回实例 }
6. 理解 es6 class 构造以及继承的底层实现原理
-
ES6
类的底层还是通过构造函数去创建的。// es6 Parent 类实现 class Parent {constructor(name,age){ this.name = name; this.age = age; } speakSomething(){console.log("I can speek chinese"); } } // 转化为 var Parent = function () {function Parent(name, age) {_classCallCheck(this, Parent); // 判断实例 Parent instanceof Parent(函数)是否为 true this.name = name; this.age = age; } // 此方法通过使用 Object.defineProperty 为 function Parent 的 prototype 添加属性值 _createClass(Parent, [{ key: "speakSomething", value: function speakSomething() {console.log("I can speek chinese"); } }]); return Parent; }();
-
ES6
的继承实现// 定义子类,继承父类 class Child extends Parent { static width = 18 constructor(name,age){super(name,age); } coding(){console.log("I can code JS"); } } // 转化为 var Child = function (_Parent) {_inherits(Child, _Parent); function Child(name, age) {_classCallCheck(this, Child); return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name, age)); } _createClass(Child, [{ key: "coding", value: function coding() {console.log("I can code JS"); } }]); return Child; }(Parent);
这里其实就是多了一个 _inherits(Child, _Parent);
方法,实现了以下功能,具体可看文章《ES6 类以及继承的实现原理》
// 实现的结果是:subClass.prototype.__proto__ = superClass.prototype
subClass.__proto__ = superClass // 实现静态属性的继承
作用域和闭包
1. 理解词法作用域和动态作用域
词法作用域也称静态作用域,javascript
采用静态作用域
静态作用域 —— 函数的作用域基于函数创建的位置。
动态作用域 —— 函数的作用域基于函数的使用位置。
var value = 1;
function foo() {console.log(value);
}
function bar() {
var value = 2;
foo();}
bar(); // 输出 1。JavaScript 采用的是词法作用域,也称为静态作用域。相同的,动态作用域此代码应该输出 2
2. 理解 JavaScript 的作用域和作用域链
作用域(scope)就是变量访问规则的有效范围。
在 JavaScript
中全局变量的作用域是全局的,在代码的任何地方都是有定义的。然而函数的参数和局部变量只在函数体内有定义。另外局部变量的优先级要高于同名的全局变量, 也就是说当局部变量与全局变量重名时,局部变量会覆盖全局变量。
3. this 的原理以及几种不同使用场景的取值
this 的几种不同使用场景的取值 +
<a href="http://www.ruanyifeng.com/blog/2018/06/javascript-this.html" target="_blank">JavaScript 的 this 原理 </a>
4. 闭包的实现原理和作用,可以列举几个开发中闭包的实际应用
原理:闭包就是能够读取其他函数内部变量的函数。由于在 Javascript 语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成 ” 定义在一个函数内部的函数 ”。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
作用:闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
应用:1. 匿名自执行函数 2. 结果缓存 3. 封装局部变量
参考链接:《学习 Javascript 闭包(Closure)》
5. 理解堆栈溢出和内存泄漏的原理,如何防止
堆栈溢出 的产生是由于过多的函数调用,导致调用堆栈无法容纳这些调用的返回地址,一般在递归中产生。堆栈溢出很可能由无限递归(Infinite recursion)产生,但也可能仅仅是过多的堆栈层级.
参考链接:《内存泄漏与避免》
6. 如何处理循环的异步操作
- 将异步操作变同步,使用 async/await.
- 去掉循环,将循环变成递归
执行机制
1. 为何 try 里面放 return,finally 还会执行,理解其内部机制
在 try
语句中,在执行 return
语句时,要返回的结果已经准备好了,就在此时,程序转到 finally
执行了。
在转去之前,try
中先把要返回的结果存放到局部变量中去,执行完 finally
之后,在从中取出返回结果。
因此,即使finally
中对返回的结果进行了改变,但是不会影响返回结果。
它应该使用栈保存返回值。
2. JavaScript 如何实现异步编程,可以详细描述 EventLoop 机制
JavaScript
如何实现异步编程:
-
callback
(回调函数)
回调函数代表着,当某个任务处理完,然后需要做的事。比如读取文件,连接数据库,等文件准备好,或数据库连接成功执行编写的回调函数,又比如像一些动画处理,当动画走完,然后执行回调。
- 发布订阅模式
顾名思义,便是先订阅了事件,有人一发布事件你就知道了,接着执行后面的操作。
Promise
Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件的结果,相比回调函数,Promise
提供统一的 API
,各种异步操作都可以用同样的方法进行处理。
-
Generator
(生成器)函数
Generator
函数是 ES6
提供的一种异步编程解决方案,其行为类似于状态机。
async/await
async/await
本质上还是基于 Generator
函数,可以说是 Generator
函数的语法糖,async
就相当于之前写的 run 函数 (执行Generator
函数的函数), 而 await
就相当于 yield
,只不过 await
表达式后面只能跟着 Promise
对象,如果不是 Promise
对象的话,会通过 Promise.resolve
方法使之变成 Promise
对象。async
修饰 function
, 其返回一个 Promise
对象。
《浏览器 Event Loop 机制》
3. 宏任务和微任务分别有哪些
宏任务:setTimeout,setInterval,setImmediate (Node 独有),requestAnimationFrame (浏览器独有),I/O,UI rendering (浏览器独有)
微任务:process.nextTick (Node 独有),Promise,Object.observe,MutationObserver
4. 可以快速分析一个复杂的异步嵌套逻辑,并掌握分析方法
// 执行顺序,先微队列,后宏队列。console.log(1);
setTimeout(() => {console.log(2);
setTimeout(() => {console.log(8);
})
Promise.resolve().then(() => {console.log(3)
});
});
new Promise((resolve, reject) => {console.log(4)
setTimeout(() => {console.log(10);
})
resolve()}).then(() => {console.log(5);
Promise.resolve().then(() => {console.log(11)
});
setTimeout(() => {console.log(13);
})
})
setTimeout(() => {Promise.resolve().then(() => {console.log(9)
});
console.log(6);
setTimeout(() => {console.log(12);
})
})
console.log(7);
从头至尾执行一次代码, 根据上面分类规则分至不同队列, new promise(function)
也是立即执行。setTimeout
的回调函数属于宏队列(macrotask)
,resolve
的回调函数属于微队列
// 栈区(stack)console.log(1);
console.log(4);
console.log(7);
// 宏队列
() => {console.log(2);
setTimeout(() => {console.log(8);
})
Promise.resolve().then(() => {console.log(3)
});
}
() => {console.log(10);
}
() => {Promise.resolve().then(() => {console.log(9)
});
console.log(6);
setTimeout(() => {console.log(12);
})
}
// 微队列
() => {console.log(5);
Promise.resolve().then(() => {console.log(11)
});
setTimeout(() => {console.log(13);
})
}
优先执行微队列,微队列执行过程中产生的微队列和宏队列置于队列末尾排序执行,而宏队列产生的微队列和宏队列于新的队列中等待。。
执行微队列:(分类)
// 栈区(stack)console.log(1);
console.log(4);
console.log(7);
//////////
console.log(5);
// 微队列
() => {console.log(11)
});
// 宏队列
() => {console.log(2);
setTimeout(() => {console.log(8);
})
Promise.resolve().then(() => {console.log(3)
});
}
() => {console.log(10);
}
() => {Promise.resolve().then(() => {console.log(9)
});
console.log(6);
setTimeout(() => {console.log(12);
})
}
() => {console.log(13);
}
此时新增了一个微队列console.log(11)
, 因为是微队列产生的,继续执行:
// 栈区(stack)console.log(1);
console.log(4);
console.log(7);
//////////
console.log(5);
/////////
console.log(11)
// 微队列 - 空
// 宏队列
() => {console.log(2);
setTimeout(() => {console.log(8);
})
Promise.resolve().then(() => {console.log(3)
});
}
() => {console.log(10);
}
() => {Promise.resolve().then(() => {console.log(9)
});
console.log(6);
setTimeout(() => {console.log(12);
})
}
() => {console.log(13);
}
执行完微队列后执行宏队列:
// 栈区(stack)console.log(1);
console.log(4);
console.log(7);
//////////
console.log(5);
/////////
console.log(11);
/////////
console.log(2);
console.log(10);
console.log(6);
console.log(13);
// 微队列
() => {console.log(3)
}
() => {console.log(9)
}
// 宏队列
() => {console.log(8);
}
() => {console.log(12);
}
接下来执行微队列后宏队列,即:
// 栈区(stack)console.log(1);
console.log(4);
console.log(7);
//////////
console.log(5);
/////////
console.log(11);
/////////
console.log(2);
console.log(10);
console.log(6);
console.log(13);
////////
console.log(3)
console.log(9)
////////
console.log(8);
console.log(12);
5. 使用 Promise 实现串行
// 一个 promise 的 function
function delay(time) {return new Promise((resolve, reject) => {console.log(`wait ${time}s`)
setTimeout(() => {console.log('execute');
resolve()}, time * 1000)
})
}
const arr = [3, 4, 5];
-
reduce
arr.reduce((s, v) => {return s.then(() => delay(v)) }, Promise.resolve())
-
async
+ 循环 +await
(async function () {for (const v of arr) {await delay(v) } } )()
-
普通循环
let p = Promise.resolve() for (const i of arr) {p = p.then(() => delay(i)) }
-
递归
function dispatch(i, p = Promise.resolve()) {if (!arr[i]) return Promise.resolve() return p.then(() => dispatch(i + 1, delay(arr[i]))) } dispatch(0)
6. Node 与浏览器 EventLoop 的差异
《JavaScript 运行机制详解:再谈 Event Loop》
《带你彻底弄懂 Event Loop》
7. 如何解决页面加载海量数据而页面不卡顿
- 分治思想,在一定的时间内多次加载数据,直至渲染完成,使用
window.requestAnimationFrame
和document.createDocumentFragment()
实现, 可参考文章【如何解决页面加载海量数据而不冻结前端 UI】 - 局部显示,毕竟用户能看到的就一屏内容,监听用户的滚动行为,改变显示元素,可使
DOM
结构最简单化。可参考文章【大数据如何在前端流畅展示】, 不过他的Demo
有点问题.
语法和 API
1. 理解 ECMAScript 和 JavaScript 的关系
ECMAScript
是 JavaScript
的规范,JavaScript
是 ECMAScript
的实现。
2. 熟练运用 es5、es6 提供的语法规范
【JavaScript 标准参考教程(alpha)】
【ECMAScript 6 入门】
3. setInterval 需要注意的点,使用 settimeout 实现 setInterval
- setInterval 需要注意的点:
在使用 setInterval
方法时,每一次启动都需要对 setInterval
方法返回的值做一个判断,判断是否是空值,若不是空值,则要停止定时器并将值设为空,再重新启动,如果不进行判断并赋值,有可能会造成计时器循环调用,在同等的时间内同时执行调用的代码,并会随着代码的运行时间增加而增加,导致功能无法实现,甚至占用过多资源而卡死奔溃。因此在每一次使用 setInterval 方法时,都需要进行一次判断。
let timer = setInterval(func, 1000)
// 在其他地方再次用到 setInterval(func, 1000)
if (timer !== null) {clearInterval(timer)
timer = null
}
timer = setInterval(func, 1000)
-
使用 settimeout 实现 setInterval
setIntervalFunc = () =>{console.log(1) // 使用递归 setTimeout(setIntervalFunc, 1000); }; setInterval()
4. JavaScript 提供的正则表达式 API、可以使用正则表达式(邮箱校验、URL 解析、去重等)解决常见问题
邮箱校验:
function isEmail(emailStr) {return /^[a-zA-Z0-9]+([._-]*[a-zA-Z0-9]*)*@[a-zA-Z0-9]+.[a-zA-Z0-9{2,5}$]/.test(emailStr);
}
URL 解析:
function isUrl(urlStr) {return /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\*\+,;=.%]+$/.test(value)
}
数组去重:
// set 结构
let arr = [1, 1, 2, 2, 3, 3]
arr2 = [...new Set(arr)]
console.log(arr2) // [1,2,3]
// Object.keys(), 利用属性 key 的唯一性
let arrObj = [1, 1, 2, 2, 3, 3]
arrObj2 = {}
for (i in arrObj) {arrObj2[arrObj[i]] = true
}
let arrObj3 = Object.keys(arrObj2)
console.log(arrObj3)
// 利用 indexOf() 查询数组内是否已经包含该元素
var arrIndexOf = ['a','c','b','d','a','b']
var arrIndexOf2 = [];
for(var i = 0;i<arrIndexOf.length;i++){if(arrIndexOf2.indexOf(arrIndexOf[i])<0){arrIndexOf2.push(arrIndexOf[i]);
}
}
console.log(arrIndexOf2)// ['a', 'c', 'b', 'd']
二、HTML 和 CSS
HTML
1. 从规范的角度理解 HTML,从分类和语义的角度使用标签
语义化标签:<header> <footer> <nav> <section> <article> <aside> 等
- 让页面呈现清晰的结构
- 屏幕阅读器(如果访客有视障)会完全根据你的标记来“读”你的网页
- 搜索引擎的爬虫依赖标签确定上下文和权重问题
- 便于团队开发和维护
标签分类:
- 文档标签(10 个):`<html>、<head>、<body>、<title>、<meta>、<base>
、<style>、<link>、<script>、<noscript>`
- 表格标签(10 个):`<table>、<thead>、<tbody>、<tfoot>、<tr>、<td>、<th>
、<col>、<colgroup>、<caption>`
- 表单标签(10 个):`<from>、<input>、<textarea>、<button>、<select>
、<optgroup>、<option>、<label>、<fieldset>、<legend>`
- 列表标签(6 个):
<ul>、<ol>、<li>、<dl>、<dt>、<dd>
- 多媒体标签(5 个):
<img>、<map>、<area>、<object>、<param>
- 文章标签:
<h1> - <h6>、<p>、<br>、<span>、<bdo>、<pre>、<acronym>、<abbr>、<blockquote>、<q>、<ins>、<del>、<address>
- 字体样式标签:
<tt>、<i>、<b>、<big>、<small>、<em>、<strong>、<dfn>、<code>、<samp>、<kbd>、<var>、<cite>、<sup>、<sub>
在不同的场景使用不同的标签,更能显示清晰的结构。
2. 元信息类标签 (head、title、meta) 的使用目的和配置方法
<head>
标签用于定义文档的头部,它是所有头部元素的容器。<head>
中的元素可以引用脚本、指示浏览器在哪里找到样式表、提供元信息等等。可以包含的标签有:<base>, <link>, <meta>, <script>, <style>, 以及 <title>。
<title>
定义文档的标题,它是 head 部分中唯一必需的元素。
<meta>
元素可提供有关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描述和关键词。使用方法参考【meta 标签详解】
3. HTML5 离线缓存原理
- [] 待补充
4. 可以使用 Canvas API、SVG 等绘制高性能的动画
- 【Canvas 进阶(一)二维码的生成与扫码识别】
- 【Canvas 进阶(二)写一个生成带 logo 的二维码 npm 插件】
- 【Canvas 进阶(三)ts + canvas 重写”辨色“小游戏】
- 【流动的 SVG 线条】
CSS
1. CSS 盒模型,在不同浏览器的差异
- 标准
w3c
盒子模型的范围包括margin、border、padding、content
,并且content
部分不包含其他部分 -
ie
盒子模型的范围也包括margin、border、padding、content
,和标准w3c
盒子模型不同的是:ie
盒子模型的content
部分包含了border
和pading
。
</details>
<details>
<summary>2. CSS 所有选择器及其优先级、使用场景,哪些可以继承,如何运用 at 规则 </summary>
不同级别优先级:!important > 行内样式 > ID 选择器 > 类选择器 > 元素 > 通配符 > 继承 > 浏览器默认属性
相同级别优先级 : 内联 (行内) 样式 > 内部样式表 > 外部样式表 > 导入样式(@import)。
可继承属性:
字体系列属性, font-family, font-weight, font-size, font-style...
文本系列属性, text-indent, text-align, line-heigh, word-spacing, letter-spacing, text-transform, color
元素可见性:visibility, 光标属性:cursor
AT rule:
一、什么是 at-rules
eg:@charset "utf-8";
at-rule
是 CSS
样式声明,以 @
开头,紧跟着是标识符(charset)
,最后以分号(;)结尾。
二、几个 at-rules
1、@charset
—定义被样式表使用的字符集
2、@import
——告诉 CSS
引擎包含外部的 CSS
样式表
3、@namespace
——告诉 CSS
引擎所有的内容都必须考虑使用 XML
命名空间前缀
4、嵌套at-rules
(1)@media
——条件组规则。如果设备符合标准定义的条件查询则使用该媒体
(2)@font-face
——描述了一个将从外部下载的字体
(3)@keyframes
——描述了中间步骤在 CSS
动画的序列
(4)@page
——描述了文件的布局变化,当要打印文档时。
(5)@supports
——条件组规则,如果浏览器满足给出的规则,则把它应用到内容中
(6)@document
——条件组规则,如果被用到文档的 CSS
样式表满足了给定的标准,那么将被应用到所有的内容中。
</details>
<details>
<summary>3. CSS 伪类和伪元素有哪些,它们的区别和实际应用 </summary>
伪类:用于向某些选择器添加特殊的效果. :active, :focus, :link, :visited, :hover, :first-child
伪元素:用于将特殊的效果添加到某些选择器. :before, :after, :first-line, :first-letter
伪类和伪元素的根本区别在于:它们是否创造了新的元素(抽象)。从我们模仿其意义的角度来看,如果需要添加新元素加以标识的,就是伪元素,反之,如果只需要在既有元素上添加类别的,就是伪类。
</details>
<details>
<summary>4. HTML 文档流的排版规则,CSS 几种定位的规则、定位参照物、对文档流的影响,如何选择最好的定位方式,雪碧图实现原理 </summary>
HTML 文档流的排版规则: 把元素按从上而下,从左到右的顺序默认排列。不在一行的元素从上而下,在一行的从左到右排列。
CSS 几种定位的规则:
-
static
定位(普通流定位) -
float
定位 (浮动定位), 有两个取值:left
(左浮动) 和right
(右浮动)。
浮动元素会在没有浮动元素的上方,效果上看是遮挡住了没有浮动的元素,有 float 样式规则的元素是脱离文档流的,它的父元素的高度并不能有它撑开。
-
relative
定位(相对定位), 相对本元素的左上角进行定位,top,left,bottom,right
都可以有值。虽然经过定位后,位置可能会移动,但是本元素并没有脱离文档流,还占有原来的页面空间。 -
absolute
定位 (绝对定位), 相对于祖代中有relative
(相对定位) 并且离本元素层级关系上是最近的元素的左上角进行定位,如果在祖代元素中没有有relative
定位的,就默认相对于body
进行定位。绝对定位是脱离文档流的 -
fixed
定位(固定定位),这种定位方式是相对于整个文档的,只需设置它相对于各个方向的偏移值,就可以将该元素固定在页面固定的位置,通常用来显示一些提示信息,脱离文档流;
雪碧图实现原理:CSS Sprite
,是一种 CSS
图像合并技术,该方法是将小图标和背景图像合并到一张图片上,然后利用 css
的背景定位来显示需要显示的图片部分。
</details>
<details>
<summary>5. 水平垂直居中的方案、可以实现 6 种以上并对比它们的优缺点 </summary>
参考文章:【CSS 实现水平垂直居中的 1010 种方式】
</details>
<details>
<summary>6. BFC 实现原理,可以解决的问题,如何创建 BFC</summary>
BFC(Block formatting context)
直译为 ” 块级格式化上下文 ”。它是一个独立的渲染区域,只有块级元素参与,它规定了内部的块级元素如何布局,并且与这个区域外部毫不相干。
BCF 可以解决的问题:浮动定位,消除外边距折叠,清除浮动,自适应多栏布局
BFC 的创建:根元素或包含根元素的元素,浮动元素(float
不为none
),绝对定位元素(position
为 absolute
或者 fixed
),display
为 inline-block,table-cell,table-caption,overflow
值不为 visible
,弹性元素(flex
布局),网格元素(grid
布局)
</details>
<details>
<summary>7. CSS 模块化方案、如何配置按需加载、如何防止 CSS 阻塞渲染 </summary>
CSS 模块化方案: 文件细化,命名约定,CSS Modules,css in js
如何防止 CSS 阻塞渲染:
CSS
是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。
有一些 CSS
样式只在特定条件下(例如显示网页或将网页投影到大型显示器上时)使用,我们可以通过 CSS
“媒体类型”和“媒体查询”来解决这类用例:
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 40em)">
首屏相关的关键 `CSS` 使用阻塞渲染的方式加载,所有的非关键 `CSS` 在首屏渲染完成后加载。
</details>
<details>
<summary>8. 手写图片瀑布流效果 </summary>
参考文章:【瀑布流布局的实现】
</details>
<details>
<summary>9. 使用 CSS 绘制几何图形(圆形、三角形、扇形、菱形等)</summary>
// 圆形
.circle{
width:100px;
height:100px;
border-radius:50%;
background:blue;
}
// 三角形
.triangle {
width: 0;
height: 0;
border: 50px solid blue;
/* 通过改变边框颜色,可以改变三角形的方向 */
border-color: blue transparent transparent transparent;
}
// 扇形,扇形是由一个圆形和一个矩形进行组合得到的,用矩形遮住圆形的一部分就形成了扇形。.sector {
width: 142px;
height: 142px;
background: #fff;
border-radius: 50%;
background-image: linear-gradient(to right, transparent 50%, #655 0);
}
.sector::before {
content: '';
display: block;
margin-left: 50%;
height: 100%;
width: 100%;
background-color: inherit;
transform-origin: left;
/* 调整角度,改变扇形大小 */
transform: rotate(230deg);
}
// 菱形
.rhombus {
width: 200px;
height: 200px;
transform: rotateZ(45deg) skew(30deg, 30deg);
background: blue;
}
10. 使用纯 CSS 实现曲线运动(贝塞尔曲线)
CSS3 新增了 transition-timing-function 属性,它的取值就可以设置为一个三次贝塞尔曲线方程。
参考文章:【贝塞尔曲线的 css 实现——淘宝加入购物车基础动画】
11. 实现常用布局(三栏、圣杯、双飞翼、吸顶),说出多种方式并理解其优缺点
圣杯布局, 两边顶宽,中间自适应的三栏布局。
- [] 期待评论补充
三、计算机基础
关于编译原理,不需要理解非常深入,但是最基本的原理和概念一定要懂,这对于学习一门编程语言非常重要
编译原理
1. 理解代码到底是什么,计算机如何将代码转换为可以运行的目标程序
代码就是程序员用开发工具所支持的语言写出来的源文件,是一组由字符、符号或信号码元以离散形式表示信息的明确的规则体系。
计算机源代码最终目的是将人类可读文本翻译成为计算机可执行的二进制指令,这种过程叫编译,它由通过编译器完成。
2. 正则表达式的匹配原理和性能优化
- [] 待补充
3. 如何将 JavaScript 代码解析成抽象语法树(AST)
- [] 待补充
4. base64 的编码原理
- [] 待补充
5. 几种进制的相互转换计算方法,在 JavaScript 中如何表示和转换
parseInt(str, radix)
将一个 radix 进制的 str 转化为十进制,parseInt('23',8) // 19
,将八进制的‘23’转化为 10 进制的‘19’
number.toString(radix)
将一个数字转化为 radix 进制的数字字符串
0x11.toString(8) // 21
0x11.toString(10) // 17
0x11.toString(2) // 10001
网络协议
1. 理解什么是协议,了解 TCP/IP 网络协议族的构成,每层协议在应用程序中发挥的作用
协议,网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组约定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流。它的三要素是:语法、语义、时序。
TCP/IP 网络协议族的构成: TCP/IP
协议是 Internet
最基本的协议。由传输层的 TCP
协议和网络层的 IP
协议组成。
TCP
负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而 IP
是给因特网的每一台联网设备规定一个地址。
应用层
应用层决定了向用户提供应该服务时通信的活动。
TCP/IP
协议族内预存了各类通用的应用服务。比如,FTP
(File Transfer Protocol,文件传输协议)和 DNS
(Domain Name System,域名系统)服务就是其中的两类。HTTP
协议也处于该层。
传输层
传输层对上层应用层,提供处于网络连接中两台计算机之间的数据传输。
在传输层有两个性质不同的协议:TCP
(Transmission Control Protocol,传输控制协议)和 UDP
(User Data Protocol,用户数据报协议)。
网络层(又名网络互连层)
网络层用来处理在网络上流动的数据包。数据包是网络传输的最小数据单位。该层规定了通过怎样的路径(所谓的传输路线)到达对方计算机,并把数据包传送给对方。
与对方计算机之间通过多台计算机或网络设备进行传输时,网络层所起的所用就是在众多的选项内选择一条传输路线。
链路层(又名数据链路层,网络接口层)
用来处理连接网络的硬件部分。包括控制操作系统、硬件的设备驱动、NIC(Network Interface Card,网络适配器,即网卡),及光纤等物理可见部分(还包括连接器等一切传输媒介)。硬件上的范畴均在链路层的作用范围之内。
2. 三次握手和四次挥手详细原理,为什么要使用这种机制
三次握手和四次挥手详细原理:
三次握手:避免连接请求的数据包丢失,数据传输过程因为网络并发量很大在某结点被阻塞
四次挥手:TCP 连接是全双工通道,需要双向关闭。
参考文章:【TCP/IP 协议族】
3. 有哪些协议是可靠,TCP 有哪些手段保证可靠交付
TCP
的协议:FTP
(文件传输协议)、Telnet
(远程登录协议)、SMTP
(简单邮件传输协议)、POP3
(和 SMTP
相对,用于接收邮件)、HTTP
协议等。
TCP
提供可靠的、面向连接的数据传输服务。使用 TCP
通信之前,需要进行“三次握手”建立连接,通信结束后还要使用“四次挥手”断开连接。
4. DNS 的作用、DNS 解析的详细过程,DNS 优化原理
DNS 的作用:DNS 是互联网的一项服务。它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。
DNS 解析过程:
1、在浏览器中输入 www.qq.com
域名,操作系统会先检查自己本地的 hosts
文件是否有这个网址映射关系,如果有,就先调用这个 IP
地址映射,完成域名解析。
2、如果 hosts
里没有这个域名的映射,则查找本地 DNS 解析器缓存
,是否有这个网址映射关系,如果有,直接返回,完成域名解析。
3、如果 hosts
与 本地 DNS 解析器缓存
都没有相应的网址映射关系,首先会找 TCP/ip
参数中设置的 首选 DNS 服务器
,在此我们叫它 本地 DNS 服务器
,此服务器收到查询时,如果要查询的域名,包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析,此解析具有权威性。
4、如果要查询的域名,不由 本地 DNS 服务器区域解析
,但该服务器已缓存了此网址映射关系,则调用这个 IP
地址映射,完成域名解析,此解析不具有权威性。
5、如果 本地 DNS 服务器
本地区域文件与缓存解析都失效,则根据 本地 DNS 服务器
的设置(是否设置转发器)进行查询,如果未用转发模式,本地 DNS
就把请求发至 13
台根 DNS
,根 DNS
服务器收到请求后会判断这个域名 (.com)
是谁来授权管理,并会返回一个负责该顶级域名服务器的一个IP
。 本地 DNS 服务器
收到 IP
信息后,将会联系负责 .com
域的这台服务器。这台负责 .com
域的服务器收到请求后,如果自己无法解析,它就会找一个管理 .com
域的下一级 DNS
服务器地址(http://qq.com)
给 本地 DNS 服务器
。当 本地 DNS 服务器
收到这个地址后,就会找 http://qq.com
域服务器,重复上面的动作,进行查询,直至找到 www.qq .com
主机。
6、如果用的是转发模式,此 DNS
服务器就会把请求转发至上一级 DNS
服务器,由上一级服务器进行解析,上一级服务器如果不能解析,或找根 DNS
或把转请求转至上上级,以此循环。不管是 本地 DNS 服务器
用是是转发,还是根提示,最后都是把结果返回给 本地 DNS 服务器
,由此 DNS
服务器再返回给客户机。
DNS 优化:减少 DNS 的请求次数;进行 DNS 预获取。
减少 DNS 的请求次数————在项目中减少不同域名的 http 请求,尽量少的域名减少 DNS 的请求数
DNS 预获取————减少用户的等待时间,提升用户体验。
默认情况下浏览器会对页面中和当前域名(正在浏览网页的域名)不在同一个域的域名进行预获取,并且缓存结果,这就是隐式的 DNS Prefetch。如果想对页面中没有出现的域进行预获取,那么就要使用显示的 DNS Prefetch 了。
<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" href="//www.itechzero.com">
<link rel="dns-prefetch" href="//api.share.baidu.com">
<link rel="dns-prefetch" href="//bdimg.share.baidu.com">
5. CDN 的作用和原理
- CDN 的作用
CDN
的全称是 Content Delivery Network
,即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。
- CDN 的原理
1、多域名加载资源
一般情况下,浏览器都会对单个域名下的并发请求数(文件加载)进行限制,通常最多有 4
个,那么第 5
个加载项将会被阻塞,直到前面的某一个文件加载完毕。因为 CDN
文件是存放在不同区域(不同 IP
)的,所以对浏览器来说是可以同时加载页面所需的所有文件(远不止 4
个),从而提高页面加载速度。
2、文件可能已经被加载过并保存有缓存
一些通用的 js
库或者是 css
样式库,如 jQuery
,在网络中的使用是非常普遍的。当一个用户在浏览你的某一个网页的时候,很有可能他已经通过你网站使用的 CDN
访问过了其他的某一个网站,恰巧这个网站同样也使用了 jQuery
,那么此时用户浏览器已经缓存有该 jQuery
文件(同 IP
的同名文件如果有缓存,浏览器会直接使用缓存文件,不会再进行加载),所以就不会再加载一次了,从而间接的提高了网站的访问速度。
3、分布式的数据中心
假如你的站点布置在北京,当一个香港或者更远的用户访问你的站点的时候,他的数据请求势必会很慢很慢。而 CDN
则会让用户从离他最近的节点去加载所需的文件,所以加载速度提升就是理所当然的了。
6. HTTP 请求报文和响应报文的具体组成,能理解常见请求头的含义,有几种请求方式,区别是什么
参考文章:【HTTP 请求详解】
HTTP 协议的六种请求方法:
- GET: 发送请求来获得服务器上的资源,请求体中不会包含请求数据,请求数据放在协议头中
- POST: 和
get
一样很常见,向服务器提交资源让服务器处理,比如提交表单、上传文件等,可能导致建立新的资源或者对原有资源的修改。提交的资源放在请求体中。不支持快取。非幂等 - HEAD: 本质和
get
一样,但是响应中没有呈现数据,而是http
的头信息,主要用来检查资源或超链接的有效性或是否可以可达、检查网页是否被串改或更新,获取头信息等,特别适用在有限的速度和带宽下。 - PUT: 和
post
类似,html
表单不支持,发送资源与服务器,并存储在服务器指定位置,要求客户端事先知道该位置;比如post
是在一个集合上(/province)
,而put
是具体某一个资源上(/province/123)
。所以put
是安全的,无论请求多少次,都是在123
上更改,而post
可能请求几次创建了几次资源。幂等 - DELETE: 请求服务器删除某资源。和
put
都具有破坏性,可能被防火墙拦截 - CONNECT:
HTTP/1.1
协议中预留给能够将连接改为管道方式的代理服务器。就是把服务器作为跳板,去访问其他网页然后把数据返回回来,连接成功后,就可以正常的get
、post
了。
7: OPTIONS: 获取 http
服务器支持的 http
请求方法,允许客户端查看服务器的性能,比如 ajax
跨域时的预检等。
8: TRACE: 回显服务器收到的请求,主要用于测试或诊断。一般禁用,防止被恶意攻击或盗取信息。
7. HTTP 所有状态码的具体含义,看到异常状态码能快速定位问题
-
1XX:信息状态码
- 100 Continue 继续,一般在发送 post 请求时,已发送了 http header 之后服务端将返回此信息,表示确认,之后发送具体参数信息
-
2XX:成功状态码
- 200 OK 正常返回信息
- 201 Created 请求成功并且服务器创建了新的资源
- 202 Accepted 服务器已接受请求,但尚未处理
-
3XX:重定向
- 301 Moved Permanently 请求的网页已永久移动到新位置。
- 302 Found 临时性重定向。
- 303 See Other 临时性重定向,且总是使用 GET 请求新的 URI。
- 304 Not Modified 自从上次请求后,请求的网页未修改过。
-
4XX:客户端错误
- 400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。
- 401 Unauthorized 请求未授权。
- 403 Forbidden 禁止访问。
- 404 Not Found 找不到如何与 URI 相匹配的资源。
-
5XX: 服务器错误
- 500 Internal Server Error 最常见的服务器端错误。
- 503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)。
8. HTTP1.1、HTTP2.0 带来的改变
缓存处理,在 HTTP1.0
中主要使用 header
里的 If-Modified-Since,Expires
来做为缓存判断的标准,HTTP1.1
则引入了更多的缓存控制策略例如 Entity tag,If-Unmodified-Since, If-Match, If-None-Match
等更多可供选择的缓存头来控制缓存策略。
带宽优化及网络连接的使用,HTTP1.0
中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1
则在请求头引入了 range
头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content)
,这样就方便了开发者自由的选择以便于充分利用带宽和连接。
错误通知的管理 ,在 HTTP1.1
中新增了 24
个错误状态响应码,如 409(Conflict)
表示请求的资源与资源的当前状态发生冲突;410(Gone)
表示服务器上的某个资源被永久性的删除。
Host 头处理,在 HTTP1.0
中认为每台服务器都绑定一个唯一的 IP
地址,因此,请求消息中的 URL
并没有传递主机名 (hostname)
。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers)
,并且它们共享一个 IP
地址。HTTP1.1
的请求消息和响应消息都应支持 Host
头域,且请求消息中如果没有 Host
头域会报告一个错误(400 Bad Request)
。
长连接 ,HTTP 1.1
支持长连接(PersistentConnection)
和请求的流水线 (Pipelining)
处理,在一个 TCP
连接上可以传送多个 HTTP
请求和响应,减少了建立和关闭连接的消耗和延迟,在 HTTP1.1
中默认开启 Connection:keep-alive
,一定程度上弥补了 HTTP1.0
每次请求都要创建连接的缺点。
HTTP2.0 和 HTTP1.X 相比的新特性
- 新的二进制格式(Binary Format),
HTTP1.x
的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0
和1
的组合。基于这种考虑HTTP2.0
的协议解析决定采用二进制格式,实现方便且健壮。 - 多路复用
(MultiPlexing)
,即连接共享,即每一个request
都是是用作连接共享机制的。一个request
对应一个id
,这样一个连接上可以有多个request
,每个连接的request
可以随机的混杂在一起,接收方可以根据request
的id
将request
再归属到各自不同的服务端请求里面。 -
header
压缩,如上文中所言,对前面提到过HTTP1.x
的header
带有大量信息,而且每次都要重复发送,HTTP2.0
使用encoder
来减少需要传输的header
大小,通讯双方各自cache
一份header fields
表,既避免了重复header
的传输,又减小了需要传输的大小。 - 服务端推送(server push),
HTTP2.0
具有server push
功能。
9. HTTPS 的加密原理,如何开启 HTTPS,如何劫持 HTTPS 请求
参考文章:【一个故事讲完 https】
设计模式
1. 熟练使用前端常用的设计模式编写代码,如单例模式、装饰器模式、代理模式等
参考文章:【设计模式】
2. 发布订阅模式和观察者模式的异同以及实际应用
观察者模式和发布订阅模式最大的区别就是发布订阅模式有个事件调度中心。
// 观察者模式
class Subject{constructor(){this.subs = [];
}
addSub(sub){this.subs.push(sub);
}
notify(){
this.subs.forEach(sub=> {sub.update();
});
}
}
class Observer{update(){console.log('update');
}
}
let subject = new Subject();
let ob = new Observer();
// 目标添加观察者了
subject.addSub(ob);
// 目标发布消息调用观察者的更新方法了
subject.notify(); //update
// 发布订阅者模式
class PubSub {constructor() {this.subscribers = {}
}
subscribe(type, fn) {if (!Object.prototype.hasOwnProperty.call(this.subscribers, type)) {this.subscribers[type] = [];}
this.subscribers[type].push(fn);
}
unsubscribe(type, fn) {let listeners = this.subscribers[type];
if (!listeners || !listeners.length) return;
this.subscribers[type] = listeners.filter(v => v !== fn);
}
publish(type, ...args) {let listeners = this.subscribers[type];
if (!listeners || !listeners.length) return;
listeners.forEach(fn => fn(...args));
}
}
let ob = new PubSub();
ob.subscribe('add', (val) => console.log(val));
ob.publish('add', 1);
四、数据结构和算法
据我了解的大部分前端对这部分知识有些欠缺,甚至抵触,但是,如果突破更高的天花板,这部分知识是必不可少的,而且我亲身经历——非常有用!
JavaScript 编码能力
1. 多种方式实现数组去重、扁平化、对比优缺点
参考文章:【JS 数组去重方法整理】
【5 种方式实现数组扁平化】
2. 多种方式实现深拷贝、对比优缺点
参考文章:【递归实现深拷贝】
3. 手写函数柯里化工具函数、并理解其应用场景和优势
- [] 待补充
4. 手写防抖和节流工具函数、并理解其内部原理和应用场景
参考文章:【函数的防抖与节流】
5. 实现一个 sleep 函数
function sleep(time) {return new Promise((resolve,reject) => setTimeout(resolve, time))
}
sleep(3000).then(() => {console.log('沉睡 3000ms')})
手动实现前端轮子
1. 手动实现 call、apply、bind
call
- 判断当前
this
是否为函数,防止Function.prototype.myCall()
直接调用 -
context
为可选参数,如果不传的话默认上下文为window
- 为
context
创建一个Symbol
(保证不会重名)属性,将当前函数赋值给这个属性 - 处理参数,传入第一个参数后的其余参数
- 调用函数后即删除该
Symbol
属性
Function.prototype.myCall = function(context = window, ...args) {if (this === Function.prototype) {return undefined; // 用于防止 Function.prototype.myCall() 直接调用
}
context = context || window;
const fn = Symbol();
context[fn] = this;
const result = context[fn](...args);
delete context[fn];
return result;
};
apply
apply
实现类似 call
,参数为数组
Function.prototype.myApply = function(context = window, args) {if (this === Function.prototype) {return undefined; // 用于防止 Function.prototype.myCall() 直接调用
}
const fn = Symbol();
context[fn] = this;
let result;
if (Array.isArray(args)) {result = context[fn](...args);
} else {result = context[fn]();}
delete context[fn];
return result;
};
bind
因为 bind()
返回一个方法需手动执行,因此利用闭包实现。
Function.prototype.myBind = function(context, ...args1) {if (this === Function.prototype) {throw new TypeError('Error');
}
const _this = this;
return function F(...args2) {
// 判断是否用于构造函数
if (this instanceof F) {return new _this(...args1, ...args2);
}
return _this.apply(context, args1.concat(args2));
};
};
2. 手动实现符合 Promise/A+ 规范的 Promise
参考文章:【手动实现 promise】
3. 手写一个 EventEmitter 实现事件发布、订阅
function EventEmitter() {this._events = Object.create(null);
}
// 向事件队列添加事件
// prepend 为 true 表示向事件队列头部添加事件
EventEmitter.prototype.addListener = function (type, listener, prepend) {if (!this._events) {this._events = Object.create(null);
}
if (this._events[type]) {if (prepend) {this._events[type].unshift(listener);
} else {this._events[type].push(listener);
}
} else {this._events[type] = [listener];
}
};
// 移除某个事件
EventEmitter.prototype.removeListener = function (type, listener) {if (Array.isArray(this._events[type])) {if (!listener) {delete this._events[type]
} else {this._events[type] = this._events[type].filter(e => e !== listener && e.origin !== listener)
}
}
};
// 向事件队列添加事件,只执行一次
EventEmitter.prototype.once = function (type, listener) {const only = (...args) => {listener.apply(this, args);
this.removeListener(type, listener);
}
only.origin = listener;
this.addListener(type, only);
};
// 执行某类事件
EventEmitter.prototype.emit = function (type, ...args) {if (Array.isArray(this._events[type])) {this._events[type].forEach(fn => {fn.apply(this, args);
});
}
};
// 测试一下
var emitter = new EventEmitter();
var onceListener = function (args) {console.log('我只能被执行一次', args, this);
}
var listener = function (args) {console.log('我是一个 listener', args, this);
}
emitter.once('click', onceListener);
emitter.addListener('click', listener);
emitter.emit('click', '参数');
emitter.emit('click');
emitter.removeListener('click', listener);
emitter.emit('click');
4. 可以说出两种实现双向绑定的方案、可以手动实现
参考文章:【Vue 双向数据绑定原理】
5. 手写 JSON.stringify、JSON.parse
let Myjson = {parse: function(jsonStr) {return eval('(' + jsonStr + ')');
},
stringify: function(jsonObj) {
var result = '',
curVal;
if (jsonObj === null) {return String(jsonObj);
}
switch (typeof jsonObj) {
case 'number':
case 'boolean':
return String(jsonObj);
case 'string':
return '"'+ jsonObj +'"';
case 'undefined':
case 'function':
return undefined;
}
switch (Object.prototype.toString.call(jsonObj)) {case '[object Array]':
result += '[';
for (var i = 0, len = jsonObj.length; i < len; i++) {curVal = JSON.stringify(jsonObj[i]);
result += (curVal === undefined ? null : curVal) + ",";
}
if (result !== '[') {result = result.slice(0, -1);
}
result += ']';
return result;
case '[object Date]':
return '"'+ (jsonObj.toJSON ? jsonObj.toJSON() : jsonObj.toString()) +'"';
case '[object RegExp]':
return "{}";
case '[object Object]':
result += '{';
for (i in jsonObj) {if (jsonObj.hasOwnProperty(i)) {curVal = JSON.stringify(jsonObj[i]);
if (curVal !== undefined) {result += '"'+ i +'":'+ curVal +',';}
}
}
if (result !== '{') {result = result.slice(0, -1);
}
result += '}';
return result;
case '[object String]':
return '"'+ jsonObj.toString() +'"';
case '[object Number]':
case '[object Boolean]':
return jsonObj.toString();}
}
};
6. 手写懒加载效果
参考文章:【图片懒加载】
浏览器原理
1. 可详细描述浏览器从输入 URL 到页面展现的详细过程
参考文章:【输入 URL 至页面渲染】
2. 浏览器的垃圾回收机制,如何避免内存泄漏
参考文章:【浏览器内存回收机制】
参考文章:【内存泄漏与避免】
资源推荐
语言基础
- 现代
JavaScript
教程:zh.javascript.info/ - 阮一峰的
ECMAScript 6
教程:es6.ruanyifeng.com/ -
HTML meta
标签总结与属性使用介绍:https://segmentfault.com/a/1190000004279791 -
CSS
编码指导:https://github.com/chadluo/CSS-Guidelines/blob/master/README.md
计算机基础
- 大前端开发者需要了解的基础编译原理和语言知识:http://fullstack.blog
- 正则表达式 30 分钟入门教程:https://deerchao.net/tutorials/regex/regex.htm
数据结构和算法
- 用动画的形式呈现解
LeetCode
题目的思路:https://github.com/MisterBooo/LeetCodeAnimation -
JavaScript
数据结构和算法:https://github.com/ConardLi/awesome-coding-js -
30-seconds-of-code
(里面有很多js
代码非常巧妙,我正在将它翻译成中文):https://github.com/ConardLi/30-seconds-of-code-Zh-CN
运行环境
- 《重学前端》中的浏览器原理章节:https://time.geekbang.org/column/article/80240
- 图解浏览器的基本工作原理:https://zhuanlan.zhihu.com/p/47407398
- 七天学会
NodeJS
:github.com/nqdeng/7-da… -
Node.js
模块加载与运行原理:efe.baidu.com/blog/nodejs…
框架和类库
-
TypeScript Handbook
:zhongsp.gitbooks.io/typescript-… -
React.js
小书:huziketang.mangojuice.top/books/react… -
React
深入系列:juejin.im/post/5cad39… -
Webpack React
小书:fakefish.github.io/react-webpa… -
Vue.js
技术揭秘:github.com/ustbhuangyi… -
Vuex
在Vue
中管理状态:sabe.io/tutorials/g… - 你需要
Mobx
还是Redux
?:juejin.im/post/5a7fd7… -
Underscore
源码分析:yoyoyohamapi.gitbooks.io/undersercor… - 微信小程序开发资源汇总:github.com/justjavac/a…
- 腾讯移动
Web
前端知识库:github.com/AlloyTeam/M…
前端工程
- 一口(很长的)气了解
babel
:zhuanlan.zhihu.com/p/43249121 -
Webpack
傻瓜式指南:zhuanlan.zhihu.com/p/20367175 -
Webpack
原理:segmentfault.com/a/119000001… - 廖雪峰的
git
教程:www.liaoxuefeng.com/wiki/001373… - 图解
Git
:marklodato.github.io/visual-git-… - 前端开发者必备的
Nginx
知识:juejin.im/post/5c85a6… - 使用
Jenkins
进行持续集成:www.liaoxuefeng.com/article/001…
项目和业务
- 常见六大
Web
安全攻防解析:github.com/ljianshu/Bl… - 新人如何快速融入技术实力强的前端团队:juejin.im/post/5cb860…
学习提升
- 印记中文(各种中文开发文档):www.docschina.org/
- 前端学习方法:github.com/helloqingfe…
- 如何在工作内外获得持续的技术成长:juejin.im/post/5cbd74…
- 优秀的前端博客汇总:github.com/foru17/fron…
博客推荐
- 冴羽的博客:github.com/mqyqingfeng…
- 张鑫旭的博客:www.zhangxinxu.com/wordpress/
- 大深海的博客:https://github.com/chenshenhai/blog
- 木易杨的博客:https://github.com/yygmind/blog
- MuYunyun 的博客:https://github.com/MuYunyun/blog
技术之外
- 互联网术语大全:www.jianshu.com/p/9a7ca206c…
- 互联网沟通、问答、学习的艺术:zhuanlan.zhihu.com/p/41431775
- 经常加班至深夜,怎样才能保持身体健康:www.zhihu.com/question/21…
此文章 Markdown 源文件地址:https://github.com/zxpsuper/blog…