ajax 可以无需刷新页面与服务器进行通讯,允许根据用户事件来更新部分页面内容。
readyStatus
的值:
使用 promise 封装 ajax:
function transdata(data) {
let arr = []
for (let key in data) {
arr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
}
return arr.join('&')
}
function Ajax(option) {
return new Promise((resolve, reject) => {
let xhr, timer
let data = transdata(option.data)
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest()
} else {
// 兼容低版本ie浏览器
xhr = new ActiveXObject("Microsoft.XMLHTTP")
}
if (option.type.toLowerCase() === "get") {
// get 方法
xhr.open(option.type, option.url + "?" + data, true)
xhr.send()
} else {
// post 方法
xhr.open(option.type, option.url, true)
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
xhr.send(data)
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
clearInterval(timer)
if (xhr.status >= 200 && xhr.status <= 300 || xhr.status === 304) {
resolve(xhr.response)
} else {
reject(new Error("error"))
}
}
}
// 超时处理
if (option.timout) {
timer = setInterval(function () {
console.log("请求超时")
xhr.abort()
clearInterval(timer)
}, option.timeout)
}
})
}
使用:
Ajax({
type: "post",
url: "/api/v1/lists",
data: {
"_gid": "1"
},
timeout: 3000
}).then(res => {
cb(res)
})
XMLHttpRequest 是一个设计粗糙的 API,不符合关注分离(Separation of Concerns)的原则,配置和调用方式非常混乱,而且基于事件的异步模型写起来也没有现代的 Promise,generator/yield,async/await 友好。
Fetch 基于标准 Promise 实现,支持 async/await,脱离了XHR,是ES规范里新的实现方式。
fetch(url).then(response => response.json()).then(data => console.log(data)).catch(e => console.log("Oops, error", e))
缺点:
fetch(url, {credentials: 'include'})
。函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
function curry(fn, args) {
// 获取函数需要的参数长度
let length = fn.length;
let args = args || [];
return function() {
let newArgs = args.concat(Array.prototype.slice.call(arguments));
// 判断参数的长度是否已经满足函数所需参数的长度
if (newArgs.length >= length) {
// 如果满足,执行函数
return fn.apply(this, newArgs);
} else {
// 如果不满足,递归返回科里化的函数,等待参数的传入
return curry.call(this, fn, newArgs);
}
}
};
// es6 实现
function curry(fn, ...args) {
return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
应用:参数复用
function url(protocol, hostname, pathname) {
return `${protocol}${hostname}${pathname}`;
}
let curry_uri = curry(url)
const base_uri = curry_uri('https://', 'www.github.com')
const uri1 = base_uri('/a/') //'https://www.github.com/a/'
const uri2 = base_uri('/b/') //'https://www.github.com/b/'
const uri3 = base_uri('/c/') //'https://www.github.com/c/'
经典面试题:实现 add 方法
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
下面这个只能两个数据相加
function add(a, b) {
return a + b
}
let curry_add = curry(add)
curry_add(1)(2) // 3;
改进后全部参数可以相加
function add() {
// 将传入的不定参数转为数组对象
let args = Array.prototype.slice.call(arguments);
// 递归:内部函数里面进行自己调用自己
// 当 add 函数不断调用时,把第 N+1 个括号的参数加入到第 N 个括号的参数里面
let inner = function() {
args.push(...arguments);
return inner;
}
inner.toString = function() {
// args 里的值不断累加
return args.reduce(function(prev, cur) {
return prev + cur;
});
};
return inner;
}
add(1)(2)(3,4).toString() // 10
add(1)(2)(3)(4).toString() // 10
防抖
原理:在事件触发 n 秒后才执行,如果在一个事件触发的 n 秒内又触发了这个事件,那就以新的事件的时间为准,n 秒后才执行。
简易版防抖实现:
function debounce(fn, delay = 1000) {
let time = null
function _debounce() {
var context = this
var args = arguments
if (time !== null) {
clearTimeout(time)
}
time = setTimeout(() => {func.apply(context, args)}, delay)
}
return _debounce
}
不希望非要等到事件停止触发后才执行,希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。
function debounce(func, wait, immediate) {
var timeout, result
var debounced = function () {
var context = this
var args = arguments
if (timeout) clearTimeout(timeout)
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout
timeout = setTimeout(function(){
timeout = null
}, wait)
if (callNow) result = func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait)
}
return result
}
// 取消防抖
debounced.cancel = function() {
clearTimeout(timeout)
timeout = null
};
return debounced
}
//使用
window.onresize = debounce(XXXfunction, 10000, true);
节流
持续触发事件,每隔一段时间,只执行一次事件。可分为首次是否执行以及结束后是否执行。
简易版节流实现:
function throttle(fn, interval) {
let lastTime = 0
const _throttle = function(...args) {
const nowTime = new Date().getTime()
const remainTime = nowTime - lastTime
if (remainTime - interval >= 0) {
fn.apply(this, args)
lastTime = nowTime
}
}
return _throttle
}
options参数 :
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = new Date().getTime();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = null;
}
return throttled;
}
存在各种问题的方法:
let b = JSON.parse(JSON.stringify(a))
let b= [].concat(a)
let b = a.slice()
,只有一级属性值的数组对象Object.assign()
,当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝let b = [...a]
递归拷贝
const deepClone = (tar, map = new WeakMap) => {
if (!(typeof tar === 'object' && tar != null)) return tar
if (map.has(tar)) return map.get(tar)
if (tar instanceof Map || tar instanceof Set) {
const retObj = new tar.constructor()
map.set(tar, retObj) // 在调用 deepClone 之前将 tar-retObj 记录到 map 中, 防止循环引用
retObj.add ?
tar.forEach(val => retObj.add(deepClone(val, map))) :
tar.forEach((val, key) => retObj.set(key, deepClone(val, map)))
} else if (tar instanceof Date || tar instanceof RegExp) {
const retObj = new tar.constructor(tar)
map.set(tar, retObj)
} else { // 对象或数组
const retObj = Object.create(Reflect.getPrototypeOf(tar), Object.getOwnPropertyDescriptors(tar))
map.set(tar, retObj)
Reflect.ownKeys(tar).forEach(key => {
retObj[key] = deepClone(tar[key], map)
})
}
return retObj
}
function unique(arr){
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){
arr.splice(j,1);
j--;
}
}
}
return arr;
}
[NaN].indexOf(NaN) // -1
。function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array.indexOf(arr[i]) === -1) {
array.push(arr[i])
}
}
return array;
}
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array =[];
for(var i = 0; i < arr.length; i++) {
if( !array.includes( arr[i]) ) {
array.push(arr[i]);
}
}
return array
}
function unique(arr){
return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[]);
}
function unique(arr) {
var obj = {};
return arr.filter(function(item, index, arr){
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
function unique(arr) {
return arr.filter(function(item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;
});
}
function unique (arr) {
return Array.from(new Set(arr))
}
function unique (arr) {
return [...new Set(arr)]
}
function arrayNonRepeatfy(arr) {
let map = new Map();
let array = new Array(); // 数组用于返回结果
for (let i = 0; i < arr.length; i++) {
if(map.has(arr[i])) { // 如果有该key值
map.set(arr[i], true);
} else {
map.set(arr[i], false); // 如果没有该key值
array.push(arr[i]);
}
}
return array ;
}
call
Function.prototype.myCall = function() {
const [context, ...args] = [...arguments]
context.fn = this
const res = context.fn(args)
delete context.fn
return res
}
let obj = {
value: 1
}
function bar(name, age) {
console.log(this.value);
return {
value: this.value,
name: name,
age: age
}
}
console.log(bar.myCall(obj, 'kevin', 18));
// 1
// Object {
// value: 1,
// name: 'kevin',
// age: 18
// }
apply
Function.prototype.myApply = function(context, arr) {
context.fn = this
let res = !args ? context.fn() : context.fn(...args)
delete context.fn
return res
}
console.log(bar.apply(obj, ['kevin', 18]))
// 1
// Object {
// value: 1,
// name: 'kevin',
// age: 18
// }
bind
Function.prototype.myBind = function(context) {
let self = this
let args = Array.prototype.slice.call(arguments, 1)
let fNOP = function () {}
let fBound = function () {
let 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 一个函数时:
function myNew() {
let constr = Array.prototype.shift.call(arguments)
// 1. 创建一个新对象
let obj = Object.create(constr.prototype)
// 2. 将构造函数的作用域赋给新对象,并执行构造函数中的代码
let result = constr.apply(obj, arguments)
// 如果构造函数有返回值,则返回;否则,就会默认返回新对象。
return result instanceof Object? result : obj
}
一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知值的代理。它让你能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。
Promise 包含3种状态:
pending
):初始状态,既没有被兑现,也没有被拒绝。fulfilled
):意味着操作成功完成。rejected
):意味着操作失败。
基础实例:
let myFirstPromise = new Promise((resolve, reject) => {
// We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
// In this example, we use setTimeout(...) to simulate async code.
// In reality, you will probably be using something like XHR or an HTML5 API.
setTimeout( function() {
resolve("Success!") // Yay! Everything went well!
}, 250)
})
myFirstPromise.then((successMessage) => {
// successMessage is whatever we passed in the resolve(...) function above.
// It doesn't have to be a string, but if it is only a succeed message, it probably will be.
console.log("Yay! " + successMessage)
}).catch(err => { console.log(err) });
Promise 的实现
class MyPromise {
constructor(executor) {
this.state = 'pending'
this.value = null
this.reason = null
this.callbacks = []
const resolve = value => {
if (this.state !== 'pending') return
this.state = 'fulfilled'
this.value = value
this.callbacks.forEach(callback => callback.fulfilled(value))
}
const reject = reason => {
if (this.state !== 'pending') return
this.state = 'rejected'
this.reason = reason
this.callbacks.forEach(callback => callback.rejected(reason))
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
if(typeof onFulfilled !== 'function') onFulfilled = value => value
if(typeof onRejected !== 'function') onRejected = reason => { throw reason }
let promise = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
this.resolvePromise(promise, onFulfilled(this.value), resolve, reject)
} catch (error) {
reject(error)
}
});
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
this.resolvePromise(promise, onRejected(this.reason), resolve, reject)
} catch (error) {
reject(error)
}
})
}
if (this.state === 'pending') {
this.callbacks.push({
fulfilled: () => {
setTimeout(() => {
try {
this.resolvePromise(promise, onFulfilled(this.value), resolve, reject)
} catch (error) {
reject(error)
}
})
},
rejected: () => {
setTimeout(() => {
try {
this.resolvePromise(promise, onRejected(this.reason), resolve, reject)
} catch (error) {
reject(error)
}
})
}
})
}
})
return promise
}
resolvePromise(promise, result, resolve, reject) {
if (promise === result) reject(new TypeError('Chaining cycle detected for promise'))
if (result && typeof result === 'object' || typeof result === 'function') {
let called
try {
let then = result.then
if (typeof then === 'function') {
then.call(result, value => {
if (called) return
called = true
this.resolvePromise(promise, value, resolve, reject)
}, reason => {
if (called) return
called = true
reject(reason)
})
} else {
if (called) return
called = true
resolve(result)
}
} catch (error) {
if (called) return
called = true
reject(error)
}
} else {
resolve(result)
}
}
}
ajax 请求受同源策略影响,不允许进行跨域请求,而 script 标签 src 属性中的链接却可以访问跨域的 js 脚本,利用这个特性,服务端不再返回 JSON 格式的数据,而是返回一段调用某个函数的 js 代码,在 src 中进行了调用,这样实现了跨域。
因为 jsonp 发送的并不是 ajax 请求,而是动态创建script标签。script 标签是没有同源限制的,把 script 标签的 src 指向请求的服务端地址。
缺点:
//手写jsonp,函数中传入参数,url,params,callbackName
function jsonp({ url, params, callbackName }) {
//定义一个函数拼接url字符串
function getUrl() {
let dataSrc = ''
for (let key in params) {
if(params.hasOwnProperty(key)) {
dataSrc += `${key}=${params[key]}&`
}
}
dataSrc+=`callBack=${callbackName}`
return `${url}?${dataSrc}`
}
return new Promise((resolve,reject)=>{
var scriptEle = document.createElement('script')
scriptEle.src = getUrl
document.body.appendChild(scriptEle)
window[callbackName] = data => {
resolve(data)
document.removeChild(scriptEle)
}
})
}
async 函数是使用async关键字声明的函数。async 函数是 AsyncFunction 构造函数的实例,并且其中允许使用 await 关键字。async 和 await 关键字让我们可以用一种更简洁的方式写出基于 Promise 的异步行为,而无需刻意地链式调用 promise。本质是 Promise 的语法糖,用于优化 then 链式调用。
async 函数一定会返回一个 promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中:
async function foo() {
await 1;
}
// 等价于如下写法
function foo() {
return Promise.resolve(1);
}
async 函数的函数体可以被看作是由 0 个或者多个 await 表达式分割开来的。从第一行代码直到(并包括)第一个 await 表达式(如果有的话)都是同步运行的。这样的话,一个不含 await 表达式的 async 函数是会同步运行的。然而,如果函数体内有一个 await 表达式,async 函数就一定会异步执行。
async function foo() {
await 1;
}
// 等价于如下写法
function foo() {
return Promise.resolve(1).then(() => undefined);
}
await 必须在 async 函数中使用:
async function() {
const user_info = await getUserInfo(params)
}
参考:https://juejin.cn/post/6844903764202094606?utm_source=gold_browser_extension#heading-0
Node 中的 events 模块:
const EventEmitter = require('events')
const ev = new EventEmitter()
// on
ev.on('事件1', () => {
console.log('事件1执行了')
})
// emit触发
ev.emit('事件1')
// once 首次触发
ev.once('事件1', () => {
console.log('事件1执行了')
})
// 移除
ev.off('事件1', callBackFn)
Events 模块本质上是观察者模式的实现,所谓观察者模式就是:它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。观察者模式有对应的观察者以及被观察的对象,在 Events 模块中,对应的实现就是 on 和 emit 函数。
class EventEmitter{
constructor(){
// 事件监听函数保存的地方
this.events={};
}
on(eventName,listener){
if (this.events[eventName]) {
this.events[eventName].push(listener);
} else {
// 如果没有保存过,将回调函数保存为数组
this.events[eventName] = [listener];
}
}
emit(eventName,...rest){
// emit触发事件,把回调函数拉出来执行
this.events[eventName] && this.events[eventName].forEach(listener => listener.apply(this,rest))
}
}