1. 程式人生 > 實用技巧 >2020-11-09

2020-11-09

javascript

1. 原型/建構函式/例項
原型(prototype):一個簡單的物件,用於實現物件的 屬性繼承。可以簡單的理解成物件的爹。
每個JavaScript物件中都包含一個 __proto__的屬性指向它爹(該物件的原型),可用 obj.__proto__訪問。

建構函式:可以通過new來新建一個物件的函式。

例項:通過建構函式和new創建出來的物件,便是例項。例項通過__proto__指向原型,通過constructor指向建構函式。
例如:
//例項
const instance = new Object();
//原型
const prototype = Object.prototype;
三者關係:
例項.__proto__ === 原型
原型.constructor === 建構函式
建構函式.prototype === 原型

2、原型鏈
原型鏈是由原型物件組成,每個物件都有 __proto__屬性,指向了建立該物件的建構函式的原型,__proto__將物件連線起來組成了原型
鏈。

2.1、屬性查詢機制:當查詢物件的屬性時,如果例項物件自身不存在該屬性,則沿著原型鏈往上一級查詢,找到時則輸出,不存在時,則繼續
沿著原型鏈往上一級查詢,直至最頂級的原型物件 Object.prototype,如還是沒找到,則輸出 undefined 。
2.2、屬性修改機制:只會修改例項物件本身的屬性,如果不存在,則進行新增該屬性,如果需要修改原型的屬性時,則可以用:
b.prototype.x = 2;但是這樣會造成所有繼承該物件的例項的屬性發生改變。

3、執行上下文(EC)
執行上下文可以理解為一個物件:
它包含三個部分:變數物件(VO) , 作用域鏈(詞法作用域) , this指向 。
它的型別:全域性執行上下文 , 函式執行上下文 , eval執行上下文 。
程式碼執行過程:
建立全域性上下文(global EC) , 全域性執行上下文(caller)逐行 自上而下 執行。遇到函式時,函式執行上下文被 push 到執行棧頂層。
函式執行上下文被啟用,成為 active EC,開始執行函式中的程式碼,caller被掛起。
函式執行完後,callee 被 pop移除出執行棧,控制權交還全域性上下文(caller),繼續執行。

變數物件:它是執行上下文中的一部分,可以抽象為一種 資料作用域,其實也可以理解為就是 一個簡單的物件,它儲存著該執行上下文中的所有變數
和函式宣告(不包含函式表示式)。
活動物件(AO):當變數物件所處的上下文為 active EC 時,稱為 活動物件。
作用域:執行上下文中還包含作用域鏈。作用域可以理解為該上下文中宣告的 變數和宣告的作用範圍。可分為塊級作用域和函式作用域。
特性:
宣告提前:一個宣告在函式體內都是可見的,函式優於變數。
非匿名自執行函式,函式變數為 只讀 狀態,無法修改。
作用域鏈:可以理解為一組物件列表,包含父級和自身的變數物件。因此我們可以通過作用域鏈訪問到父級裡宣告的變數或者函式。
由兩部分組成:[[scope]]屬性:指向父級變數物件和作用域鏈,也就是包含了父級的[[scope]]和AO。
AO:自身活動物件。

5、閉包
它屬於一種特殊的作用域,稱為 靜態作用域。他的定義理解為:父函式被銷燬的情況下,返回出的子函式的[[scope]]中仍然保留著父級的單變數
物件和作用域鏈,因此可以繼續訪問到父級的變數物件,這樣的函式稱為閉包。
閉包會產生一個很經典的問題:
多個子函式[[scope]]都是同時指向父級,是完全共享的。因此當父級的變數物件被修改時,所有子函式都受到影響。
解決:
變數可以通過函式引數的形式傳入,避免使用預設的[[scope]]向上查詢。
使用setTimeout包裹,通過第三個引數傳入。
使用 塊級作用域 ,讓變數成為自己上下文的屬性,避免共享。

6、script引入方式
html靜態<script>引入。
js動態插入<script>
<script defer>:非同步載入,元素解析完成後執行
<script async>:非同步載入,但執行時會阻塞元素渲染

7、物件的拷貝
淺拷貝:以賦值的形式拷貝物件,仍指向同一個地址,修改時原物件也會受到影響。
Object.assign , 展開運算子(...)
深拷貝:完全拷貝一個新物件,修改時原物件不再收到任何影響。
JSON.parse(JSON.stringify()):效能最快,具有迴圈引用的物件時,報錯。 當值為函式,undefined,symbol時,無法拷貝。
遞迴進行逐一賦值。

8、new運算子的執行過程
新生成一個物件
連結到原型: obj.__proto__ = Con.prototype
繫結 this: apply
返回新物件(如果建構函式有自己 return時,則返回該值)

9、instanceof 原理
能在例項的 原型物件鏈中找到該建構函式的prototype屬性所指向的原型物件,就返回true。
//__proto__:代表原型物件鏈
instance.[__proto__...] === instance.constructor.prototype
//return ture

10、程式碼的複用(再次提醒自己,任何程式碼開始寫第二遍時,就要開始考慮如何複用)
函式封裝
繼承
複製extend
混入 mixin
借用 apply/call

11、繼承
繼承通常指的是:原型鏈繼承,通過指定原型,並可以通過原型鏈繼承原型上的屬性或者方法。
最優化:聖盃模式
var inherit = (function(c,p){
var F = function(){};
return function(c,p){
F.prototype = p.prototype;
c.prototype = new F();
c.uber = p.prototype;
c.prototype.constructor = c;
}
})()
ES6語法糖 class/extends

12、型別轉換
- , * , / ,% :一律轉換成數值後計算
+:
數字 + 字串 = 字串,運算順序是從左到右。
數字 + 物件 ,優先呼叫物件的 valueOf > toString
數字 + boolean/null : 數字
數字 + undefined : NaN
[1].toString() === '1'
{}.toString() === '[object object]'
NaN !== NaN , +undefined 為 NaN

13.模組化
分類:
es6: import / export
commonjs: require / module.exports / exports
amd:require / defined
require 與 import 的區別:
require支援動態匯入,import不支援。
require 是 同步 匯入,import 屬於 非同步 匯入。
require 是 值拷貝,匯出值變化不會影響匯入值,import 指向 記憶體地址,匯入值會隨著匯出值而變化。

14、防抖與節流
它們是一種常用的 高頻觸發優化方式,能對效能有較大的幫助。
防抖(debounce):將多次高頻操作優化為只在最後一次執行,通常使用的場景是:使用者輸入,只需再輸入完成後做一次輸入校驗即可。
function debounce(fn,wait,immediate){
let timer = null;
return function(){
let args = arguments;
let context = this;
if(immediate && !timer){
fn.apply(context,args);
}
if(timer) clearTimeout(timer);
timer = setTimeout(()=>{
fn.apply(context,args);
},wait)
}
}

節流(throttle):每隔一段時間後執行一次,也就是降低頻率,將高頻操作優化成低頻操作,通常使用場景:滾動條事件或者resize事件,
每隔100-500ms執行一次即可。
function throttle(fn,wait,immediate){
let timer = null;
let callNow = immediate;
return function(){
let context = this,args = arguments;
if(callNow){
fn.apply(context,args);
callNow = false;
}
if(!timer){
timer = setTimeout(()=>{
fn.apply(context,args)
timer = null;
},wait)
}
}
}

15、函式執行改變this
三種方式手動修改this指向:
call:fn.call(target,1,2)
apply:fn.apply(target,[1,2]);
bind:fn.bind(target)(1,2)

16、ES6/ES7
宣告:
let/const: 塊級作用域,不存在變數提升,暫時性死區,不允許重複宣告
const:宣告常量,無法修改
解構賦值
class/extend:類宣告與繼承
Set/Map:新的資料解構
非同步解決方案:
promise的使用與實現
generator:
yield:暫停程式碼
next():繼續執行程式碼
await/async 是 generator 的語法糖,babel是基於 promise 實現。

瀏覽器:

1、跨標籤頁通訊
不同標籤頁間的通訊,本質原理就是去運用一些可以 共享的中間介質,如下面方法:
1.1、通過父頁面 window.open() 和子頁面 postMessage
非同步下,通過 window.open('about:blank') 和 tab.location.href='*'
1.2、設定同域下共享的 localStorage 與 監聽 window.onstorage
重複寫入相同的值無法觸發。
會受到瀏覽器隱身模式等的限制
1.3、設定共享 cookie 與不斷輪詢髒檢查(setInterval)
1.4、藉助服務端或者中間層實現

2、瀏覽器架構
使用者介面
主程序
核心:
渲染引擎
JS引擎:
執行棧
事件觸發執行緒:
訊息佇列:
微任務
巨集任務
網路非同步執行緒
定時器執行緒

3、瀏覽器下事件迴圈(Event Loop)
事件迴圈是指:執行一個巨集任務,然後執行清空微任務列表,迴圈再執行巨集任務,再清微任務列表。
微任務 microtask(jobs): promise / ajax / Object.observe(已廢棄)
巨集任務 macrotask(jobs): setTimeout / script / IO / UI Rendering

4、從輸入url 到展示的過程
DNS解析
TCP三次握手
傳送請求,分析url,設定請求報文(頭,主體)
伺服器返回請求的檔案(html)
瀏覽器渲染
HTML parser -->DOM Tree
標記化演算法,進行元素狀態的標記
dom樹構建
CSS parser -->Style Tree
解析css程式碼,生成樣式樹
attachment -->Render Tree
結合dom樹與style樹,生成渲染樹
layout:佈局
GPU painting:畫素繪製頁面

5、重繪與迴流
當元素的樣式發生變化時,瀏覽器需要觸發更新,重新繪製元素。這個過程中,有兩種型別的操作,即重繪與迴流。
重繪(repaint):當元素樣式的改變不影響佈局時,瀏覽器將使用重繪對元素進行更新,此時由於只需要UI層面的重新畫素繪製,因此 損耗較少。
迴流(reflow):當元素的尺寸,結構或觸發某些屬性時,瀏覽器會重新渲染頁面,稱為迴流。此時,瀏覽器需要重新經過計算,計算後還需要重新頁面佈局,因此
是較重的操作。會觸發迴流的操作:
頁面初次渲染
瀏覽器視窗大小改變
元素尺寸,位置,內容發生改變
元素字型大小變化
新增或者刪除可見的dom元素
啟用css偽類(例如: :hover)
查詢某些屬性或呼叫某些方法:
clientWidth,clientHieght,clientTop,clientLeft
offsetWidth,offsetHeight,offsetTop,offsetLeft
scrollWidth,scrollHeight,scrollTop,scrollLeft
getComputedStyle()
getBoundingClientRect()
scrollTo()

迴流必定觸發重繪,重繪不一定觸發迴流。重繪的開銷較小,迴流的代價較高。
最佳實踐:
css:
避免使用table佈局
將動畫效果應用到position屬性為absolute 或 fixed的元素上
js:
避免頻繁操作樣式,可彙總後統一 一次修改。
儘量使用class進行樣式修改
減少dom的增刪次數,可使用字串或者 documentFragment 一次性插入。
極限優化時,修改樣式可將其 display:none 後修改
避免多次觸發上面提到的那些會觸發迴流的方法,可以的話儘量用變數存住。

6、儲存(可分為:短暫性儲存和永續性儲存)
短暫性的時候,我們只需要將資料存在記憶體,只在執行時可用。
永續性儲存,可以分為 瀏覽器端 與 伺服器端。
瀏覽器:
cookie:通常用於儲存使用者身份,登入狀態等。
http中自動攜帶,體積上限為4k,可自行設定過期時間。
locaLStorage/sessionStorage:長久儲存/視窗關閉刪除,體積限制為4-5M
indexDB
伺服器:
分散式快取 redis
資料庫

7、Web Worker
現代瀏覽器為 JavaScript 創造的多執行緒環境。可以新建並將部分任務分配到 worker 執行緒並行執行,兩個執行緒可 獨立執行,互不干擾,可通過自帶的
訊息機制 相互通訊。
用法:
//建立 worker
const worker = new Worker('work.js');
//向 worker 執行緒推送訊息
worker.postMessage('hello world');
//監聽 worker 執行緒傳送過來的訊息
worker.onmessage = function(event){
console.log('received message'+event.data);
}

限制:
同源限制
無法使用 document / window / alert / confirm
無法載入本地資源

8、記憶體洩露
意外的全域性變數:無法被回收
定時器:未被正確關閉,導致所引用的外部變數無法被釋放
事件監聽:沒有正確銷燬
閉包:會導致父級中的變數無法被釋放
dom引用:dom元素被刪除時,記憶體中的引用未被正確清空

chrome中的timeline進行記憶體標記,視覺化檢視記憶體的變化情況,找出異常點。

服務端與網路

1、http / https 協議
1.0、協議缺陷:
無法複用連結,完成即斷開,重新慢啟動 和 TCP 3次握手
head of line blocking:線頭阻塞,導致請求之間互相影響
1.1、改進:
長連線(預設 keep-alive),複用
host欄位指定對應的虛擬站點
新增功能:
斷點續傳
身份認證
狀態管理
cache快取
Cache-Control
Expires
last-Modified
Etag
2.0、
多路複用
二進位制分幀層:應用層和傳輸層之間
首部壓縮
服務端推送
https:較為安全的網路傳輸協議:
證書(公鑰)
SSL加密
埠443
TCP:
三次握手
四次揮手
滑動視窗:流量控制
擁塞處理:
慢開始
擁塞避免
快速重傳
快速恢復
快取策略:可分為 強快取 和協商快取

2、常見狀態碼
1xx:接受,繼續處理
200:成功,並返回資料
201:已建立
202:已接受
203:成功,但未授權
204:成功,無內容
205:成功,重置內容
206:成功,部分內容
301:永久移動,重定向
302:臨時移動,可使用原有url
304:資源未修改,可使用快取
305:需代理訪問
400:請求語法錯誤
401:要求身份認證
403:拒絕請求
404:資源不存在
500:伺服器錯誤

3、get/post
get:快取,請求長度受限,會被歷史儲存記錄
post:安全,大資料,更多編碼型別


4、websocket

websockt是一個 持久化的協議,基於http,服務端可以主動push

new Websocket(url)

ws.onerror = fn

ws.onclose = fn

ws.onopen = fn

ws.onmessage = fn

ws.send()

5、TCP三次握手

建立連線前,客戶端和服務端需要通過握手來確認對方:

客戶端傳送 syn(同步序列編號)請求,進入 syn_send 狀態,等待確認

服務端接收並確認 syn包後傳送 syn+ack 包,進入 syn_recv 狀態

客戶端接收 syn+ack包後,傳送 ack包,雙方進入 established 狀態

6、TCP四次揮手

客戶端 -- FIN --> 服務端,FIN-WAIT

服務端 -- ACK --> 客戶端,CLOSE-WAIT

服務端 -- ACK,FIN --> 客戶端,LAST-ACK

客戶端 -- ACK --> 服務端,CLOSED

7、node 的 event loop:6階段

timer階段:執行到期的 setTimeout / setInterval 佇列回撥

I/O 階段:執行上輪迴圈殘流的 callback

idle,prepare

poll:等待回撥

1、執行回撥

2、執行定時器

2.1、如有到期的 setTimeout / setInterval ,則返回 timer階段

2.2、如有 setImmediate,則前往check階段

check:

執行 setImmediate

close callbacks

8、跨域:

JSONP:利用<script> 標籤不受跨域限制的特點,缺點是隻能支援get請求

function jsonp(url,jsonpCallback,success){

const script = document.createElement('script');

script.src = url;

script.async = true;

script.type = 'text/javascript';

window[jsonpCallback] = function(data){

success && success(data)

}

document.body.appendChild(script);

}

設定 CORS: Access-Control-Allow-Origin:'*'

postMessage

9、安全

XSS 攻擊:注入惡意程式碼

cookie 設定 httpOnly

轉義頁面上的輸入內容和輸出內容

CSRF:跨站請求偽造,防護:
get不修改資料

不被第三方網站訪問到使用者的cookie

設定白名單,不被第三方網站請求

請求校驗

vue

1、nextTick
在下次dom更新迴圈結束之後執行延遲迴調,可用於獲取更新後的dom狀態。
macrotasks 任務的實現: setImmediate / MessageChannel / setTimeout

2、生命週期
_init_:
initLifecyle / Event,往 vm 上掛載各種屬性
callHook: beforeCreated --例項剛建立
initInjection/initState: 初始化注入和data響應性
created: 建立完成,屬性已經繫結,但還未生成真實dom
進行元素的掛載: $el / vm.$mount()
是否有 template: 解析成 render function
*.vue檔案:vue-loader 會將 <template> 編譯成 render function
beforeMount: 模板編譯 / 掛載之前
執行 render function,生成真實的 dom,並替換到 dom tree 中
mounted:元件已掛載

update:
執行diff演算法,比對改變是否需要觸發UI更新
flushScheduleQueue:
watcher.before: 觸發 beforeUpdate 鉤子 - watcher.run():執行watcher中的notify,通知所有依賴項更新UI
觸發 updated 鉤子:元件已更新

actived / deactivated(keep-alive):不銷燬,快取,元件啟用與失活。

destroy:
beforeDestroy:銷燬開始
銷燬自身且遞迴銷燬子元件以及事件監聽:
remove():刪除節點
watcher.teardown():清空依賴
vm.$off():解綁監聽
destroyed:完成後觸發鉤子

哇哦,下面見程式碼,看vue的初始化
new Vue({})
//初始化 vue 例項
function _init(){



}


3、資料響應(資料劫持)
資料響應的實現由兩部分構成:觀察者 watcher 和 依賴收集器 dep, 其核心是 defineProperty 這個方法,它可以重寫屬性的 get 與 set
方法,從而完成監聽資料的改變。
Observe(觀察者)觀察 props 與 state:
遍歷 props與state,對每個屬性建立獨立的監聽器(watcher)。
使用 defineProperty 重寫每個屬性的 get/set(defineReactive):
get:收集依賴
Dep.depend():
watcher.addDep()
set:派發更新
Dep.notify()
watcher.update()
queenWatcher()
nextTick
flushScheduleQueue
watcher.run()
updateComponent()

見程式碼梳理:
let data = {a:1};
//資料響應性
observe(data)
//初始化觀察者
new Watcher(data,'name',updateComponent)
data.a = 2;
//簡單表示用於資料更新後的操作
function updateComponent(){
vm._update() // patchs
}
//監視物件
function observe(obj){
//遍歷物件,使用 get/set 重新定義物件的每個屬性值
Object.keys(obj).map(key => {
defineReactive(obj,key,obj[key])
})
}

function defineReactive(obj,k,v){
//遞迴子屬性
if(type(v)=='object') observe(v)

//新建依賴收集器
let dep = new Dep()
//定義 get/set
Object.defineProperty(obj,k,{
enumerable:true,
configurable:true,
get:function reactiveGetter(){
//當有獲取該屬性時,證明依賴於該物件,因此被新增進收集器中
if(Dep.target){
dep.addSub(Dep.target)
}
return v
},
//重新設定值時,觸發收集器的通知機制
set:function reactiveSetter(nV){
v = nV
dep.notify()
}
})

},
//依賴收集器
class Dep{
construtor(){
this.subs = [];
}
addSub(sub){
this.subs.push(sub)
}
notify(){
this.subs.map(sub=>{
sub.update()
})
}
}
Dep.target = null

//觀察者
class Watcher{
constructor(obj,key,cb){
Dep.target = this;
this.cb = cb
this.obj = obj
this.key = key
this.value = obj[key]
Dep.target = null
}
addDep(Dep){
Dep.addSub(this)
}
update(){
this.value = this.obj[this.key]
this.cb(this.value)
}
before(){
callHook('beforeUpdate')
}
}

4、virtual dom 原理實現
建立dom樹
樹的diff,同層對比,輸入 patchs(listDiff/diffChildren/diffProps):
沒有新的節點,返回
新的節點 tagName 與 key 不變,對比props,繼續遞迴遍歷子樹:
對比屬性(對比新舊屬性列表):
舊屬性是否存在與新屬性列表中
都存在的是否有變化
是否出現舊列表中沒有的新屬性
tagName 和 key值變化了,則直接替換成新節點
渲染差異:
遍歷 patchs,把需要更改的節點取出來
區域性更新 dom

見程式碼梳理:
//diff演算法的實現
function dff(oldTree,newTree){
//差異收集
let pathchs = {}
dfs(oleTree,newTree,0,patchs)
return pathchs
}
function dfs(oldNode,newNode,index,pathchs){
let curPathchs = [];
if(newNode){
//當新舊節點的tagName和key值完全一致時
if(oldNode.tagName === newNode.tagName && oldNode.key === newNode.key){
//繼續比對屬性差異
let props = diffProps(oldNode.props,newNode.props)
curPathchs.push({type:'changeProps',props})
//遞迴進入下一層級的比較
diffChildrens(oldNode.children,newNode.children,index,pathchs)
}else{
//當tagName 或者 key 修改了後,表示已經時全新節點,無需再比
curPathchs.push({type:'replaceNode',node:newNode})
}
}
//構建出整顆差異樹
if(curPathchs.length){
if(pathchs[index]){
pathchs[index] = pathchs[index].concat(curPathchs)
}else{
pathchs[index] = curPathchs
}
}
}
//屬性對比實現
function diffProps(oldProps,newProps){
let propsPathchs = []
//遍歷新舊屬性列表,查詢刪除項,查詢修改項,查詢新增項
forin(oldProps,(k,v)=>{
if(!newProps.hasOwnProperty(k)){
propsPathchs.push({type:'remove',prop:k})
}else{
if(v !== newProps[k] ){
propsPathcchs.push({type:'chage',prop:k,value:newProps[k]})
}
}
})
forin(newProps,(k,v)=>{
if(!oldProps.hasOwnProperty(k)){
propsPathchs.push({type:'add',prop:k,value:v})
}
})
return propsPathchs
}
//對比子級差異
function diffChildrens(oldChild,newChild,index,pathchs){
//標記子級的刪除、新增、移動
let {change,list} = diffList(oldChild,newChild,index,pathchs)
if(change.length){
if(pathchs[index]){
pathchs[index] = pathchs[index].concat(chage)
}else{
pathchs[index] = change
}
}
//根據key獲取原本匹配的節點,進一步遞迴從頭開始對比
oldChild.map((item,i)=>{
let keyIndex = list.indexOf(item,key)
if(keyIndex){
let node = newChild[keyIndex]
//進一步遞迴對比
dfs(item,node,index,pathchs)
}
})
}
//列表對比,主要也是根據key 值查詢匹配項
//對比出新舊列表的新增,刪除,移動
function diffList(oldList,newList,index,pathchs){
let chage = []
let list = []
const newKeys = getKey(newList)
oldList.map(v=>{
if(newKeys.indexOf(v.key) > -1){
list.push(v.key)
}else{
list.push(null)
}
})
//標記刪除
for(let i=list.length-1;i>=0;i++){
if(!list[i]){
list.splice(i,1)
change.push({type:'remove',index:1})
}
}
//標記新增和移動
newList.map((item,i)=>{
const key = item.key
const index = list.indexOf(key)
if(index===-1 || key==null){
//新增
change.push({type:'add',node:item,index:i})
list.splice(i,0,key)
}else{
//移動
if(index !==i){
change.push({
type:'move',
form:index,
to:i
})
move(list,index,i)
}
}
})
return { change , list }
}

5、proxy 相比於 defineProperty 的優勢
陣列變化也能監聽到。
不需要深度遍歷監聽
let data = {a:1}
let reactiveData = new Proxy(data,{
get:function(target,name){
//....
}
})

6、vue-router
mode:
hash
history
跳轉
this.$router.push()
<router-link to=""></router-link>
佔位
<router-view></router-view>

7、vuex
state:狀態中心
mutations:更改狀態
actions:非同步更改狀態
getters:獲取狀態
modules:將 state 分成 多個 modules ,便於管理


vue.js 原始碼揭祕網址:
https://ustbhuangyi.github.io/vue-analysis/v2/prepare/build.html#%E6%9E%84%E5%BB%BA%E8%84%9A%E6%9C%AC

演算法:

//五大演算法
貪心演算法:區域性最優解法
分治演算法:分成多個小模組,與原問題性質相同
動態規劃:每個狀態都是過去歷史的一個總結
回溯法:發現原先選擇不優時,退回重新選擇
分支限界法

基礎排序演算法:
氣泡排序:兩兩比較
function bubleSort(arr){
let len = arr.length;
for(let i=len;i>=2;i--){ //這個地方也可以直接寫 i>=1
for(let j=0;j<=i-1;j++){ //對應的這裡改成 j<=i 即可
if(arr[j] > arr[j+1]){
[arr[j],arr[j+1]] = [arr[j+1],arr[j]]
}
}
}
return arr;
}
插入排序:即將元素插入到已排序好的陣列中
function insertSort(arr){
for(let i=0;i<arr.length;i++){
for(let j=i;j>0;j--){
if(arr[j] < arr[j-1]){
[arr[j],arr[j-1]] = [arr[j-1],arr[j]];
}else{
break;
}
}
}
return arr;
}

遞迴運用(費波那契數列):爬樓梯問題
初始在第一級,到第一級有1種方法( s(1)=1),到第二級也只有一種方法( s(2)=1 ),第三級(s(3) = s(1)+s(2))
function cStairs(n){
if(n ===1 || n===2){
return 1;
}else{
return cStairs(n-1) + cStairs(n-2)
}
}

資料樹:
二叉樹:最多隻有兩個子節點
完全二叉樹
滿二叉樹:
深度為h,有n個節點,且滿足 n=2^h -1
二叉查詢樹:是一種特殊的二叉樹,能有效地提高查詢效率 BST
小值在左,大值在右
節點n的所有左子樹值小於n,所有右子樹值大於n。
...

天平找次品
有n個硬幣,其中1個為假幣,假幣重量較輕,有一把天平,需要稱多少次能保證一定找到假幣?
三等分演算法:
1、將硬幣分成3組,隨便取其中兩組天平稱重
平衡,假幣在剩餘那組,取其繼續1迴圈
不平衡,假幣在輕的那一組,取其繼續1迴圈。

//記錄小程式裡面有個api獲取滾動距離

wx.createSelectorQuery().select('#container').boundingClientRect(function(rect){ //container這個容器一定
//是最大那個div。
if(rect){
bottom = Math.abs(rect.bottom);
if(parseInt(bottom) == windowHeight){ //這裡的windowHeight是通過wx.getSystemInfo()獲取的。
//然後這裡可以做處理了
//這裡表示 滾動到底部了
}else{

}
}
})

參考連結:https://juejin.im/post/6844903776512393224#heading-13