共计 14695 个字符,预计需要花费 37 分钟才能阅读完成。
字符串查找
请应用最根本的遍从来实现判断字符串 a 是否被蕴含在字符串 b 中,并返回第一次呈现的地位(找不到返回 -1)。
a='34';b='1234567'; // 返回 2
a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
isContain(a,b);
function isContain(a, b) {for (let i in b) {if (a[0] === b[i]) {
let tmp = true;
for (let j in a) {if (a[j] !== b[~~i + ~~j]) {tmp = false;}
}
if (tmp) {return i;}
}
}
return -1;
}
setTimeout 与 setInterval 实现
setTimeout 模仿实现 setInterval
题目形容: setInterval
用来实现循环定时调用 可能会存在肯定的问题 能用 setTimeout
解决吗
实现代码如下:
function mySetInterval(fn, t) {
let timerId = null;
function interval() {fn();
timerId = setTimeout(interval, t); // 递归调用
}
timerId = setTimeout(interval, t); // 首次调用
return {
// 利用闭包的个性 保留 timerId
cancel:() => {clearTimeout(timerId)
}
}
}
// 测试
var a = mySetInterval(()=>{console.log(111);
},1000)
var b = mySetInterval(() => {console.log(222)
}, 1000)
// 终止定时器
a.cancel()
b.cancel()
为什么要用
setTimeout
模仿实现setInterval
?setInterval
的缺点是什么?
setInterval(fn(), N);
下面这句代码的意思其实是
fn()
将会在N
秒之后被推入工作队列。在setInterval
被推入工作队列时,如果在它后面有很多工作或者某个工作等待时间较长比方网络申请等,那么这个定时器的执行工夫和咱们预约它执行的工夫可能并不统一
// 最常见的呈现的就是,当咱们须要应用 ajax 轮询服务器是否有新数据时,必定会有一些人会应用 setInterval,然而无论网络情况如何,它都会去一遍又一遍的发送申请,最初的间隔时间可能和原定的工夫有很大的出入
// 做一个网络轮询,每一秒查问一次数据。let startTime = new Date().getTime();
let count = 0;
setInterval(() => {
let i = 0;
while (i++ < 10000000); // 假如的网络提早
count++;
console.log(
"与原设定的距离时差了:",
new Date().getTime() - (startTime + count * 1000),
"毫秒"
);
}, 1000)
// 输入:// 与原设定的距离时差了:567 毫秒
// 与原设定的距离时差了:552 毫秒
// 与原设定的距离时差了:563 毫秒
// 与原设定的距离时差了:554 毫秒(2 次)
// 与原设定的距离时差了:564 毫秒
// 与原设定的距离时差了:602 毫秒
// 与原设定的距离时差了:573 毫秒
// 与原设定的距离时差了:633 毫秒
再次强调,定时器指定的工夫距离,示意的是何时将定时器的代码增加到音讯队列,而不是何时执行代码。所以真正何时执行代码的工夫是不能保障的,取决于何时被主线程的事件循环取到,并执行。
setInterval(function, N)
// 即:每隔 N 秒把 function 事件推到音讯队列中
上图可见,
setInterval
每隔100ms
往队列中增加一个事件;100ms
后,增加T1
定时器代码至队列中,主线程中还有工作在执行,所以期待,some event
执行完结后执行T1
定时器代码;又过了100ms
,T2
定时器被增加到队列中,主线程还在执行T1
代码,所以期待;又过了100ms
,实践上又要往队列里推一个定时器代码,但因为此时T2
还在队列中,所以T3
不会被增加(T3
被跳过),后果就是此时被跳过;这里咱们能够看到,T1
定时器执行完结后马上执行了 T2 代码,所以并没有达到定时器的成果
setInterval 有两个毛病
- 应用
setInterval
时,某些距离会被跳过 - 可能多个定时器会间断执行
能够这么了解 :每个
setTimeout
产生的工作会间接push
到工作队列中;而setInterval
在每次把工作push
到工作队列前,都要进行一下判断 (看上次的工作是否仍在队列中)。因此咱们个别用setTimeout
模仿setInterval
,来躲避掉下面的毛病
setInterval 模仿实现 setTimeout
const mySetTimeout = (fn, t) => {const timer = setInterval(() => {clearInterval(timer);
fn();}, t);
};
// 测试
// mySetTimeout(()=>{// console.log(1);
// },1000)
数组中的数据依据 key 去重
给定一个任意数组,实现一个通用函数,让数组中的数据依据 key 排重:
const dedup = (data, getKey = () => {}) => {// todo}
let data = [{ id: 1, v: 1},
{id: 2, v: 2},
{id: 1, v: 1},
];
// 以 id 作为排重 key,执行函数失去后果
// data = [// { id: 1, v: 1},
// {id: 2, v: 2},
// ];
实现
const dedup = (data, getKey = () => {}) => {const dateMap = data.reduce((pre, cur) => {const key = getKey(cur)
if (!pre[key]) {pre[key] = cur
}
return pre
}, {})
return Object.values(dateMap)
}
应用
let data = [{ id: 1, v: 1},
{id: 2, v: 2},
{id: 1, v: 1},
];
console.log(dedup(data, (item) => item.id))
// 以 id 作为排重 key,执行函数失去后果
// data = [// { id: 1, v: 1},
// {id: 2, v: 2},
// ];
实现 Array.isArray 办法
Array.myIsArray = function(o) {return Object.prototype.toString.call(Object(o)) === '[object Array]';
};
console.log(Array.myIsArray([])); // true
实现一个繁难的 MVVM
实现一个繁难的
MVVM
我会分为这么几步来:
- 首先我会定义一个类
Vue
,这个类接管的是一个options
,那么其中可能有须要挂载的根元素的id
,也就是el
属性;而后应该还有一个data
属性,示意须要双向绑定的数据 - 其次我会定义一个
Dep
类,这个类产生的实例对象中会定义一个subs
数组用来寄存所依赖这个属性的依赖,曾经增加依赖的办法addSub
,删除办法removeSub
,还有一个notify
办法用来遍历更新它subs
中的所有依赖,同时 Dep 类有一个动态属性target
它用来示意以后的观察者,当后续进行依赖收集的时候能够将它增加到dep.subs
中。 - 而后设计一个
observe
办法,这个办法接管的是传进来的data
,也就是options.data
,外面会遍历data
中的每一个属性,并应用Object.defineProperty()
来重写它的get
和set
,那么这外面呢能够应用new Dep()
实例化一个dep
对象,在get
的时候调用其addSub
办法增加以后的观察者Dep.target
实现依赖收集,并且在set
的时候调用dep.notify
办法来告诉每一个依赖它的观察者进行更新 - 实现这些之后,咱们还须要一个
compile
办法来将 HTML 模版和数据联合起来。在这个办法中首先传入的是一个node
节点,而后遍历它的所有子级,判断是否有firstElmentChild
,有的话则进行递归调用 compile 办法,没有firstElementChild
的话且该child.innderHTML
用正则匹配满足有/\{\{(.*)\}\}/
项的话则示意有须要双向绑定的数据,那么就将用正则new Reg('\\{\\{\\s*' + key + '\\s*\\}\\}', 'gm')
替换掉是其为msg
变量。 - 实现变量替换的同时,还须要将
Dep.target
指向以后的这个child
,且调用一下this.opt.data[key]
,也就是为了触发这个数据的get
来对以后的child
进行依赖收集,这样下次数据变动的时候就能告诉child
进行视图更新了,不过在最初要记得将Dep.target
指为null
哦(其实在Vue
中是有一个targetStack
栈用来寄存target
的指向的) - 那么最初咱们只须要监听
document
的DOMContentLoaded
而后在回调函数中实例化这个Vue
对象就能够了
coding :
须要留神的点:
childNodes
会获取到所有的子节点以及文本节点(包含元素标签中的空白节点)firstElementChild
示意获取元素的第一个字元素节点,以此来辨别是不是元素节点,如果是的话则调用compile
进行递归调用,否则用正则匹配- 这外面的正则真的不难,大家能够看一下
残缺代码如下:
<!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>MVVM</title>
</head>
<body>
<div id="app">
<h3> 姓名 </h3>
<p>{{name}}</p>
<h3> 年龄 </h3>
<p>{{age}}</p>
</div>
</body>
</html>
<script>
document.addEventListener(
"DOMContentLoaded",
function () {let opt = { el: "#app", data: { name: "期待批改...", age: 20} };
let vm = new Vue(opt);
setTimeout(() => {opt.data.name = "jing";}, 2000);
},
false
);
class Vue {constructor(opt) {
this.opt = opt;
this.observer(opt.data);
let root = document.querySelector(opt.el);
this.compile(root);
}
observer(data) {Object.keys(data).forEach((key) => {let obv = new Dep();
data["_" + key] = data[key];
Object.defineProperty(data, key, {get() {Dep.target && obv.addSubNode(Dep.target);
return data["_" + key];
},
set(newVal) {obv.update(newVal);
data["_" + key] = newVal;
},
});
});
}
compile(node) {[].forEach.call(node.childNodes, (child) => {if (!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)) {let key = RegExp.$1.trim();
child.innerHTML = child.innerHTML.replace(new RegExp("\\{\\{\\s*" + key + "\\s*\\}\\}", "gm"),
this.opt.data[key]
);
Dep.target = child;
this.opt.data[key];
Dep.target = null;
} else if (child.firstElementChild) this.compile(child);
});
}
}
class Dep {constructor() {this.subNode = [];
}
addSubNode(node) {this.subNode.push(node);
}
update(newVal) {this.subNode.forEach((node) => {node.innerHTML = newVal;});
}
}
</script>
简化版 2
function update(){console.log('数据变动~~~ mock update view')
}
let obj = [1,2,3]
// 变异办法 push shift unshfit reverse sort splice pop
// Object.defineProperty
let oldProto = Array.prototype;
let proto = Object.create(oldProto); // 克隆了一分
['push','shift'].forEach(item=>{proto[item] = function(){update();
oldProto[item].apply(this,arguments);
}
})
function observer(value){ // proxy reflect
if(Array.isArray(value)){
// AOP
return value.__proto__ = proto;
// 重写 这个数组里的 push shift unshfit reverse sort splice pop
}
if(typeof value !== 'object'){return value;}
for(let key in value){defineReactive(value,key,value[key]);
}
}
function defineReactive(obj,key,value){observer(value); // 如果是对象 持续减少 getter 和 setter
Object.defineProperty(obj,key,{get(){return value;},
set(newValue){if(newValue !== value){observer(newValue);
value = newValue;
update();}
}
})
}
observer(obj);
// AOP
// obj.name = {n:200}; // 数据变了 须要更新视图 深度监控
// obj.name.n = 100;
obj.push(123);
obj.push(456);
console.log(obj);
实现 Object.freeze
Object.freeze
解冻一个对象,让其不能再增加 / 删除属性,也不能批改该对象已有属性的可枚举性、可配置可写性,也不能批改已有属性的值和它的原型属性,最初返回一个和传入参数雷同的对象
function myFreeze(obj){
// 判断参数是否为 Object 类型,如果是就关闭对象,循环遍历对象。去掉原型属性,将其 writable 个性设置为 false
if(obj instanceof Object){Object.seal(obj); // 关闭对象
for(let key in obj){if(obj.hasOwnProperty(key)){
Object.defineProperty(obj,key,{writable:false // 设置只读})
// 如果属性值仍然为对象,要通过递归来进行进一步的解冻
myFreeze(obj[key]);
}
}
}
}
参考 前端进阶面试题具体解答
实现 JSONP 办法
利用
<script>
标签不受跨域限度的特点,毛病是只能反对get
申请
- 创立
script
标签 - 设置
script
标签的src
属性,以问号传递参数,设置好回调函数callback
名称 - 插入到
html
文本中 - 调用回调函数,
res
参数就是获取的数据
function jsonp({url,params,callback}) {return new Promise((resolve,reject)=>{let script = document.createElement('script')
window[callback] = function (data) {resolve(data)
document.body.removeChild(script)
}
var arr = []
for(var key in params) {arr.push(`${key}=${params[key]}`)
}
script.type = 'text/javascript'
script.src = `${url}?callback=${callback}&${arr.join('&')}`
document.body.appendChild(script)
})
}
// 测试用例
jsonp({
url: 'http://suggest.taobao.com/sug',
callback: 'getData',
params: {
q: 'iphone 手机',
code: 'utf-8'
},
}).then(data=>{console.log(data)})
- 设置
CORS: Access-Control-Allow-Origin:*
postMessage
类数组转化为数组的办法
const arrayLike=document.querySelectorAll('div')
// 1. 扩大运算符
[...arrayLike]
// 2.Array.from
Array.from(arrayLike)
// 3.Array.prototype.slice
Array.prototype.slice.call(arrayLike)
// 4.Array.apply
Array.apply(null, arrayLike)
// 5.Array.prototype.concat
Array.prototype.concat.apply([], arrayLike)
用 Promise 实现图片的异步加载
let imageAsync=(url)=>{return new Promise((resolve,reject)=>{let img = new Image();
img.src = url;
img.οnlοad=()=>{console.log(` 图片申请胜利,此处进行通用操作 `);
resolve(image);
}
img.οnerrοr=(err)=>{console.log(` 失败,此处进行失败的通用操作 `);
reject(err);
}
})
}
imageAsync("url").then(()=>{console.log("加载胜利");
}).catch((error)=>{console.log("加载失败");
})
原生实现
function ajax() {let xhr = new XMLHttpRequest() // 实例化,以调用办法
xhr.open('get', 'https://www.google.com') // 参数 2,url。参数三:异步
xhr.onreadystatechange = () => { // 每当 readyState 属性扭转时,就会调用该函数。if (xhr.readyState === 4) { //XMLHttpRequest 代理以后所处状态。if (xhr.status >= 200 && xhr.status < 300) { //200-300 申请胜利
let string = request.responseText
//JSON.parse() 办法用来解析 JSON 字符串,结构由字符串形容的 JavaScript 值或对象
let object = JSON.parse(string)
}
}
}
request.send() // 用于理论收回 HTTP 申请。不带参数为 GET 申请}
手写 Promise.then
then
办法返回一个新的 promise
实例,为了在 promise
状态发生变化时(resolve
/ reject
被调用时)再执行 then
里的函数,咱们应用一个 callbacks
数组先把传给 then 的函数暂存起来,等状态扭转时再调用。
那么,怎么保障后一个 **then**
里的办法在前一个 **then**
(可能是异步)完结之后再执行呢? 咱们能够将传给 then
的函数和新 promise
的 resolve
一起 push
到前一个 promise
的 callbacks
数组中,达到承前启后的成果:
- 承前:以后一个
promise
实现后,调用其resolve
变更状态,在这个resolve
里会顺次调用callbacks
里的回调,这样就执行了then
里的办法了 - 启后:上一步中,当
then
里的办法执行实现后,返回一个后果,如果这个后果是个简略的值,就间接调用新promise
的resolve
,让其状态变更,这又会顺次调用新promise
的callbacks
数组里的办法,周而复始。。如果返回的后果是个promise
,则须要等它实现之后再触发新promise
的resolve
,所以能够在其后果的then
里调用新promise
的resolve
then(onFulfilled, onReject){
// 保留前一个 promise 的 this
const self = this;
return new MyPromise((resolve, reject) => {
// 封装前一个 promise 胜利时执行的函数
let fulfilled = () => {
try{const result = onFulfilled(self.value); // 承前
return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); // 启后
}catch(err){reject(err)
}
}
// 封装前一个 promise 失败时执行的函数
let rejected = () => {
try{const result = onReject(self.reason);
return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
}catch(err){reject(err)
}
}
switch(self.status){
case PENDING:
self.onFulfilledCallbacks.push(fulfilled);
self.onRejectedCallbacks.push(rejected);
break;
case FULFILLED:
fulfilled();
break;
case REJECT:
rejected();
break;
}
})
}
留神:
- 间断多个
then
里的回调办法是同步注册的,但注册到了不同的callbacks
数组中,因为每次then
都返回新的promise
实例(参考下面的例子和图) - 注册实现后开始执行构造函数中的异步事件,异步实现之后顺次调用
callbacks
数组中提前注册的回调
字符串查找
请应用最根本的遍从来实现判断字符串 a 是否被蕴含在字符串 b 中,并返回第一次呈现的地位(找不到返回 -1)。
a='34';b='1234567'; // 返回 2
a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
isContain(a,b);
function isContain(a, b) {for (let i in b) {if (a[0] === b[i]) {
let tmp = true;
for (let j in a) {if (a[j] !== b[~~i + ~~j]) {tmp = false;}
}
if (tmp) {return i;}
}
}
return -1;
}
实现数组的 push 办法
let arr = [];
Array.prototype.push = function() {for( let i = 0 ; i < arguments.length ; i++){this[this.length] = arguments[i] ;
}
return this.length;
}
debounce(防抖)
触发高频工夫后 n 秒内函数只会执行一次, 如果 n 秒内高频工夫再次触发, 则从新计算工夫。
const debounce = (fn, time) => {
let timeout = null;
return function() {clearTimeout(timeout)
timeout = setTimeout(() => {fn.apply(this, arguments);
}, time);
}
};
防抖常利用于用户进行搜寻输出节约申请资源,window
触发 resize
事件时进行防抖只触发一次。
将 VirtualDom 转化为实在 DOM 构造
这是以后 SPA 利用的外围概念之一
// vnode 构造:// {
// tag,
// attrs,
// children,
// }
//Virtual DOM => DOM
function render(vnode, container) {container.appendChild(_render(vnode));
}
function _render(vnode) {
// 如果是数字类型转化为字符串
if (typeof vnode === 'number') {vnode = String(vnode);
}
// 字符串类型间接就是文本节点
if (typeof vnode === 'string') {return document.createTextNode(vnode);
}
// 一般 DOM
const dom = document.createElement(vnode.tag);
if (vnode.attrs) {
// 遍历属性
Object.keys(vnode.attrs).forEach(key => {const value = vnode.attrs[key];
dom.setAttribute(key, value);
})
}
// 子数组进行递归操作
vnode.children.forEach(child => render(child, dom));
return dom;
}
实现类的继承
实现类的继承 - 简版
类的继承在几年前是重点内容,有 n 种继承形式各有优劣,es6 遍及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深刻了解的去看红宝书即可,咱们目前只实现一种最现实的继承形式。
// 寄生组合继承
function Parent(name) {this.name = name}
Parent.prototype.say = function() {console.log(this.name + ` say`);
}
Parent.prototype.play = function() {console.log(this.name + ` play`);
}
function Child(name, parent) {
// 将父类的构造函数绑定在子类上
Parent.call(this, parent)
this.name = name
}
/**
1. 这一步不必 Child.prototype = Parent.prototype 的起因是怕共享内存,批改父类原型对象就会影响子类
2. 不必 Child.prototype = new Parent()的起因是会调用 2 次父类的构造方法(另一次是 call),会存在一份多余的父类实例属性
3. Object.create 是创立了父类原型的正本,与父类原型齐全隔离
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {console.log(this.name + ` say`);
}
// 留神记得把子类的结构指向子类自身
Child.prototype.constructor = Child;
// 测试
var parent = new Parent('parent');
parent.say()
var child = new Child('child');
child.say()
child.play(); // 继承父类的办法
ES5 实现继承 - 具体
第一种形式是借助 call 实现继承
function Parent1(){this.name = 'parent1';}
function Child1(){Parent1.call(this);
this.type = 'child1'
}
console.log(new Child1);
这样写的时候子类尽管可能拿到父类的属性值,然而问题是父类中一旦存在办法那么子类无奈继承。那么引出上面的办法
第二种形式借助原型链实现继承:
function Parent2() {
this.name = 'parent2';
this.play = [1, 2, 3]
}
function Child2() {this.type = 'child2';}
Child2.prototype = new Parent2();
console.log(new Child2());
看似没有问题,父类的办法和属性都可能拜访,但实际上有一个潜在的有余。举个例子:
var s1 = new Child2();
var s2 = new Child2();
s1.play.push(4);
console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]
明明我只扭转了 s1 的 play 属性,为什么 s2 也跟着变了呢?很简略,因为两个实例应用的是同一个原型对象
第三种形式:将前两种组合:
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
function Child3() {Parent3.call(this);
this.type = 'child3';
}
Child3.prototype = new Parent3();
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3]
之前的问题都得以解决。然而这里又徒增了一个新问题,那就是 Parent3 的构造函数会多执行了一次(
Child3.prototype = new Parent3()
;)。这是咱们不愿看到的。那么如何解决这个问题?
第四种形式: 组合继承的优化 1
function Parent4 () {
this.name = 'parent4';
this.play = [1, 2, 3];
}
function Child4() {Parent4.call(this);
this.type = 'child4';
}
Child4.prototype = Parent4.prototype;
这里让将父类原型对象间接给到子类,父类构造函数只执行一次,而且父类属性和办法均能拜访,然而咱们来测试一下
var s3 = new Child4();
var s4 = new Child4();
console.log(s3)
子类实例的构造函数是 Parent4,显然这是不对的,应该是 Child4。
第五种形式(最举荐应用):优化 2
function Parent5 () {
this.name = 'parent5';
this.play = [1, 2, 3];
}
function Child5() {Parent5.call(this);
this.type = 'child5';
}
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;
这是最举荐的一种形式,靠近完满的继承。
Promise.race
Promise.race = function(promiseArr) {return new Promise((resolve, reject) => {
promiseArr.forEach(p => {
// 如果不是 Promise 实例须要转化为 Promise 实例
Promise.resolve(p).then(val => resolve(val),
err => reject(err),
)
})
})
}
实现一下 hash 路由
根底的 html
代码:
<html>
<style>
html, body {
margin: 0;
height: 100%;
}
ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
}
.box {
width: 100%;
height: 100%;
background-color: red;
}
</style>
<body>
<ul>
<li>
<a href="#red"> 红色 </a>
</li>
<li>
<a href="#green"> 绿色 </a>
</li>
<li>
<a href="#purple"> 紫色 </a>
</li>
</ul>
</body>
</html>
简略实现:
<script>
const box = document.getElementsByClassName('box')[0];
const hash = location.hash
window.onhashchange = function (e) {const color = hash.slice(1)
box.style.background = color
}
</script>
封装成一个 class:
<script>
const box = document.getElementsByClassName('box')[0];
const hash = location.hash
class HashRouter {constructor (hashStr, cb) {
this.hashStr = hashStr
this.cb = cb
this.watchHash()
this.watch = this.watchHash.bind(this)
window.addEventListener('hashchange', this.watch)
}
watchHash () {let hash = window.location.hash.slice(1)
this.hashStr = hash
this.cb(hash)
}
}
new HashRouter('red', (color) => {box.style.background = color})
</script>
实现 findIndex 办法
var users = [{id: 1, name: '张三'},
{id: 2, name: '张三'},
{id: 3, name: '张三'},
{id: 4, name: '张三'}
]
Array.prototype.myFindIndex = function (callback) {// var callback = function (item, index) {return item.id === 4}
for (var i = 0; i < this.length; i++) {if (callback(this[i], i)) {
// 这里返回
return i
}
}
}
var ret = users.myFind(function (item, index) {return item.id === 2})
console.log(ret)
throttle(节流)
高频工夫触发, 但 n 秒内只会执行一次, 所以节流会浓缩函数的执行频率。
const throttle = (fn, time) => {
let flag = true;
return function() {if (!flag) return;
flag = false;
setTimeout(() => {fn.apply(this, arguments);
flag = true;
}, time);
}
}
节流常利用于鼠标一直点击触发、监听滚动事件。