『question 系列』的 js 部分,有一个很关键的部分就是手写的实现。
今天我们就把上出现的手写题目,进行梳理。下面我们开始吧。
面试手写部分
- AJAX手写
1
2
3
4
5
6
7
8
9var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var result = xhr.responseText
resolve(result)
}
}
xhr.open(method,url,isAsyn)
xhr.send() // xhr.send(body)
对于这种状态在ajax中分为5中状态:
0: (未初始化)还没有调用send()方法。
1: (载入)已经调用send()方法,正在派发请求。
2: (载入完成)send()已经执行完成,已经接收到全部的响应内容。
3: (交互)正在解析响应内容。
4: (完成)响应内容已经解析完成,用户可以调用。
AJAX状态码说明
1** :请求收到,继续处理
2** :操作成功收到,分析、接受
3** :完成此请求必须进一步处理
4** :请求包含一个错误语法或不能完成
5** :服务器执行一个完全有效请求失败
- Promise用法
1
2
3
4
5
6
7function fn() {
return Promise((resolve, reject)=>{
resolve(data) //成功时调用
reject(err) //失败时调用
})
}
fn.then(success,fail).then(success2,fail2)
Promise.all 用法–promise1和promise2都成功才会调用successPromise.all([promise1,promise2]).then(success,fail)
Promise.race 用法–promise1和promise2只要有一个成功就会调用successPromise.race([promise1,promise2]).then(success,fail)
- JavaScript常见的六种继承方式
方式一、原型链继承
Student.prototype = new Person() // 子类型的原型为父类型的一个实例对象
核心: 将父类的实例作为子类的原型
缺点:
来自原型对象的所有属性被所有实例共享
创建子类实例时,无法向父类构造函数传参
要想为子类新增属性和方法,必须要在Student.prototype = new Person() 之后执行,不能放到构造器中
方式二:
1 | function Student(name, age, price) { |
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类
缺点:
实例并不是父类的实例,只是子类的实例
只能继承父类的实例属性和方法,不能继承原型属性和方法
无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
方式三: 原型链+借用构造函数的组合继承
1 | function Student(name, age, price) { |
缺点:
调用了两次父类构造函数,生成了两份实例
方式四: 组合继承优化1
1 | function Student(name, age, price) { |
缺点:
没办法辨别是实例是子类还是父类创造的,子类和父类的构造函数指向是同一个
方式五: 组合继承优化2
1 | function Student(name, age, price) { |
方式六:ES6中class 的继承
1 | class Student extends Person { |
五和六分别是ES5和ES6的标准方法
- 数组去重
1.哈希表思想
1 | function unique(arr) { |
2.遍历数组,用indexOf() / includes()
1 | //indexOf() |
3.数组下标判断法
1 | //顺序查找 |
4.排序,去除相邻的重复值
1 | function unique(arr) { |
5.双层循环遍历
1 | function unique(arr) { |
6.ES6的Set结构(高性能)
1 | function unique(arr) { |
- 函数节流(throttle)
核心的事情是能不能,开始是能。调用完,马上不能。一段时间之后才可以1
2
3
4
5
6
7
8
9
10function throttle (fn, delay) {
let canUse = true
return function () {
if (canUse) {
fn.apply(this,arguments)
canUse = false
setTimeout(()=>canUse = true, delay)
}
}
}
- 函数防抖(debounce)
防抖和节流的区别
核心的事情是重新定时,所以开始定义timer为null。返回的函数也是先清timer。进去timer之后,先执行fn。之后赶紧清timer1
2
3
4
5
6
7
8
9
10
11
12
13//存在问题版本
function debounce(fn, delay){
let timerId = null
return function(){
const context = this
if(timerId){window.clearTimeout(timerId)}
// 问题主要是箭头函数导致this指向,和arguments传递的问题
timerId = setTimeout(()=>{
fn.apply(context, arguments)
timerId = null
},delay)
}
}
1 | function debounce(fn, delay) { |
- 手动写一个node服务器
1
2
3
4
5
6
7
8
9
10const http = require('http');
const fs = require('fs');
const server = http.createServer((req,res) => {
if (req.url == '/') {
const indexFile = fs.createReadStream('./index.html')
req.writeHead(200,{'context-Type':'text/html;charset = utf8'})
indexFile.pipe(res)
}
}
server.listen(8080)
- 如何用正则实现 trim()
1
2
3
4
5
6
7String.prototype.trim = function(){
return this.replace(/^\s+|\s+$/g, '')
}
//或者
function trim(string){
return string.replace(/^\s+|\s+$/g, '')
}
- 手写Promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
function Promise(executor) {
var _this = this
_this.state = PENDING
_this.value = undefined
_this.onFulfilledFunc=[]
_this.onRejectedFunc=[]
executor(resolve,rejected)
function resolve(value){
if(_this.state===PENDING){
_this.value = value
_this.state = RESOLVED
}
}
function reject(value){
if(_this.state===PENDING){
_this.value = value
_this.state = REJECTED
}
}
}
Promise.prototype.then = function(onFulfilled,onRejected){
let self = this;
let promise2;
if(this.state === RESOLVED){
promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
try{
let x = onFulfilled(self.value)
resolvePromise(promise,x,resolve,reject)
}catch(error){
reject(error)
}
})
})
}
if(this.state === REJECTED){
promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
try{
let x = onRejected(self.value)
resolvePromise(promise,x,resolve,reject)
}catch(error){
reject(error)
}
})
})
}
if(this.state === PENDING){
promise2 = new Promise((resolve,reject)=>{
self.onFulfilledFunc.push(()=>{
setTimeout(() => {
try{
let x = onFulfilled(self.value)
resolvePromise(promise,x,resolve,reject)
}catch(error){
reject(error)
}
})
})
self.onRejectedFunc.push(()=>{
setTimeout(()=>{
try{
let x = onRejected(self.value)
resolvePromise(promise,x,resolve,reject)
}catch(error){
reject(error)
}
})
})
})
}
return promise2
}
function resolvePromise(promise,x,solve,reject){
if(promise === x && x !== undefined){
reject(new TypeError('发生了循环引用'))
}
if(x !== null && (typeof x === 'function' || typeof x === 'object')){
try{
let then = x.then
if(typeof then ==='function'){
then.call(x, y => {
resolvePromise(promise,y,solve,reject)
}, e => {
reject(e)
}
}else {
resolve(x)
}
}catch(error){
reject(error)
}
}else {
resolve(x)
}
}
- 用js如何去除url中的#号
• 情景一: 单纯将hash路由改变成history路由即可去除hash的#号,此时需要服务器做路由重定向,比如nginx, node重定向
• 情景二: 单纯去除#1
2
3
4function dropHash(url) {
let i = url.indexOf('#')
return i > -1 ? url.replace(/#/g, '') : url
}
- 手动实现 Array.prototype.map 方法
map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function map(arr, mapCallback) {
// 首先,检查传递的参数是否正确。
if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') {
return [];
} else {
let result = [];
// 每次调用此函数时,我们都会创建一个 result 数组
// 因为我们不想改变原始数组。
for (let i = 0, len = arr.length; i < len; i++) {
result.push(mapCallback(arr[i], i, arr));
// 将 mapCallback 返回的结果 push 到 result 数组中
}
return result;
}
}
- 手动实现Array.prototype.filter方法
filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function filter(arr, filterCallback) {
// 首先,检查传递的参数是否正确。
if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function')
{
return [];
} else {
let result = [];
// 每次调用此函数时,我们都会创建一个 result 数组
// 因为我们不想改变原始数组。
for (let i = 0, len = arr.length; i < len; i++) {
// 检查 filterCallback 的返回值是否是真值
if (filterCallback(arr[i], i, arr)) {
// 如果条件为真,则将数组元素 push 到 result 中
result.push(arr[i]);
}
}
return result; // return the result array
}
}
- 手动实现Array.prototype.reduce方法
reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function reduce(arr, reduceCallback, initialValue) {
// 首先,检查传递的参数是否正确。
if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function')
{
return [];
} else {
// 如果没有将initialValue传递给该函数,我们将使用第一个数组项作为initialValue
let hasInitialValue = initialValue !== undefined;
let value = hasInitialValue ? initialValue : arr[0];
// 如果有传递 initialValue,则索引从 1 开始,否则从 0 开始
for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {
value = reduceCallback(value, arr[i], i, arr);
}
return value;
}
}
- 编写一个可以执行如下操作的函数。 可以创建一个闭包来存放传递给函数 createBase 的值。被返回的内部函数是在外部函数中创建的,内部函数就成了一个闭包,它可以访问外部函数中的变量,在本例中是变量 baseNumber。
1
2
3var addSix = createBase(6);
addSix(10); // 返回 16
addSix(21); // 返回 271
2
3
4
5
6
7
8
9
10function createBase(baseNumber) {
return function(N) {
// 我们在这里访问 baseNumber,即使它是在这个函数之外声明的。
// JavaScript 中的闭包允许我们这么做。
return baseNumber + N;
}
}
var addSix = createBase(6);
addSix(10);
addSix(21);
- 请写出下面代码的运行结果: 答案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('settimeout')
})
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')1
2
3
4
5
6
7
8// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// settimeout
- 将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组
1
Array.from(new Set(arr.flat(Infinity))).sort((a, b) => a - b)
- 判断一个给定的字符串是否是同构的。
paper 和 title 将返回 true。
egg 和 sad 将返回 false。
dgg 和 add 将返回 true。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function isIsomorphic(firstString, secondString) {
// 检查长度是否相等,如果不相等, 它们不可能是同构的
if (firstString.length !== secondString.length) return false
var letterMap = {};
for (var i = 0; i < firstString.length; i++) {
var letterA = firstString[i],
letterB = secondString[i];
// 如果 letterA 不存在, 创建一个 map,并将 letterB 赋值给它
if (letterMap[letterA] === undefined) {
letterMap[letterA] = letterB;
} else if (letterMap[letterA] !== letterB) {
// 如果 letterA 在 map 中已存在, 但不是与 letterB 对应,
// 那么这意味着 letterA 与多个字符相对应。
return false;
}
}
// 迭代完毕,如果满足条件,那么返回 true。
// 它们是同构的。
return true;
}
- 本文作者: Jambo
- 版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!