共计 15118 个字符,预计需要花费 38 分钟才能阅读完成。
实现 bind
实现 bind 要做什么
- 返回一个函数,绑定 this,传递预置参数
- bind 返回的函数能够作为构造函数应用。故作为构造函数时应使得 this 生效,然而传入的参数仍然无效
// mdn 的实现
if (!Function.prototype.bind) {Function.prototype.bind = function(oThis) {if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
// this instanceof fBound === true 时, 阐明返回的 fBound 被当做 new 的结构函数调用
return fToBind.apply(this instanceof fBound
? this
: oThis,
// 获取调用时 (fBound) 的传参.bind 返回的函数入参往往是这么传递的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 保护原型关系
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
// 上行的代码使 fBound.prototype 是 fNOP 的实例, 因而
// 返回的 fBound 若作为 new 的构造函数,new 生成的新对象作为 this 传入 fBound, 新对象的__proto__就是 fNOP 的实例
fBound.prototype = new fNOP();
return fBound;
};
}
手写 Promise.all
1) 外围思路
- 接管一个 Promise 实例的数组或具备 Iterator 接口的对象作为参数
- 这个办法返回一个新的 promise 对象,
- 遍历传入的参数,用 Promise.resolve()将参数 ” 包一层 ”,使其变成一个 promise 对象
- 参数所有回调胜利才是胜利,返回值数组与参数程序统一
- 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。
2)实现代码
一般来说,Promise.all 用来解决多个并发申请,也是为了页面数据结构的不便,将一个页面所用到的在不同接口的数据一起申请过去,不过,如果其中一个接口失败了,多个申请也就失败了,页面可能啥也出不来,这就看以后页面的耦合水平了
function promiseAll(promises) {return new Promise(function(resolve, reject) {if(!Array.isArray(promises)){throw new TypeError(`argument must be a array`)
}
var resolvedCounter = 0;
var promiseNum = promises.length;
var resolvedResult = [];
for (let i = 0; i < promiseNum; i++) {Promise.resolve(promises[i]).then(value=>{
resolvedCounter++;
resolvedResult[i] = value;
if (resolvedCounter == promiseNum) {return resolve(resolvedResult)
}
},error=>{return reject(error)
})
}
})
}
// test
let p1 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(1)
}, 1000)
})
let p2 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(2)
}, 2000)
})
let p3 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(3)
}, 3000)
})
promiseAll([p3, p1, p2]).then(res => {console.log(res) // [3, 1, 2]
})
实现 prototype 继承
所谓的原型链继承就是让新实例的原型等于父类的实例:
// 父办法
function SupperFunction(flag1){this.flag1 = flag1;}
// 子办法
function SubFunction(flag2){this.flag2 = flag2;}
// 父实例
var superInstance = new SupperFunction(true);
// 子继承父
SubFunction.prototype = superInstance;
// 子实例
var subInstance = new SubFunction(false);
// 子调用本人和父的属性
subInstance.flag1; // true
subInstance.flag2; // false
实现节流函数(throttle)
防抖函数原理: 规定在一个单位工夫内,只能触发一次函数。如果这个单位工夫内触发屡次函数,只有一次失效。
// 手写简化版
// 节流函数
const throttle = (fn, delay = 500) => {
let flag = true;
return (...args) => {if (!flag) return;
flag = false;
setTimeout(() => {fn.apply(this, args);
flag = true;
}, delay);
};
};
实用场景:
- 拖拽场景:固定工夫内只执行一次,避免超高频次触发地位变动
- 缩放场景:监控浏览器 resize
- 动画场景:防止短时间内屡次触发动画引起性能问题
实现 add(1)(2)(3)
函数柯里化概念:柯里化(Currying)是把承受多个参数的函数转变为承受一个繁多参数的函数,并且返回承受余下的参数且返回后果的新函数的技术。
1)粗犷版
function add (a) {return function (b) {return function (c) {return a + b + c;}
}
}
console.log(add(1)(2)(3)); // 6
2)柯里化解决方案
- 参数长度固定
var add = function (m) {var temp = function (n) {return add(m + n);
}
temp.toString = function () {return m;}
return temp;
};
console.log(add(3)(4)(5)); // 12
console.log(add(3)(6)(9)(25)); // 43
对于 add(3)(4)(5),其执行过程如下:
- 先执行 add(3),此时 m =3,并且返回 temp 函数;
- 执行 temp(4),这个函数内执行 add(m+n),n 是此次传进来的数值 4,m 值还是上一步中的 3,所以 add(m+n)=add(3+4)=add(7),此时 m =7,并且返回 temp 函数
- 执行 temp(5),这个函数内执行 add(m+n),n 是此次传进来的数值 5,m 值还是上一步中的 7,所以 add(m+n)=add(7+5)=add(12),此时 m =12,并且返回 temp 函数
- 因为前面没有传入参数,等于返回的 temp 函数不被执行而是打印,理解 JS 的敌人都晓得对象的 toString 是批改对象转换字符串的办法,因而代码中 temp 函数的 toString 函数 return m 值,而 m 值是最初一步执行函数时的值 m =12,所以返回值是 12。
- 参数长度不固定
function add (...args) {
// 求和
return args.reduce((a, b) => a + b)
}
function currying (fn) {let args = []
return function temp (...newArgs) {if (newArgs.length) {
args = [
...args,
...newArgs
]
return temp
} else {let val = fn.apply(this, args)
args = [] // 保障再次调用时清空
return val
}
}
}
let addCurry = currying(add)
console.log(addCurry(1)(2)(3)(4, 5)()) //15
console.log(addCurry(1)(2)(3, 4, 5)()) //15
console.log(addCurry(1)(2, 3, 4, 5)()) //15
Array.prototype.reduce()
Array.prototype.reduce = function(callback, initialValue) {if (this == undefined) {throw new TypeError('this is null or not defined');
}
if (typeof callback !== 'function') {throw new TypeError(callbackfn + 'is not a function');
}
const O = Object(this);
const len = this.length >>> 0;
let accumulator = initialValue;
let k = 0;
// 如果第二个参数为 undefined 的状况下
// 则数组的第一个有效值作为累加器的初始值
if (accumulator === undefined) {while (k < len && !(k in O)) {k++;}
// 如果超出数组界线还没有找到累加器的初始值,则 TypeError
if (k >= len) {throw new TypeError('Reduce of empty array with no initial value');
}
accumulator = O[k++];
}
while (k < len) {if (k in O) {accumulator = callback.call(undefined, accumulator, O[k], k, O);
}
k++;
}
return accumulator;
}
参考 前端进阶面试题具体解答
实现深拷贝
- 浅拷贝: 浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为援用类型的话,那么会将这个援用的地址复制给对象,因而两个对象会有同一个援用类型的援用。浅拷贝能够应用 Object.assign 和开展运算符来实现。
- 深拷贝: 深拷贝绝对浅拷贝而言,如果遇到属性值为援用类型的时候,它新建一个援用类型并将对应的值复制给它,因而对象取得的一个新的援用类型而不是一个原有类型的援用。深拷贝对于一些对象能够应用 JSON 的两个函数来实现,然而因为 JSON 的对象格局比 js 的对象格局更加严格,所以如果属性值里边呈现函数或者 Symbol 类型的值时,会转换失败
(1)JSON.stringify()
JSON.parse(JSON.stringify(obj))
是目前比拟罕用的深拷贝办法之一,它的原理就是利用JSON.stringify
将js
对象序列化(JSON 字符串),再应用JSON.parse
来反序列化 (还原)js
对象。- 这个办法能够简略粗犷的实现深拷贝,然而还存在问题,拷贝的对象中如果有函数,undefined,symbol,当应用过
JSON.stringify()
进行解决之后,都会隐没。
let obj1 = { a: 0,
b: {c: 0}
};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}
(2)函数库 lodash 的_.cloneDeep 办法
该函数库也有提供_.cloneDeep 用来做 Deep Copy
var _ = require('lodash');
var obj1 = {
a: 1,
b: {f: { g: 1} },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
(3)手写实现深拷贝函数
// 深拷贝的实现
function deepCopy(object) {if (!object || typeof object !== "object") return;
let newObject = Array.isArray(object) ? [] : {};
for (let key in object) {if (object.hasOwnProperty(key)) {newObject[key] =
typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
}
}
return newObject;
}
解析 URL Params 为对象
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 后果
{ user: 'anonymous',
id: [123, 456], // 反复呈现的 key 要组装成数组,能被转成数字的就转成数字类型
city: '北京', // 中文需解码
enabled: true, // 未指定值得 key 约定为 true
}
*/
function parseParam(url) {const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 前面的字符串取出来
const paramsArr = paramsStr.split('&'); // 将字符串以 & 宰割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach(param => {if (/=/.test(param)) { // 解决有 value 的参数
let [key, val] = param.split('='); // 宰割 key 和 value
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则增加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果对象没有这个 key,创立 key 并设置值
paramsObj[key] = val;
}
} else { // 解决没有 value 的参数
paramsObj[param] = true;
}
})
return paramsObj;
}
用正则写一个依据 name 获取 cookie 中的值的办法
function getCookie(name) {var match = document.cookie.match(new RegExp('(^|)' + name + '=([^;]*)'));
if (match) return unescape(match[2]);
}
- 获取页面上的
cookie
能够应用document.cookie
这里获取到的是相似于这样的字符串:
'username=poetry; user-id=12345; user-roles=home, me, setting'
能够看到这么几个信息:
- 每一个 cookie 都是由
name=value
这样的模式存储的 - 每一项的结尾可能是一个空串
''
(比方username
的结尾其实就是), 也可能是一个空字符串' '
(比方user-id
的结尾就是) - 每一项用
";"
来辨别 - 如果某项中有多个值的时候,是用
","
来连贯的 (比方user-roles
的值) - 每一项的结尾可能是有
";"
的(比方username
的结尾),也可能是没有的 (比方user-roles
的结尾) - 所以咱们将这里的正则拆分一下:
'(^|)'
示意的就是获取每一项的结尾,因为咱们晓得如果^
不是放在[]
里的话就是示意结尾匹配。所以这里(^|)
的意思其实就被拆分为(^)
示意的匹配username
这种状况,它后面什么都没有是一个空串 (你能够把(^)
了解为^
它前面还有一个暗藏的''
);而|
示意的就是或者是一个" "
(为了匹配user-id
结尾的这种状况)+name+
这没什么好说的=([^;]*)
这里匹配的就是=
前面的值了,比方poetry
;刚刚说了^
要是放在[]
里的话就示意"除了 ^ 前面的内容都能匹配"
,也就是非的意思。所以这里([^;]*)
示意的是除了";"
这个字符串别的都匹配 (*
应该都晓得什么意思吧,匹配 0 次或屡次)- 有的大佬等号前面是这样写的
'=([^;]*)(;|$)'
,而最初为什么能够把'(;|$)'
给省略呢?因为其实最初一个cookie
项是没有';'
的,所以它能够合并到=([^;]*)
这一步。 - 最初获取到的
match
其实是一个长度为 4 的数组。比方:
[
"username=poetry;",
"","poetry",";"
]
- 第 0 项:全量
- 第 1 项:结尾
- 第 2 项:两头的值
- 第 3 项:结尾
所以咱们是要拿第 2 项 match[2]
的值。
- 为了避免获取到的值是
%xxx
这样的字符序列,须要用unescape()
办法解码。
树形构造转成列表(解决菜单)
[
{
id: 1,
text: '节点 1',
parentId: 0,
children: [
{
id:2,
text: '节点 1_1',
parentId:1
}
]
}
]
转成
[
{
id: 1,
text: '节点 1',
parentId: 0 // 这里用 0 示意为顶级节点
},
{
id: 2,
text: '节点 1_1',
parentId: 1 // 通过这个字段来确定子父级
}
...
]
实现代码如下:
function treeToList(data) {let res = [];
const dfs = (tree) => {tree.forEach((item) => {if (item.children) {dfs(item.children);
delete item.children;
}
res.push(item);
});
};
dfs(data);
return res;
}
解析 URL Params 为对象
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 后果
{ user: 'anonymous',
id: [123, 456], // 反复呈现的 key 要组装成数组,能被转成数字的就转成数字类型
city: '北京', // 中文需解码
enabled: true, // 未指定值得 key 约定为 true
}
*/
function parseParam(url) {const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 前面的字符串取出来
const paramsArr = paramsStr.split('&'); // 将字符串以 & 宰割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach(param => {if (/=/.test(param)) { // 解决有 value 的参数
let [key, val] = param.split('='); // 宰割 key 和 value
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则增加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果对象没有这个 key,创立 key 并设置值
paramsObj[key] = val;
}
} else { // 解决没有 value 的参数
paramsObj[param] = true;
}
})
return paramsObj;
}
对象数组列表转成树形构造(解决菜单)
[
{
id: 1,
text: '节点 1',
parentId: 0 // 这里用 0 示意为顶级节点
},
{
id: 2,
text: '节点 1_1',
parentId: 1 // 通过这个字段来确定子父级
}
...
]
转成
[
{
id: 1,
text: '节点 1',
parentId: 0,
children: [
{
id:2,
text: '节点 1_1',
parentId:1
}
]
}
]
实现代码如下:
function listToTree(data) {let temp = {};
let treeData = [];
for (let i = 0; i < data.length; i++) {temp[data[i].id] = data[i];
}
for (let i in temp) {if (+temp[i].parentId != 0) {if (!temp[temp[i].parentId].children) {temp[temp[i].parentId].children = [];}
temp[temp[i].parentId].children.push(temp[i]);
} else {treeData.push(temp[i]);
}
}
return treeData;
}
实现数组元素求和
- arr=[1,2,3,4,5,6,7,8,9,10],求和
let arr=[1,2,3,4,5,6,7,8,9,10]
let sum = arr.reduce((total,i) => total += i,0);
console.log(sum);
- arr=[1,2,3,[[4,5],6],7,8,9],求和
var = arr=[1,2,3,[[4,5],6],7,8,9]
let arr= arr.toString().split(',').reduce((total,i) => total += Number(i),0);
console.log(arr);
递归实现:
let arr = [1, 2, 3, 4, 5, 6]
function add(arr) {if (arr.length == 1) return arr[0]
return arr[0] + add(arr.slice(1))
}
console.log(add(arr)) // 21
实现 some 办法
Array.prototype.mySome=function(callback, context = window){
var len = this.length,
flag=false,
i = 0;
for(;i < len; i++){if(callback.apply(context, [this[i], i , this])){
flag=true;
break;
}
}
return flag;
}
// var flag=arr.mySome((v,index,arr)=>v.num>=10,obj)
// console.log(flag);
循环打印红黄绿
上面来看一道比拟典型的问题,通过这个问题来比照几种异步编程办法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯一直交替反复亮灯?
三个亮灯函数:
function red() {console.log('red');
}
function green() {console.log('green');
}
function yellow() {console.log('yellow');
}
这道题简单的中央在于 须要“交替反复”亮灯,而不是“亮完一次”就完结了。
(1)用 callback 实现
const task = (timer, light, callback) => {setTimeout(() => {if (light === 'red') {red()
}
else if (light === 'green') {green()
}
else if (light === 'yellow') {yellow()
}
callback()}, timer)
}
task(3000, 'red', () => {task(2000, 'green', () => {task(1000, 'yellow', Function.prototype)
})
})
这里存在一个 bug:代码只是实现了一次流程,执行后红黄绿灯别离只亮一次。该如何让它交替反复进行呢?
下面提到过递归,能够递归亮灯的一个周期:
const step = () => {task(3000, 'red', () => {task(2000, 'green', () => {task(1000, 'yellow', step)
})
})
}
step()
留神看黄灯亮的回调里又再次调用了 step 办法 以实现循环亮灯。
(2)用 promise 实现
const task = (timer, light) =>
new Promise((resolve, reject) => {setTimeout(() => {if (light === 'red') {red()
}
else if (light === 'green') {green()
}
else if (light === 'yellow') {yellow()
}
resolve()}, timer)
})
const step = () => {task(3000, 'red')
.then(() => task(2000, 'green'))
.then(() => task(2100, 'yellow'))
.then(step)
}
step()
这里将回调移除,在一次亮灯完结后,resolve 以后 promise,并仍然应用递归进行。
(3)用 async/await 实现
const taskRunner = async () => {await task(3000, 'red')
await task(2000, 'green')
await task(2100, 'yellow')
taskRunner()}
taskRunner()
实现 Event(event bus)
event bus 既是 node 中各个模块的基石,又是前端组件通信的依赖伎俩之一,同时波及了订阅 - 公布设计模式,是十分重要的根底。
简略版:
class EventEmeitter {constructor() {this._events = this._events || new Map(); // 贮存事件 / 回调键值对
this._maxListeners = this._maxListeners || 10; // 设立监听下限
}
}
// 触发名为 type 的事件
EventEmeitter.prototype.emit = function(type, ...args) {
let handler;
// 从贮存事件键值对的 this._events 中获取对应事件回调函数
handler = this._events.get(type);
if (args.length > 0) {handler.apply(this, args);
} else {handler.call(this);
}
return true;
};
// 监听名为 type 的事件
EventEmeitter.prototype.addListener = function(type, fn) {
// 将 type 事件以及对应的 fn 函数放入 this._events 中贮存
if (!this._events.get(type)) {this._events.set(type, fn);
}
};
面试版:
class EventEmeitter {constructor() {this._events = this._events || new Map(); // 贮存事件 / 回调键值对
this._maxListeners = this._maxListeners || 10; // 设立监听下限
}
}
// 触发名为 type 的事件
EventEmeitter.prototype.emit = function(type, ...args) {
let handler;
// 从贮存事件键值对的 this._events 中获取对应事件回调函数
handler = this._events.get(type);
if (args.length > 0) {handler.apply(this, args);
} else {handler.call(this);
}
return true;
};
// 监听名为 type 的事件
EventEmeitter.prototype.addListener = function(type, fn) {
// 将 type 事件以及对应的 fn 函数放入 this._events 中贮存
if (!this._events.get(type)) {this._events.set(type, fn);
}
};
// 触发名为 type 的事件
EventEmeitter.prototype.emit = function(type, ...args) {
let handler;
handler = this._events.get(type);
if (Array.isArray(handler)) {
// 如果是一个数组阐明有多个监听者, 须要顺次此触发外面的函数
for (let i = 0; i < handler.length; i++) {if (args.length > 0) {handler[i].apply(this, args);
} else {handler[i].call(this);
}
}
} else {
// 单个函数的状况咱们间接触发即可
if (args.length > 0) {handler.apply(this, args);
} else {handler.call(this);
}
}
return true;
};
// 监听名为 type 的事件
EventEmeitter.prototype.addListener = function(type, fn) {const handler = this._events.get(type); // 获取对应事件名称的函数清单
if (!handler) {this._events.set(type, fn);
} else if (handler && typeof handler === "function") {
// 如果 handler 是函数阐明只有一个监听者
this._events.set(type, [handler, fn]); // 多个监听者咱们须要用数组贮存
} else {handler.push(fn); // 曾经有多个监听者, 那么间接往数组里 push 函数即可
}
};
EventEmeitter.prototype.removeListener = function(type, fn) {const handler = this._events.get(type); // 获取对应事件名称的函数清单
// 如果是函数, 阐明只被监听了一次
if (handler && typeof handler === "function") {this._events.delete(type, fn);
} else {
let postion;
// 如果 handler 是数组, 阐明被监听屡次要找到对应的函数
for (let i = 0; i < handler.length; i++) {if (handler[i] === fn) {postion = i;} else {postion = -1;}
}
// 如果找到匹配的函数, 从数组中革除
if (postion !== -1) {
// 找到数组对应的地位, 间接革除此回调
handler.splice(postion, 1);
// 如果革除后只有一个函数, 那么勾销数组, 以函数模式保留
if (handler.length === 1) {this._events.set(type, handler[0]);
}
} else {return this;}
}
};
实现具体过程和思路见实现 event
手写 Object.create
思路:将传入的对象作为原型
function create(obj) {function F() {}
F.prototype = obj
return new F()}
实现迭代器生成函数
咱们说 迭代器对象 全凭 迭代器生成函数 帮咱们生成。在 ES6
中,实现一个迭代器生成函数并不是什么难事儿,因为 ES6 早帮咱们思考好了全套的解决方案,内置了贴心的 生成器(Generator
)供咱们应用:
// 编写一个迭代器生成函数
function *iteratorGenerator() {
yield '1 号选手'
yield '2 号选手'
yield '3 号选手'
}
const iterator = iteratorGenerator()
iterator.next()
iterator.next()
iterator.next()
丢进控制台,不负众望:
写一个生成器函数并没有什么难度,但在面试的过程中,面试官往往对生成器这种语法糖背地的实现逻辑更感兴趣。上面咱们要做的,不仅仅是写一个迭代器对象,而是用 ES5
去写一个可能生成迭代器对象的迭代器生成函数(解析在正文里):
// 定义生成器函数,入参是任意汇合
function iteratorGenerator(list) {
// idx 记录以后拜访的索引
var idx = 0
// len 记录传入汇合的长度
var len = list.length
return {
// 自定义 next 办法
next: function() {
// 如果索引还没有超出汇合长度,done 为 false
var done = idx >= len
// 如果 done 为 false,则能够持续取值
var value = !done ? list[idx++] : undefined
// 将以后值与遍历是否结束(done)返回
return {
done: done,
value: value
}
}
}
}
var iterator = iteratorGenerator(['1 号选手', '2 号选手', '3 号选手'])
iterator.next()
iterator.next()
iterator.next()
此处为了记录每次遍历的地位,咱们实现了一个闭包,借助自在变量来做咱们的迭代过程中的“游标”。
运行一下咱们自定义的迭代器,后果合乎预期:
应用 setTimeout 实现 setInterval
setInterval 的作用是每隔一段指定工夫执行一个函数,然而这个执行不是真的到了工夫立刻执行,它真正的作用是每隔一段时间将事件退出事件队列中去,只有当以后的执行栈为空的时候,能力去从事件队列中取出事件执行。所以可能会呈现这样的状况,就是以后执行栈执行的工夫很长,导致事件队列里边积攒多个定时器退出的事件,当执行栈完结的时候,这些事件会顺次执行,因而就不能到距离一段时间执行的成果。
针对 setInterval 的这个毛病,咱们能够应用 setTimeout 递归调用来模仿 setInterval,这样咱们就确保了只有一个事件完结了,咱们才会触发下一个定时器事件,这样解决了 setInterval 的问题。
实现思路是应用递归函数,一直地去执行 setTimeout 从而达到 setInterval 的成果
function mySetInterval(fn, timeout) {
// 控制器,管制定时器是否继续执行
var timer = {flag: true};
// 设置递归函数,模仿定时器执行。function interval() {if (timer.flag) {fn();
setTimeout(interval, timeout);
}
}
// 启动定时器
setTimeout(interval, timeout);
// 返回控制器
return timer;
}
JSONP
script 标签不遵循同源协定,能够用来进行 跨域申请,长处就是兼容性好但仅限于 GET 申请
const jsonp = ({url, params, callbackName}) => {const generateUrl = () => {
let dataSrc = '';
for (let key in params) {if (Object.prototype.hasOwnProperty.call(params, key)) {dataSrc += `${key}=${params[key]}&`;
}
}
dataSrc += `callback=${callbackName}`;
return `${url}?${dataSrc}`;
}
return new Promise((resolve, reject) => {const scriptEle = document.createElement('script');
scriptEle.src = generateUrl();
document.body.appendChild(scriptEle);
window[callbackName] = data => {resolve(data);
document.removeChild(scriptEle);
}
})
}