某一天 我只是想模擬實現一下百度下拉搜索 結果發現小小的一點代碼 居然可以有這么多的知識點 涉及到JSONP 以及 事件委托 還有如何實現在點擊頁面其他地方的時候實現下拉框的收起...生命在于折騰
(●'?'●) 不用后臺請求 不用ajax請求 直接使用百度的jsonp請求地址進行請求就可以啦~~~(●'?'●)
1 先看結果
2 涉及知識點
- JSONP 是什么???
- 事件委托的使用。??
- 點擊其他位置實現下拉框收起。??
3 知識點詳解
- JSONP
我們都知道 在使用ajax請求的時候 跨域這種情況是要另外處理的 那JSONP就是可以解決這種問題的。具體是使用什么原理呢?你想一下,平時我們在頁面中引入script標簽之后,script標簽也有相關的url發起請求,而且可以請求任何有效地址的數據,正是因為這個漏洞,我們可以發起跨域請求。那么機智的程序員們,就這么想,我可不可以將需要用到的JSON 數據填充回來.所以就有了JSONP。
JSONP執行之后,會返回包含json編碼數據的響應體,也就是會自動執行。
當通過<script>元素調用數據時,響應內容必須是用js函數名和圓括號包裹起來,而不是發這樣的json數據。
[1,2,{"name": "katherine"}]
它會發送一個這樣被js包裹后的JSON響應
handleResponse (
[1,2,{"name": "katherine"}]
)
所以我們可以事先在js代碼中定義一個類似handleResponse的函數,這樣我們就可以在響應獲取到這些JSON響應之后,通過事先定義好的函數,對傳過來的數據為所欲為了。
其實說簡單一點,JSONP就是在你向頁面填充某個url的<script>標簽之后,返回一個可以執行的函數名,同時是有帶參數的函數名。所以只要我們事先定義好這個函數的相關操作就可以了。
當然,如果這個函數名是定死的話,那就不好玩了,所以一般支持JSONP的服務不會強制指定客戶端必須實現的回調函數名,如果每次函數名都一定是handleResponse
,豈不是很受限。所以,一般會用查詢參數的值,允許客戶端指定一個函數名,然后使用函數名去填充響應。也就是說我們在這個script
標簽的url給定地址中填充類似 ?cb=callbackname
來告訴服務端,我們這邊客戶端需要什么樣的函數名。
這里推薦一個覺得講得很好的地址:談談JSON和JSONP
后面我在實際應用中也會說到。
- 事件委托
在這個例子中,需要實現一個就是,當有下拉熱詞出現的時候,其實是包裹來一個ul內部的,而這些填充的很多的li,每一個li都需要綁定一個click事件,用來干什么呢?當點擊其中一個li的時候,需要將這個li的內容填充到搜索輸入框。
因為這些li是動態生成的,不可能每一個li都去綁定點擊事件。而且這樣對于性能消耗也很大,所以,可以使用事件冒泡的特點,將點擊件綁定在父級元素ul上。
然后通過事件的屬性,event.target
來獲取當前真正被點擊的元素,然后就可以獲取這個被點擊元素的內容啦。??
因為篇幅有限 我也沒有講得很詳細 傳送門: 事件委托和事件代理
4 實際代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<body>
<div id="search-arr"><input type="text" id="searchInput" type="search" placeholder="search"><ul id="selectItem"></ul></div>
</body>
</head>
</html>
<script>
// 執行JSONP操作的函數,設置添加script.
// 這個看不懂的話 建議參考一下js權威指南 第507頁
function getJSON (url,callback) {
// 因為百度的那個請求的地址是 https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=迷糊查詢參數名&cb=回調函數名
// 所以這里會每次給getJSON 函數添加一個
var cbnum = "cb" + getJSON.counter++ // 假設這里就是cb1
var cbname = "getJSON." + cbnum // 這里的函數名字就是 getJSON.cb1
url+="&cb="+ cbname // 將這個回調函數名添加 到url后面
var script = document.createElement("script") // 創建一個script標簽
getJSON[cbnum] = function(response) { // 給這個getJSON函數添加一個getJSON.cb1的函數屬性。這也就是我們的回調函數
try {
callbackfn(response) // 對獲取到的response數據進行處理的回調函數
}
finally { // 記得哦 執行完函數之后 要刪除這個添加的屬性 同時刪除這個script標簽 不然查詢次數增加之后 會越來越多
delete getJSON[cbname]
script.parentNode.removeChild(script)
}
}
script.src = url // 添加url屬性
document.body.appendChild(script)
}
getJSON.counter = 0 // 初始化一個counter的值
// 對獲取到的JSONP數據進行處理的回調函數
function callbackfn(data) {
var selectList = document.getElementById("selectItem")
// 可以通過視頻中看到,數據的結構中的關鍵字存在data.s中
if (data.s.length !== 0) {
let newList = data.s.map((item) => `<li><a target="_blank" >${item}</a></li>`)
let newStr = newList.join('')
selectList.innerHTML = newStr
selectList.style.display = 'block'
}
}
window.onload = function () {
var target = document.getElementById("searchInput")
var selectList = document.getElementById("selectItem")
AutoCompleteInput()
// 定義一個隱藏下拉單的函數
function hideList (selectList) {
selectList.innerHTML = ''
selectList.style.display = 'none'
}
// 對輸入框進行監聽 數據變化就執行getJSONP函數,請求新的關鍵字
function AutoCompleteInput() {
target.addEventListener('input', () => {
let oldValue = ''
let newValue = target.value
if (newValue !== '' && newValue !== oldValue) {
getJSON('https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd='+(newValue))
} else {
hideList(selectList)
}
oldValue = target.value
})
// 點擊除了下拉框和輸入框之外的其他地方 要隱藏下拉框
document.addEventListener('click', (e) => {
// 這里使用了contains,contaisn可以知道某個元素是否是其子元素
if(selectList.contains(e.target)||e.target.getAttribute('id') !== 'searchInput') {
hideList(selectList)
}
})
target.addEventListener('keydown', () => {
hideList(selectList)
})
// 事件代理部分代碼
selectList.addEventListener('click', (e) => {
e.preventDefault() // 我為了防止點擊之后跳轉到搜索頁面,所以寫了這句,你可以去掉,就可以跳轉到搜索結果頁面了
let selectValue = ''
if (e.target && e.target.nodeName == "A") {
selectValue = e.target.innerHTML
} else {
selectValue = e.target.getElementByTagName('a').innerHTML
}
target.value = selectValue
})
}
}
</script>
<style>
* {
padding:0;
margin:0;
box-sizing:border-box;
}
#search-arr {
position:relative;
width:200px;
margin:50px auto;
}
#searchInput {
outline: none;
width:200px;
height:30px;
border:1px solid rgba(109,207,246,.5);
border-radius:25px;
padding:10px 10px 10px 20px;
background:#ededed;
transition:all ease .4s;
}
#searchInput:focus {
/* width:250px; */
box-shadow: 0 0 5px rgba(109,207,246,.5);
background:white;
}
ul#selectItem {
display: none;
position:absolute;
top:31px;
width:100%;
border:1px solid gray;
border-top:none;
}
li {
width:100%;
list-style-type:none;
background:white;
color:black;
/* padding:10px; */
cursor:pointer;
}
li:hover {
background: #ededed;
}
a {
display: inline-block;
text-decoration: none;
color:black;
width:100%;
padding:1px 5px;
}
</style>
5 遺留問題
1 JSONP請求的錯誤處理: 我想了這個問題,像我們在執行ajax請求的時候,是可以有相應狀態碼告訴我們發生錯誤的,404啊,之類的。但是這個JSONP請求我們要怎么知道錯誤呢?怎么處理錯誤呢?
2 JSONP請求之后動態添加script標簽,需要移除這些新添加的script標簽,那是不是也會有漏洞或者會有問題呢?
??然后我驚訝地發現,有人的問題和我一樣,我就。。。。 遺留問題
我看了答案還不是很理解。
會的歡迎評論喲~~~??