最近在温习基础知识,如:jsonp
,promise
,bind
,apply
,debounce
等。那经过什么来测试练习了,就想到了「百度搜索」功能知足上面的测试。html
jsonp 主要是用来解决跨域问题的。应用很是普遍,关于更多的解决跨域方案请看前端面试总结之:js跨域问题 前端
那jsonp
的原理是什么了? 好比咱们定义一个函数foo
,而后调用它git
// 定义
function foo() {
console.log('foo')
}
// 调用
foo()
复制代码
那咱们将调用foo()
的这段代码放在一个新建的js
文件,好比a.js
而后经过script
标签引入a.js
了github
// a.js
foo()
复制代码
function foo() {
console.log('foo')
}
<script src="./a.js"></srcipt>
复制代码
jsonp
原理与之相似:
咱们在本地定义好一个函数,如jsonp_1234565
,而后将这个函数名经过特定标识符如cb=jsonp_1234565
经过script
的src
属性去请求一个js
资源(一个get
请求),即动态建立script
标签。如:<script src="https://www.baidu.com?a=1&b=2&cb=jsonp_1234565"></script>
后台经过cb
这个特定标识符获得前端定义的函数名为jsonp_1234565
而后将前端真正要的的数据放在jsonp_1234565
的参数里,并将这个函数返回给前端如:jsonp_1234565({status: 0, data: {...}})
代码以下面试
function jsonp({url = '', data = {}, cb='cb'} = {}) {
if (!url) return
// myPromise 请看下面实现,能够用成功回调的,由于学习特地用了Promise
return myPromise((resolve, reject) => {
const cbFn = `jsonp_${Date.now()}` // 定义函数名
data[cb] = cbFn // 将函数名放在`cb`标识符里
const oHead = document.querySelector('head')
const oScript = document.create('script')
const src = `${url}?${data2Url(data)}`
oScript.src = src
oHead.appendChild(oScript) // 将script标签插入head,以发送get请求
// 定义函数,后台返回就调用
window[cbFn] = function(res) {
res ? resolve(res) : reject('error')
// 若是不用Promise用回调的话只需在参数中加个success参数而后调用便可
// success && success(res)
oHead.removeChild(oScript) // 请求回来以后就没用了。若是不删除,每次请求以后就会建立一个script标签,致使页面不少的script标签,因此将它删除。用完了就扔,感受有点过河拆桥的意思
window[cbFn] = null
}
})
}
function data2Url(data) {
return Object.keys(data).reduce((acc, cur) => {
acc.push(`${cur}=${data[cur]}`)
return acc
}, []).join('&')
}
// 能够先把myPromise改为原生的Promise测试百度搜索的接口
jsonp({
url: 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
data: {
wd: 'a'
},
cb: 'cb'
}).then(res => {
console.log(res)
})
复制代码
测试建议用 EGOIST开源的codepan.net/ 相似于JSBin/CodePen/JSFiddle
json
简易版的Promise
实现,没有遵循A+
规范,查看原文JavaScript Promises - Understand JavaScript Promises by Building a Simple Promise Example跨域
new Promise((resolve, reject) => {
// 一系列操做 伪代码
if (true) {
resolve(res)
} else {
reject(err)
}
})
.then(fn1)
.then(fn2)
...
.catch(handleError)
复制代码
大体意思就是Promise
这个类接受一个函数做为参数,这个参数函数又接受两个函数做为参数
new Promise
这个实例有then
和catch
这两个方法,then
和catch
又都接受函数做为参数,而且能够链式调用数组
核心思路就是定义个数组promiseChianFn
用来装then
的回调函数,then
一次,就往promiseChianFn
push
一条then
的回调函数,当在调用resolve
函数的时候,就循环执行promiseChianFn
的函数promise
class myPromise {
constructor(excuteFn) {
this.promiseChianFn = []
this.handleError = () => {}
// mybind 请看下面实现
this._resolve = this._resolve.mybind(this)
this._reject = this._reject.mybind(this)
// 当即执行
excuteFn(this._resolve, this._reject)
}
then(fn) {
this.promiseChianFn.push(fn)
return this // 原生Promise返回的是一个新的Promise
}
catch(handleError) {
this.handleError = handleError
return this
}
_resolve(val) {
try {
let storeVal = val
// 循环执行,并把第一个函数执行的返回值赋值给storeVal 共下个函数接收 如:
/** * .then(res => { * renturn 1 * }) * .then(res => { * console.log(res) // 1 * }) * */
this.promiseChianFn.forEach(fn => {
storeVal = fn(storeVal)
})
} catch(err) {
this.promiseChianFn = []
this._reject(err)
}
}
_reject(err) {
this.handleError(err)
}
}
// 如今能够用myPromise 测试上面的jsonp了
复制代码
call
和apply
都是用来改变函数的上下文里面的this的,即改变this
指向。
注意:上下文包含 VO(variable Object--变量对象)
,做用域链
和this
这三个东西,具体请看js引擎的执行过程(一)性能优化
// 将 foo里面的上下文指向 ctx
foo.call(ctx, 1,2,3)
foo.apply(ctx, [1,2,3])
复制代码
call
和apply
的原理就是方法借用
在知乎上面看到一篇文章的比喻 猫吃鱼,狗吃肉
那猫要吃肉,就借用狗吃肉的方法 即 狗.吃肉.call(猫)
那狗要吃鱼,就借用猫吃鱼的方法 即 猫.吃鱼.call(狗)
// fn.call(ctx) 既然是方法借用,那就给ctx添加一个该方法就能够了
Function.prototype.myapply = function(ctx, args = []) {
const hash = Date.now() // 用时间戳是防止 ctx 上面的属性冲突
ctx[hash] = this // 给 ctx 添加一个方法,this 就是 fn
const res = ctx[hash](...args)
delete ctx[hash] // 过河拆桥
return res
}
// call 的话,只需将 args = [] 改成 ...args 便可
// 测试
const a = {
name: 'a',
getName() {
console.log(this.name)
}
}
const b = {
name: 'b'
}
a.getName() // a
a.getName.myapply(b) // b
复制代码
bind 返回一个新函数,并永久改变 this 指向,返回的新函数不管以后再怎么call,apply,bind都不会改变 this 指向了,并有偏函数的效果 若是要考虑 New
的状况请参照MDN
// fn2 = fn.bind(ctx)
Function.prototype.mybind = function(ctx, ...args1) {
const _this = this
return function(...args2) {
// 永远指向 ctx
return _this.myapply(ctx, args1.concat(args2))
}
}
// 测试
const fn = a.getName.mybind(b)
fn() // b
const fn2 = fn.bind(a)
fn2() // b
复制代码
「百度搜索」并无加入 debounce
,咱们能够给他加个 debounce
看下效果
debounce
(防抖) 和 throttle
(节流)主要是用来作性能优化
debounce
就像压弹簧,只要手不松开,弹簧就不会弹起来,常见应用场景就是input输入框,咱们在中止输入后才去作相关操做
throttle
就像拧紧水龙头,让水龙头隔一秒钟滴一滴水,常见应用场景为页面滚动优化
function debounce(cb, delay = 300) {
let timer
return function(...args) {
timer && clearTimeout(timer)
timer = setTimeout(() => {
cb && cb.apply(this, args)
}, delay)
}
}
复制代码
接下来咱们模拟「百度搜索」加上debounce
想传 GIF 传不了, 请上 codepan.net/ 测试
所有代码
<!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>Document</title>
</head>
<body>
<input type="text">
</body>
</html>
复制代码
function jsonp({url = '', data = {}, cb = 'cb'} = {}) {
return new myPromise((resolve, reject) => {
if (!url) return
const cbFn = `jsonp_${Date.now()}`
data[cb] = cbFn
const oHead = document.querySelector('head')
const oScript = document.createElement('script')
const src = `${url}?${data2Url(data)}`
oScript.src = src
oHead.appendChild(oScript)
window[cbFn] = function(res) {
resolve(res)
oHead.removeChild(oScript)
window[cbFn] = null
}
})
}
function data2Url(data) {
return Object.keys(data).reduce((acc, cur) => {
acc.push(`${cur}=${data[cur]}`)
return acc
}, []).join('&')
}
class myPromise {
constructor(excuteFn) {
this.promiseChainFn = []
this.handleError = () => {}
this._resolve = this._resolve.myBind(this)
this._reject = this._reject.myBind(this)
excuteFn(this._resolve, this._reject)
}
then(cb) {
this.promiseChainFn.push(cb)
return this
}
catch(handleError) {
this.handleError = handleError
return this
}
_resolve(res) {
try {
let storeVal = res
this.promiseChainFn.forEach(fn => {
storeVal = fn(storeVal)
})
} catch(e) {
this.promiseChainFn = []
this._reject(e)
}
}
_reject(err) {
return this.handleError(err)
}
}
Function.prototype.myApply = function(ctx, args = []) {
const hash = Date.now()
ctx[hash] = this
const res = ctx[hash](...args)
delete ctx[hash]
return res
}
Function.prototype.myBind = function(ctx, ...args1) {
const _this = this
return function(...args2) {
return _this.myApply(ctx, args1.concat(args2))
}
}
function debounce(cb, delay = 300) {
let timer
return function(...args) {
timer && clearTimeout(timer)
timer = setTimeout(() => {
cb && cb.myApply(this, args)
}, delay)
}
}
const oInput = document.querySelector('input')
oInput.oninput = debounce(handleInput, 1000)
function handleInput(v) {
jsonp({
url: 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
data: { wd: this.value},
cb: 'cb'
}).then(res => {
console.log(res)
return 1
}).then(res => {
console.log(res)
return 2
}).then(res => {
console.log(res)
}).catch(function a(err) {
console.log(err)
})
}
复制代码