共计 21741 个字符,预计需要花费 55 分钟才能阅读完成。
原函数形参不定长(此时 fn.length
为 0)
function curry(fn) {
// 保留参数,除去第一个函数参数
let args = [].slice.call(arguments, 1);
// 返回一个新函数
let curried = function () {
// 新函数调用时会持续传参
let allArgs = [...args, ...arguments];
return curry(fn, ...allArgs);
};
// 利用 toString 隐式转换的个性,当最初执行函数时,会隐式转换
curried.toString = function () {return fn(...args);
};
return curried;
}
// 测试
function add(...args) {return args.reduce((pre, cur) => pre + cur, 0);
}
console.log(add(1, 2, 3, 4));
let addCurry = curry(add);
console.log(addCurry(1)(2)(3) == 6); // true
console.log(addCurry(1, 2, 3)(4) == 10); // true
console.log(addCurry(2, 6)(1).toString()); // 9
console.log(addCurry(2, 6)(1, 8)); // 打印 curried 函数
手写题:数组扁平化
function flatten(arr) {let result = [];
for (let i = 0; i < arr.length; i++) {if (Array.isArray(arr[i])) {result = result.concat(flatten(arr[i]));
} else {result = result.concat(arr[i]);
}
}
return result;
}
const a = [1, [2, [3, 4]]];
console.log(flatten(a));
基于 Localstorage 设计一个 1M 的缓存零碎,须要实现缓存淘汰机制
设计思路如下:
- 存储的每个对象须要增加两个属性:别离是过期工夫和存储工夫。
- 利用一个属性保留零碎中目前所占空间大小,每次存储都减少该属性。当该属性值大于 1M 时,须要依照工夫排序零碎中的数据,删除一定量的数据保障可能存储下目前须要存储的数据。
- 每次取数据时,须要判断该缓存数据是否过期,如果过期就删除。
以下是代码实现,实现了思路,然而可能会存在 Bug,然而这种设计题个别是给出设计思路和局部代码,不会须要写出一个无问题的代码
class Store {constructor() {let store = localStorage.getItem('cache')
if (!store) {
store = {
maxSize: 1024 * 1024,
size: 0
}
this.store = store
} else {this.store = JSON.parse(store)
}
}
set(key, value, expire) {this.store[key] = {date: Date.now(),
expire,
value
}
let size = this.sizeOf(JSON.stringify(this.store[key]))
if (this.store.maxSize < size + this.store.size) {console.log('超了 -----------');
var keys = Object.keys(this.store);
// 工夫排序
keys = keys.sort((a, b) => {let item1 = this.store[a], item2 = this.store[b];
return item2.date - item1.date;
});
while (size + this.store.size > this.store.maxSize) {let index = keys[keys.length - 1]
this.store.size -= this.sizeOf(JSON.stringify(this.store[index]))
delete this.store[index]
}
}
this.store.size += size
localStorage.setItem('cache', JSON.stringify(this.store))
}
get(key) {let d = this.store[key]
if (!d) {console.log('找不到该属性');
return
}
if (d.expire > Date.now) {console.log('过期删除');
delete this.store[key]
localStorage.setItem('cache', JSON.stringify(this.store))
} else {return d.value}
}
sizeOf(str, charset) {
var total = 0,
charCode,
i,
len;
charset = charset ? charset.toLowerCase() : '';
if (charset === 'utf-16' || charset === 'utf16') {for (i = 0, len = str.length; i < len; i++) {charCode = str.charCodeAt(i);
if (charCode <= 0xffff) {total += 2;} else {total += 4;}
}
} else {for (i = 0, len = str.length; i < len; i++) {charCode = str.charCodeAt(i);
if (charCode <= 0x007f) {total += 1;} else if (charCode <= 0x07ff) {total += 2;} else if (charCode <= 0xffff) {total += 3;} else {total += 4;}
}
}
return total;
}
}
ES6 中模板语法与字符串解决
ES6 提出了“模板语法”的概念。在 ES6 以前,拼接字符串是很麻烦的事件:
var name = 'css'
var career = 'coder'
var hobby = ['coding', 'writing']
var finalString = 'my name is' + name + ', I work as a' + career + ', I love' + hobby[0] + 'and' + hobby[1]
仅仅几个变量,写了这么多加号,还要时刻小心外面的空格和标点符号有没有跟错中央。然而有了模板字符串,拼接难度直线降落:
var name = 'css'
var career = 'coder'
var hobby = ['coding', 'writing']
var finalString = `my name is ${name}, I work as a ${career} I love ${hobby[0]} and ${hobby[1]}`
字符串不仅更容易拼了,也更易读了,代码整体的品质都变高了。这就是模板字符串的第一个劣势——容许用 ${}的形式嵌入变量。但这还不是问题的要害,模板字符串的要害劣势有两个:
- 在模板字符串中,空格、缩进、换行都会被保留
- 模板字符串齐全反对“运算”式的表达式,能够在 ${}里实现一些计算
基于第一点,能够在模板字符串里无障碍地间接写 html 代码:
let list = ` <ul> <li> 列表项 1 </li> <li> 列表项 2 </li> </ul>`;
console.log(message); // 正确输入,不存在报错
基于第二点,能够把一些简略的计算和调用丢进 ${} 来做:
function add(a, b) {const finalString = `${a} + ${b} = ${a+b}`
console.log(finalString)
}
add(1, 2) // 输入 '1 + 2 = 3'
除了模板语法外,ES6 中还新增了一系列的字符串办法用于晋升开发效率:
(1)存在性断定:在过来,当判断一个字符 / 字符串是否在某字符串中时,只能用 indexOf > -1 来做。当初 ES6 提供了三个办法:includes、startsWith、endsWith,它们都会返回一个布尔值来通知你是否存在。
- includes:判断字符串与子串的蕴含关系:
const son = 'haha'
const father = 'xixi haha hehe'
father.includes(son) // true
- startsWith:判断字符串是否以某个 / 某串字符结尾:
const father = 'xixi haha hehe'
father.startsWith('haha') // false
father.startsWith('xixi') // true
- endsWith:判断字符串是否以某个 / 某串字符结尾:
const father = 'xixi haha hehe'
father.endsWith('hehe') // true
(2)主动反复:能够应用 repeat 办法来使同一个字符串输入屡次(被间断复制屡次):
const sourceCode = 'repeat for 3 times;'
const repeated = sourceCode.repeat(3)
console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times;
箭头函数和一般函数有啥区别?箭头函数能当构造函数吗?
- 一般函数通过 function 关键字定义,this 无奈联合词法作用域应用,在运行时绑定,只取决于函数的调用形式,在哪里被调用,调用地位。(取决于调用者,和是否独立运行)
-
箭头函数应用被称为“胖箭头”的操作
=>
定义,箭头函数不利用一般函数 this 绑定的四种规定,而是依据外层(函数或全局)的作用域来决定 this,且箭头函数的绑定无奈被批改(new 也不行)。- 箭头函数罕用于回调函数中,包含事件处理器或定时器
- 箭头函数和 var self = this,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域
- 没有原型、没有 this、没有 super,没有 arguments,没有 new.target
-
不能通过 new 关键字调用
- 一个函数外部有两个办法:[[Call]] 和 [[Construct]],在通过 new 进行函数调用时,会执行 [[construct]] 办法,创立一个实例对象,而后再执行这个函数体,将函数的 this 绑定在这个实例对象上
- 当间接调用时,执行 [[Call]] 办法,间接执行函数体
- 箭头函数没有 [[Construct]] 办法,不能被用作结构函数调用,当应用 new 进行函数调用时会报错。
function foo() {return (a) => {console.log(this.a);
}
}
var obj1 = {a: 2}
var obj2 = {a: 3}
var bar = foo.call(obj1);
bar.call(obj2);
字符串模板
function render(template, data) {const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
if (reg.test(template)) { // 判断模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
return render(template, data); // 递归的渲染并返回渲染后的构造
}
return template; // 如果模板没有模板字符串间接返回
}
测试:
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let person = {
name: '布兰',
age: 12
}
render(template, person); // 我是布兰,年龄 12,性别 undefined
参考 前端进阶面试题具体解答
事件循环机制(Event Loop)
事件循环机制从整体上通知了咱们 JavaScript 代码的执行程序 Event Loop
即事件循环,是指浏览器或 Node
的一种解决 javaScript
单线程运行时不会阻塞的一种机制,也就是咱们常常应用 异步 的原理。
先执行 Script 脚本,而后清空微工作队列,而后开始下一轮事件循环,持续先执行宏工作,再清空微工作队列,如此往返。
- 宏工作:Script/setTimeout/setInterval/setImmediate/ I/O / UI Rendering
- 微工作:process.nextTick()/Promise
上诉的 setTimeout 和 setInterval 等都是工作源,真正进入工作队列的是他们散发的工作。
优先级
- setTimeout = setInterval 一个队列
- setTimeout > setImmediate
- process.nextTick > Promise
for (const macroTask of macroTaskQueue) {handleMacroTask();
for (const microTask of microTaskQueue) {handleMicroTask(microTask);
}
}
说一下前端登录的流程?
首次登录的时候,前端调后调的登录接口,发送用户名和明码,后端收到申请,验证用户名和明码,验证胜利,就给前端返回一个 token,和一个用户信息的值,前端拿到 token,将 token 贮存到 Vuex 中,而后从 Vuex 中把 token 的值存入浏览器 Cookies 中。把用户信息存到 Vuex 而后再存储到 LocalStroage 中, 而后跳转到下一个页面,依据后端接口的要求,只有不登录就不能拜访的页面须要在前端每次跳转页面师判断 Cookies 中是否有 token,没有就跳转到登录页,有就跳转到相应的页面,咱们应该再每次发送 post/get 申请的时候应该退出 token,罕用办法再我的项目 utils/service.js 中增加全局拦截器,将 token 的值放入申请头中 后端判断申请头中有无 token,有 token,就拿到 token 并验证 token 是否过期,在这里过期会返回有效的 token 而后有个跳回登录页面从新登录并且革除本地用户的信息
异步任务调度器
形容:实现一个带并发限度的异步调度器 Scheduler,保障同时运行的工作最多有 limit
个。
实现:
class Scheduler {queue = []; // 用队列保留正在执行的工作
runCount = 0; // 计数正在执行的工作个数
constructor(limit) {this.maxCount = limit; // 容许并发的最大个数}
add(time, data){const promiseCreator = () => {return new Promise((resolve, reject) => {setTimeout(() => {console.log(data);
resolve();}, time);
});
}
this.queue.push(promiseCreator);
// 每次增加的时候都会尝试去执行工作
this.request();}
request() {
// 队列中还有工作才会被执行
if(this.queue.length && this.runCount < this.maxCount) {
this.runCount++;
// 执行先退出队列的函数
this.queue.shift()().then(() => {
this.runCount--;
// 尝试进行下一次工作
this.request();});
}
}
}
// 测试
const scheduler = new Scheduler(2);
const addTask = (time, data) => {scheduler.add(time, data);
}
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
// 输入后果 2 3 1 4
10 个 Ajax 同时发动申请,全副返回展现后果,并且至少容许三次失败,说出设计思路
这个问题置信很多人会第一工夫想到 Promise.all
,然而这个函数有一个局限在于如果失败一次就返回了,间接这样实现会有点问题,须要变通下。以下是两种实现思路
// 以下是不残缺代码,着重于思路 非 Promise 写法
let successCount = 0
let errorCount = 0
let datas = []
ajax(url, (res) => {if (success) {
success++
if (success + errorCount === 10) {console.log(datas)
} else {datas.push(res.data)
}
} else {
errorCount++
if (errorCount > 3) {
// 失败次数大于 3 次就应该报错了
throw Error('失败三次')
}
}
})
// Promise 写法
let errorCount = 0
let p = new Promise((resolve, reject) => {if (success) {resolve(res.data)
} else {
errorCount++
if (errorCount > 3) {
// 失败次数大于 3 次就应该报错了
reject(error)
} else {resolve(error)
}
}
})
Promise.all([p]).then(v => {console.log(v);
});
插入排序 – 工夫复杂度 n^2
题目形容: 实现一个插入排序
实现代码如下:
function insertSort(arr) {for (let i = 1; i < arr.length; i++) {
let j = i;
let target = arr[j];
while (j > 0 && arr[j - 1] > target) {arr[j] = arr[j - 1];
j--;
}
arr[j] = target;
}
return arr;
}
// console.log(insertSort([3, 6, 2, 4, 1]));
生命周期
init
initLifecycle/Event
,往 vm 上挂载各种属性callHook: beforeCreated
: 实例刚创立initInjection/initState
: 初始化注入和data
响应性created: 创立实现,属性曾经绑定,但还未生成实在
dom`- 进行元素的挂载:
$el / vm.$mount()
-
是否有
template
: 解析成render function
*.vue
文件:vue-loader
会将<template>
编译成render function
beforeMount
: 模板编译 / 挂载之前- 执行
render function
,生成实在的dom
,并替换到dom tree
中 mounted
: 组件已挂载
update
- 执行
diff
算法,比对扭转是否须要触发UI
更新 flushScheduleQueue
watcher.before
: 触发beforeUpdate
钩子 –watcher.run()
: 执行watcher
中的notify
,告诉所有依赖项更新 UI- 触发
updated
钩子: 组件已更新 actived / deactivated(keep-alive)
: 不销毁,缓存,组件激活与失活-
destroy
beforeDestroy
: 销毁开始-
销毁本身且递归销毁子组件以及事件监听
remove()
: 删除节点watcher.teardown()
: 清空依赖vm.$off()
: 解绑监听
destroyed
: 实现后触发钩子
Vue2 | Vue3 |
---|---|
beforeCreate |
❌setup (代替) |
created |
❌setup (代替) |
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
nUpdated |
beforeDestroy |
onBeforeUnmount |
destroyed |
onUnmounted |
errorCaptured |
onErrorCaptured |
– | 🎉onRenderTracked |
– | 🎉onRenderTriggered |
下面是 vue 的申明周期的简略梳理,接下来咱们间接以代码的模式来实现 vue 的初始化
new Vue({})
// 初始化 Vue 实例
function _init() {
// 挂载属性
initLifeCycle(vm)
// 初始化事件零碎,钩子函数等
initEvent(vm)
// 编译 slot、vnode
initRender(vm)
// 触发钩子
callHook(vm, 'beforeCreate')
// 增加 inject 性能
initInjection(vm)
// 实现数据响应性 props/data/watch/computed/methods
initState(vm)
// 增加 provide 性能
initProvide(vm)
// 触发钩子
callHook(vm, 'created')
// 挂载节点
if (vm.$options.el) {vm.$mount(vm.$options.el)
}
}
// 挂载节点实现
function mountComponent(vm) {
// 获取 render function
if (!this.options.render) {
// template to render
// Vue.compile = compileToFunctions
let {render} = compileToFunctions()
this.options.render = render
}
// 触发钩子
callHook('beforeMounte')
// 初始化观察者
// render 渲染 vdom,vdom = vm.render()
// update: 依据 diff 出的 patchs 挂载成实在的 dom
vm._update(vdom)
// 触发钩子
callHook(vm, 'mounted')
}
// 更新节点实现
funtion queueWatcher(watcher) {nextTick(flushScheduleQueue)
}
// 清空队列
function flushScheduleQueue() {
// 遍历队列中所有批改
for(){
// beforeUpdate
watcher.before()
// 依赖部分更新节点
watcher.update()
callHook('updated')
}
}
// 销毁实例实现
Vue.prototype.$destory = function() {
// 触发钩子
callHook(vm, 'beforeDestory')
// 本身及子节点
remove()
// 删除依赖
watcher.teardown()
// 删除监听
vm.$off()
// 触发钩子
callHook(vm, 'destoryed')
}
对 JSON 的了解
JSON 是一种基于文本的轻量级的数据交换格局。它能够被任何的编程语言读取和作为数据格式来传递。
在我的项目开发中,应用 JSON 作为前后端数据交换的形式。在前端通过将一个合乎 JSON 格局的数据结构序列化为
JSON 字符串,而后将它传递到后端,后端通过 JSON 格局的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。
因为 JSON 的语法是基于 js 的,因而很容易将 JSON 和 js 中的对象弄混,然而应该留神的是 JSON 和 js 中的对象不是一回事,JSON 中对象格局更加严格,比如说在 JSON 中属性值不能为函数,不能呈现 NaN 这样的属性值等,因而大多数的 js 对象是不合乎 JSON 对象的格局的。
在 js 中提供了两个函数来实现 js 数据结构和 JSON 格局的转换解决,
- JSON.stringify 函数,通过传入一个合乎 JSON 格局的数据结构,将其转换为一个 JSON 字符串。如果传入的数据结构不合乎 JSON 格局,那么在序列化的时候会对这些值进行对应的非凡解决,使其符合规范。在前端向后端发送数据时,能够调用这个函数将数据对象转化为 JSON 格局的字符串。
- JSON.parse() 函数,这个函数用来将 JSON 格局的字符串转换为一个 js 数据结构,如果传入的字符串不是规范的 JSON 格局的字符串的话,将会抛出谬误。当从后端接管到 JSON 格局的字符串时,能够通过这个办法来将其解析为一个 js 数据结构,以此来进行数据的拜访。
Vue 路由守卫有哪些,怎么设置,应用场景等
罕用的两个路由守卫:router.beforeEach 和 router.afterEach
每个守卫办法接管三个参数:to: Route: 行将要进入的指标 路由对象
from: Route: 以后导航正要来到的路由
next: Function: 肯定要调用该办法来 resolve 这个钩子。在我的项目中,个别在 beforeEach 这个钩子函数中进行路由跳转的一些信息判断。判断是否登录,是否拿到对应的路由权限等等。
如何取得对象非原型链上的属性?
应用后 hasOwnProperty()
办法来判断属性是否属于原型链的属性:
function iterate(obj){var res=[];
for(var key in obj){if(obj.hasOwnProperty(key))
res.push(key+':'+obj[key]);
}
return res;
}
什么是尾调用,应用尾调用有什么益处?
尾调用指的是函数的最初一步调用另一个函数。代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留以后的执行上下文,而后再新建另外一个执行上下文退出栈中。应用尾调用的话,因为曾经是函数的最初一步,所以这时能够不用再保留以后的执行上下文,从而节俭了内存,这就是尾调用优化。然而 ES6 的尾调用优化只在严格模式下开启,失常模式是有效的。
实现函数原型办法
call
应用一个指定的 this 值和一个或多个参数来调用一个函数。
实现要点:
- this 可能传入 null;
- 传入不固定个数的参数;
- 函数可能有返回值;
Function.prototype.call2 = function (context) {
var context = context || window;
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn
return result;
}
apply
apply 和 call 一样,惟一的区别就是 call 是传入不固定个数的参数,而 apply 是传入一个数组。
实现要点:
- this 可能传入 null;
- 传入一个数组;
- 函数可能有返回值;
Function.prototype.apply2 = function (context, arr) {
var context = context || window;
context.fn = this;
var result;
if (!arr) {result = context.fn();
} else {var args = [];
for (var i = 0, len = arr.length; i < len; i++) {args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
}
bind
bind 办法会创立一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时应用。
实现要点:
- bind() 除了 this 外,还可传入多个参数;
- bing 创立的新函数可能传入多个参数;
- 新函数可能被当做结构函数调用;
- 函数可能有返回值;
Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
实现 new 关键字
new 运算符用来创立用户自定义的对象类型的实例或者具备构造函数的内置对象的实例。
实现要点:
- new 会产生一个新对象;
- 新对象须要可能拜访到构造函数的属性,所以须要从新指定它的原型;
- 构造函数可能会显示返回;
function objectFactory() {var obj = new Object()
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
// ret || obj 这里这么写思考了构造函数显示返回 null 的状况
return typeof ret === 'object' ? ret || obj : obj;
};
应用:
function person(name, age) {
this.name = name
this.age = age
}
let p = objectFactory(person, '布兰', 12)
console.log(p) // {name: '布兰', age: 12}
实现 instanceof 关键字
instanceof 就是判断构造函数的 prototype 属性是否呈现在实例的原型链上。
function instanceOf(left, right) {
let proto = left.__proto__
while (true) {if (proto === null) return false
if (proto === right.prototype) {return true}
proto = proto.__proto__
}
}
下面的 left.proto 这种写法能够换成 Object.getPrototypeOf(left)。
实现 Object.create
Object.create()办法创立一个新对象,应用现有的对象来提供新创建的对象的__proto__。
Object.create2 = function(proto, propertyObject = undefined) {if (typeof proto !== 'object' && typeof proto !== 'function') {throw new TypeError('Object prototype may only be an Object or null.')
if (propertyObject == null) {new TypeError('Cannot convert undefined or null to object')
}
function F() {}
F.prototype = proto
const obj = new F()
if (propertyObject != undefined) {Object.defineProperties(obj, propertyObject)
}
if (proto === null) {// 创立一个没有原型对象的对象,Object.create(null)
obj.__proto__ = null
}
return obj
}
实现 Object.assign
Object.assign2 = function(target, ...source) {if (target == null) {throw new TypeError('Cannot convert undefined or null to object')
}
let ret = Object(target)
source.forEach(function(obj) {if (obj != null) {for (let key in obj) {if (obj.hasOwnProperty(key)) {ret[key] = obj[key]
}
}
}
})
return ret
}
实现 JSON.stringify
JSON.stringify([, replacer [, space]) 办法是将一个 JavaScript 值 (对象或者数组) 转换为一个 JSON 字符串。此处模仿实现,不思考可选的第二个参数 replacer 和第三个参数 space
-
根本数据类型:
- undefined 转换之后仍是 undefined(类型也是 undefined)
- boolean 值转换之后是字符串 “false”/”true”
- number 类型 (除了 NaN 和 Infinity) 转换之后是字符串类型的数值
- symbol 转换之后是 undefined
- null 转换之后是字符串 “null”
- string 转换之后仍是 string
- NaN 和 Infinity 转换之后是字符串 “null”
- 函数类型:转换之后是 undefined
-
如果是对象类型(非函数)
- 如果是一个数组:如果属性值中呈现了 undefined、任意的函数以及 symbol,转换成字符串 “null”;
- 如果是 RegExp 对象:返回 {} (类型是 string);
- 如果是 Date 对象,返回 Date 的 toJSON 字符串值;
-
如果是一般对象;
- 如果有 toJSON() 办法,那么序列化 toJSON() 的返回值。
- 如果属性值中呈现了 undefined、任意的函数以及 symbol 值,疏忽。
- 所有以 symbol 为属性键的属性都会被齐全疏忽掉。
- 对蕴含循环援用的对象(对象之间互相援用,造成有限循环)执行此办法,会抛出谬误。
function jsonStringify(data) {
let dataType = typeof data;
if (dataType !== 'object') {
let result = data;
//data 可能是 string/number/null/undefined/boolean
if (Number.isNaN(data) || data === Infinity) {
//NaN 和 Infinity 序列化返回 "null"
result = "null";
} else if (dataType === 'function' || dataType === 'undefined' || dataType === 'symbol') {
//function、undefined、symbol 序列化返回 undefined
return undefined;
} else if (dataType === 'string') {result = '"'+ data +'"';}
//boolean 返回 String()
return String(result);
} else if (dataType === 'object') {if (data === null) {return "null"} else if (data.toJSON && typeof data.toJSON === 'function') {return jsonStringify(data.toJSON());
} else if (data instanceof Array) {let result = [];
// 如果是数组
//toJSON 办法能够存在于原型链中
data.forEach((item, index) => {if (typeof item === 'undefined' || typeof item === 'function' || typeof item === 'symbol') {result[index] = "null";
} else {result[index] = jsonStringify(item);
}
});
result = "[" + result + "]";
return result.replace(/'/g,'"');
} else {
// 一般对象
/** * 循环援用抛错(暂未检测,循环援用时,堆栈溢出) * symbol key 疏忽 * undefined、函数、symbol 为属性值,被疏忽 */
let result = [];
Object.keys(data).forEach((item, index) => {if (typeof item !== 'symbol') {
//key 如果是 symbol 对象,疏忽
if (data[item] !== undefined && typeof data[item] !== 'function'
&& typeof data[item] !== 'symbol') {
// 键值如果是 undefined、函数、symbol 为属性值,疏忽
result.push('"'+ item +'"' + ":" + jsonStringify(data[item]));
}
}
});
return ("{" + result + "}").replace(/'/g,'"');
}
}
}
实现 JSON.parse
介绍 2 种办法实现:
- eval 实现;
- new Function 实现;
eval 实现
第一种形式最简略,也最直观,就是间接调用 eval,代码如下:
var json = '{"a":"1","b":2}';
var obj = eval("(" + json + ")"); // obj 就是 json 反序列化之后失去的对象
然而间接调用 eval 会存在平安问题,如果数据中可能不是 json 数据,而是可执行的 JavaScript 代码,那很可能会造成 XSS 攻打。因而,在调用 eval 之前,须要对数据进行校验。
var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
if (
rx_one.test(json.replace(rx_two, "@")
.replace(rx_three, "]")
.replace(rx_four, "")
)
) {var obj = eval("(" +json + ")");
}
new Function 实现
Function 与 eval 有雷同的字符串参数个性。
var json = '{"name":" 小姐姐 ","age":20}';
var obj = (new Function('return' + json))();
实现 Promise
实现 Promise 须要齐全读懂 Promise A+ 标准,不过从总体的实现上看,有如下几个点须要思考到:
- then 须要反对链式调用,所以得返回一个新的 Promise;
- 解决异步问题,所以得先用 onResolvedCallbacks 和 onRejectedCallbacks 别离把胜利和失败的回调存起来;
- 为了让链式调用失常进行上来,须要判断 onFulfilled 和 onRejected 的类型;
- onFulfilled 和 onRejected 须要被异步调用,这里用 setTimeout 模仿异步;
- 解决 Promise 的 resolve;
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class Promise {constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (value) = > {if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onResolvedCallbacks.forEach((fn) = > fn());
}
};
let reject = (reason) = > {if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach((fn) = > fn());
}
};
try {executor(resolve, reject);
} catch (error) {reject(error);
}
}
then(onFulfilled, onRejected) {
// 解决 onFufilled,onRejected 没有传值的问题
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) = > v;
// 因为谬误的值要让前面拜访到,所以这里也要抛出谬误,不然会在之后 then 的 resolve 中捕捉
onRejected = typeof onRejected === "function" ? onRejected : (err) = > {throw err;};
// 每次调用 then 都返回一个新的 promise
let promise2 = new Promise((resolve, reject) = > {if (this.status === FULFILLED) {
//Promise/A+ 2.2.4 --- setTimeout
setTimeout(() = > {
try {let x = onFulfilled(this.value);
// x 可能是一个 proimise
resolvePromise(promise2, x, resolve, reject);
} catch (e) {reject(e);
}
}, 0);
}
if (this.status === REJECTED) {
//Promise/A+ 2.2.3
setTimeout(() = > {
try {let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {reject(e);
}
}, 0);
}
if (this.status === PENDING) {this.onResolvedCallbacks.push(() = > {setTimeout(() = > {
try {let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() = > {setTimeout(() = > {
try {let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {reject(e);
}
}, 0);
});
}
});
return promise2;
}
}
const resolvePromise = (promise2, x, resolve, reject) = > {
// 本人期待本人实现是谬误的实现,用一个类型谬误,完结掉 promise Promise/A+ 2.3.1
if (promise2 === x) {
return reject(new TypeError("Chaining cycle detected for promise #<Promise>"));
}
// Promise/A+ 2.3.3.3.3 只能调用一次
let called;
// 后续的条件要严格判断 保障代码能和别的库一起应用
if ((typeof x === "object" && x != null) || typeof x === "function") {
try {
// 为了判断 resolve 过的就不必再 reject 了(比方 reject 和 resolve 同时调用的时候)Promise/A+ 2.3.3.1
let then = x.then;
if (typeof then === "function") {
// 不要写成 x.then,间接 then.call 就能够了 因为 x.then 会再次取值,Object.defineProperty Promise/A+ 2.3.3.3
then.call(x, (y) = > {
// 依据 promise 的状态决定是胜利还是失败
if (called) return;
called = true;
// 递归解析的过程(因为可能 promise 中还有 promise)Promise/A+ 2.3.3.3.1
resolvePromise(promise2, y, resolve, reject);
}, (r) = > {
// 只有失败就失败 Promise/A+ 2.3.3.3.2
if (called) return;
called = true;
reject(r);
});
} else {
// 如果 x.then 是个一般值就间接返回 resolve 作为后果 Promise/A+ 2.3.3.4
resolve(x);
}
} catch (e) {
// Promise/A+ 2.3.3.2
if (called) return;
called = true;
reject(e);
}
} else {
// 如果 x 是个一般值就间接返回 resolve 作为后果 Promise/A+ 2.3.4
resolve(x);
}
};
Promise 写完之后能够通过 promises-aplus-tests 这个包对咱们写的代码进行测试,看是否合乎 A+ 标准。不过测试前还得加一段代码:
// promise.js
// 这里是下面写的 Promise 全副代码
Promise.defer = Promise.deferred = function () {let dfd = {}
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
module.exports = Promise;
全局装置:
npm i promises-aplus-tests -g
终端下执行验证命令:
promises-aplus-tests promise.js
下面写的代码能够顺利通过全副 872 个测试用例。
Promise.resolve
Promsie.resolve(value) 能够将任何值转成值为 value 状态是 fulfilled 的 Promise,但如果传入的值自身是 Promise 则会原样返回它。
Promise.resolve = function(value) {
// 如果是 Promsie,则间接输入它
if(value instanceof Promise){return value}
return new Promise(resolve => resolve(value))
}
Promise.reject
和 Promise.resolve() 相似,Promise.reject() 会实例化一个 rejected 状态的 Promise。但与 Promise.resolve() 不同的是,如果给 Promise.reject() 传递一个 Promise 对象,则这个对象会成为新 Promise 的值。
Promise.reject = function(reason) {return new Promise((resolve, reject) => reject(reason))
}
Promise.all
Promise.all 的规定是这样的:
- 传入的所有 Promsie 都是 fulfilled,则返回由他们的值组成的,状态为 fulfilled 的新 Promise;
- 只有有一个 Promise 是 rejected,则返回 rejected 状态的新 Promsie,且它的值是第一个 rejected 的 Promise 的值;
- 只有有一个 Promise 是 pending,则返回一个 pending 状态的新 Promise;
Promise.all = function(promiseArr) {let index = 0, result = []
return new Promise((resolve, reject) => {promiseArr.forEach((p, i) => {Promise.resolve(p).then(val => {
index++
result[i] = val
if (index === promiseArr.length) {resolve(result)
}
}, err => {reject(err)
})
})
})
}
Promise.race
Promise.race 会返回一个由所有可迭代实例中第一个 fulfilled 或 rejected 的实例包装后的新实例。
Promise.race = function(promiseArr) {return new Promise((resolve, reject) => {
promiseArr.forEach(p => {Promise.resolve(p).then(val => {resolve(val)
}, err => {rejecte(err)
})
})
})
}
Promise.allSettled
Promise.allSettled 的规定是这样:
- 所有 Promise 的状态都变动了,那么新返回一个状态是 fulfilled 的 Promise,且它的值是一个数组,数组的每项由所有 Promise 的值和状态组成的对象;
- 如果有一个是 pending 的 Promise,则返回一个状态是 pending 的新实例;
Promise.allSettled = function(promiseArr) {let result = []
return new Promise((resolve, reject) => {promiseArr.forEach((p, i) => {Promise.resolve(p).then(val => {
result.push({
status: 'fulfilled',
value: val
})
if (result.length === promiseArr.length) {resolve(result)
}
}, err => {
result.push({
status: 'rejected',
reason: err
})
if (result.length === promiseArr.length) {resolve(result)
}
})
})
})
}
Promise.any
Promise.any 的规定是这样:
- 空数组或者所有 Promise 都是 rejected,则返回状态是 rejected 的新 Promsie,且值为 AggregateError 的谬误;
- 只有有一个是 fulfilled 状态的,则返回第一个是 fulfilled 的新实例;
- 其余状况都会返回一个 pending 的新实例;
Promise.any = function(promiseArr) {
let index = 0
return new Promise((resolve, reject) => {if (promiseArr.length === 0) return
promiseArr.forEach((p, i) => {Promise.resolve(p).then(val => {resolve(val)
}, err => {
index++
if (index === promiseArr.length) {reject(new AggregateError('All promises were rejected'))
}
})
})
})
}
label 的作用是什么?如何应用?
label 标签来定义表单控件的关系:当用户抉择 label 标签时,浏览器会主动将焦点转到和 label 标签相干的表单控件上。
- 应用办法 1:
<label for="mobile">Number:</label>
<input type="text" id="mobile"/>
- 应用办法 2:
<label>Date:<input type="text"/></label>
Promise.all
形容:所有 promise
的状态都变成 fulfilled
,就会返回一个状态为 fulfilled
的数组(所有promise
的 value
)。只有有一个失败,就返回第一个状态为 rejected
的 promise
实例的 reason
。
实现:
Promise.all = function(promises) {return new Promise((resolve, reject) => {if(Array.isArray(promises)) {if(promises.length === 0) return resolve(promises);
let result = [];
let count = 0;
promises.forEach((item, index) => {Promise.resolve(item).then(
value => {
count++;
result[index] = value;
if(count === promises.length) resolve(result);
},
reason => reject(reason)
);
})
}
else return reject(new TypeError("Argument is not iterable"));
});
}
页面有多张图片,HTTP 是怎么的加载体现?
- 在
HTTP 1
下,浏览器对一个域名下最大 TCP 连接数为 6,所以会申请屡次。能够用 多域名部署 解决。这样能够进步同时申请的数目,放慢页面图片的获取速度。 - 在
HTTP 2
下,能够一瞬间加载进去很多资源,因为,HTTP2 反对多路复用,能够在一个 TCP 连贯中发送多个 HTTP 申请。