金九銀十,又是一波跑路。趁著有空把前端基礎和面試相關的知識點都系統的學習一遍,參考一些權威的書籍和優秀的文章,最后加上自己的一些理解,總結出來這篇文章。適合復習和準備面試的同學,其中的知識點包括:
String、Number、Boolean、Null、Undefined、Symbol、BigInt、Object
兩者都是存放數據的地方。
棧(stack)是自動分配的內存空間,它存放基本類型的值和引用類型的內存地址。
堆(heap)是動態分配的內存空間,它存放引用類型的值。
JavaScript 不允許直接操作堆空間的對象,在操作對象時,實際操作是對象的引用,而存放在??臻g中的內存地址就起到指向的作用,通過內存地址找到堆空間中的對應引用類型的值。
JavaScript 作為一個弱類型語言,因使用靈活的原因,在一些場景中會對類型進行自動轉換。
常見隱式類型轉換場景有3種:運算、取反、比較
運算的隱式類型轉換會將運算的成員轉換為 number 類型。
基本類型轉換:
true + false // 1
null + 10 // 10
false + 20 // 20
undefined + 30 // NaN
1 + '2' // "12"
NaN + '' // "NaN"
undefined + '' // "undefined"
null + '' // "null"
'' - 3 // -3
引用類型轉換:
[1] + 10 // "110"
[] + 20 // "20"
[1,2] + 20 // "1,220"
[20] - 10 // 10
[1,2] - 10 // NaN
({}) + 10 // "[object Object]10"
({}) - 10 // NaN
解析引用類型轉換過程:
[1,2] + 20
// 過程:
[1,2].toString() // '1,2'
'1,2' + 20 // '1,220'
[20] - 10
// 過程
[20].toString() // '20'
Number('20') // 20
20 - 10 // 10
取反的隱式類型轉換會將運算的成員轉換為 boolean 類型。
這個隱式類型轉換比較簡單,就是將值轉為布爾值再取反:
![] // false
!{} // false
!false // true
通常為了快速獲得一個值的布爾值類型,可以取反兩次:
!![] // true
!!0 // false
比較分為 嚴格比較===和 非嚴格比較==,由于===會比較類型,不會進行類型轉換。這里只討論==。
比較的隱式類型轉換基本會將運算的成員轉換為 number 類型。
undefined==null // true
''==0 // true
true==1 // true
'1'==true // true
[1]=='1' // true
[1,2]=='1,2' // true
({})=='[object Object]' // true
預編譯發生在 JavaScript 代碼執行前,對代碼進行語法分析和代碼生成,初始化的創建并存儲變量,為執行代碼做好準備。
預編譯過程:
例子:
function foo(x, y) {
console.log(x)
var x=10
console.log(x)
function x(){}
console.log(x)
}
foo(20, 30)
// 1. 創建AO對象
AO {}
// 2. 尋找形參和變量聲明賦值為 undefined
AO {
x: undefined
y: undefined
}
// 3. 實參形參相統一
AO {
x: 20
y: 30
}
// 4. 函數聲明提升
AO {
x: function x(){}
y: 30
}
編譯結束后代碼開始執行,第一個 x 從 AO 中取值,輸出是函數x;x 被賦值為 10,第二個 x 輸出 10;函數x 已被聲明提升,此處不會再賦值 x,第三個 x 輸出 10。
作用域能保證對有權訪問的所有變量和函數的有序訪問,是代碼在運行期間查找變量的一種規則。
函數在運行時會創建屬于自己的作用域,將內部的變量和函數定義“隱藏”起來,外部作用域無法訪問包裝函數內部的任何內容。
在ES6之前創建塊級作用域,可以使用 with 或 try/catch。而在ES6引入 let 關鍵字后,讓塊級作用域聲明變得更簡單。let 關鍵字可以將變量綁定到所在的任意作用域中(通常是{...}內部)。
{
let num=10
}
console.log(num) // ReferenceError: num is not defined
一旦設置了參數的默認值,函數進行聲明初始化時,參數會形成一個單獨的作用域。等到初始化結束,這個作用域就會消失。這種語法行為,在不設置參數默認值時,是不會出現的。
let x=1;
function f(x, y=x) {
console.log(y);
}
f(2) // 2
參數y的默認值等于變量x。調用函數f時,參數形成一個單獨的作用域。在這個作用域里面,默認值變量x指向第一個參數x,而不是全局變量x,所以輸出是2。
let x=1;
function foo(x, y=function() { x=2; }) {
x=3;
y();
console.log(x);
}
foo() // 2
x // 1
y 的默認是一個匿名函數,匿名函數內的x指向同一個作用域的第一個參數x。函數foo的內部變量x就指向第一個參數x,與匿名函數內部的x是一致的。y函數執行對參數x重新賦值,最后輸出的就是2,而外層的全局變量x依然不受影響。
閉包的本質就是作用域問題。當函數可以記住并訪問所在作用域,且該函數在所處作用域之外被調用時,就會產生閉包。
簡單點說,一個函數內引用著所在作用域的變量,并且它被保存到其他作用域執行,引用變量的作用域并沒有消失,而是跟著這個函數。當這個函數執行時,就可以通過作用域鏈查找到變量。
let bar
function foo() {
let a=10
// 函數被保存到了外部
bar=function () {
// 引用著不是當前作用域的變量a
console.log(a)
}
}
foo()
// bar函數不是在本身所處的作用域執行
bar() // 10
優點:私有變量或方法、緩存
缺點:閉包讓作用域鏈得不到釋放,會導致內存泄漏
JavaScript 中的對象有一個特殊的內置屬性 prototype(原型),它是對于其他對象的引用。當查找一個變量時,會優先在本身的對象上查找,如果找不到就會去該對象的 prototype 上查找,以此類推,最終以 Object.prototype 為終點。多個 prototype 連接在一起被稱為原型鏈。
原型繼承的方法有很多種,這里不會全部提及,只記錄兩種常用的方法。
function inherit(Target, Origin){
function F() {};
F.prototype=Origin.prototype;
Target.prototype=new F();
// 還原 constuctor
Target.prototype.constuctor=Target;
// 記錄繼承自誰
Target.prototype.uber=Origin.prototype;
}
圣杯模式的好處在于,使用中間對象隔離,子級添加屬性時,都會加在這個對象里面,不會對父級產生影響。而查找屬性是沿著 __proto__ 查找,可以順利查找到父級的屬性,實現繼承。
使用:
function Person() {
this.name='people'
}
Person.prototype.sayName=function () { console.log(this.name) }
function Child() {
this.name='child'
}
inherit(Child, Person)
Child.prototype.age=18
let child=new Child()
class Person {
constructor() {
this.name='people'
}
sayName() {
console.log(this.name)
}
}
class Child extends Person {
constructor() {
super()
this.name='child'
}
}
Child.prototype.age=18
let child=new Child()
Class 可以通過 extends 關鍵字實現繼承,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。
let str='hello'
str.split('')
基本類型按道理說是沒有屬性和方法,但是在實際操作時,我們卻能從基本類型調用方法,就像一個字符串能調用 split 方法。
為了方便操作基本類型值,每當讀取一個基本類型值的時候,后臺會創建一個對應的基本包裝類型的對象,從而讓我們能夠調用方法來操作這些數據。大概過程如下:
let str=new String('hello')
str.split('')
str=null
this是函數被調用時發生的綁定,它指向什么完全取決于函數在哪里被調用。我理解的this是函數的調用者對象,當在函數內使用this,可以訪問到調用者對象上的屬性和方法。
this綁定的四種情況:
優先級new綁定最高,最后到默認綁定。
注意點:構造函數內出現return,如果返回基本類型,則提前結束構造過程,返回實例對象;如果返回引用類型,則返回該引用類型。
// 返回基本類型
function Foo(){
this.name='Joe'
return 123
this.age=20
}
new Foo() // Foo {name: "Joe"}
// 返回引用類型
function Foo(){
this.name='Joe'
return [123]
this.age=20
}
new Foo() // [123]
三者作用都是改變this指向的。
call 和 apply 改變 this 指向并調用函數,它們兩者區別就是傳參形式不同,前者的參數是逐個傳入,后者傳入數組類型的參數列表。
bind 改變 this 并返回一個函數引用,bind 多次調用是無效的,它改變的 this 指向只會以第一次調用為準。
Function.prototype.mycall=function () {
if(typeof this !=='function'){
throw 'caller must be a function'
}
let othis=arguments[0] || window
othis._fn=this
let arg=[...arguments].slice(1)
let res=othis._fn(...arg)
Reflect.deleteProperty(othis, '_fn') //刪除_fn屬性
return res
}
apply 實現同理,修改傳參形式即可
Function.prototype.mybind=function (oThis) {
if(typeof this !='function'){
throw 'caller must be a function'
}
let fThis=this
//Array.prototype.slice.call 將類數組轉為數組
let arg=Array.prototype.slice.call(arguments,1)
let NOP=function(){}
let fBound=function(){
let arg_=Array.prototype.slice.call(arguments)
// new 綁定等級高于顯式綁定
// 作為構造函數調用時,保留指向不做修改
// 使用 instanceof 判斷是否為構造函數調用
return fThis.apply(this instanceof fBound ? this : oThis, arg.concat(arg_))
}
// 維護原型
if(this.prototype){
NOP.prototype=this.prototype
fBound.prototype=new NOP()
}
return fBound
}
常用:let、const、擴展運算符、模板字符串、對象解構、箭頭函數、默認參數、Promise
數據結構:Set、Map、Symbol
其他:Proxy、Reflect
Set:
WeakSet:
Map:
WeakMap:
Promise 是ES6中新增的異步編程解決方案,避免回調地獄問題。Promise 對象是通過狀態的改變來實現通過同步的流程來表示異步的操作, 只要狀態發生改變就會自動觸發對應的函數。
Promise對象有三種狀態,分別是:
狀態一旦改變既不可逆,可以通過函數來監聽 Promise 狀態的變化,成功執行 then 函數的回調,失敗執行 catch 函數的回調
淺拷貝是值的復制,對于對象是內存地址的復制,目標對象的引用和源對象的引用指向的是同一塊內存空間。如果其中一個對象改變,就會影響到另一個對象。
常用淺拷貝的方法:
let arr=[{a:1}, {b:2}]
let newArr=arr1.slice()
let newArr=[...arr1]
深拷貝是將一個對象從內存中完整的拷貝一份出來,對象與對象間不會共享內存,而是在堆內存中新開辟一個空間去存儲,所以修改新對象不會影響原對象。
常用的深拷貝方法:
JSON.parse(JSON.stringify(obj))
function deepClone(obj, map=new WeakMap()) {
if (obj===null || typeof obj !=="object") return obj;
const type=Object.prototype.toString.call(obj).slice(8, -1)
let strategy={
Date: (obj)=> new Date(obj),
RegExp: (obj)=> new RegExp(obj),
Array: clone,
Object: clone
}
function clone(obj){
// 防止循環引用,導致棧溢出,相同引用的對象直接返回
if (map.get(obj)) return map.get(obj);
let target=new obj.constructor();
map.set(obj, target);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
target[key]=deepClone(obj[key], map);
}
}
return target;
}
return strategy[type] && strategy[type](obj)
}
事件委托也叫做事件代理,是一種dom事件優化的手段。事件委托利用事件冒泡的機制,只指定一個事件處理程序,就可以管理某一類型的所有事件。
假設有個列表,其中每個子元素都會有個點擊事件。當子元素變多時,事件綁定占用的內存將會成線性增加,這時候就可以使用事件委托來優化這種場景。代理的事件通常會綁定到父元素上,而不必為每個子元素都添加事件。
<ul @click="clickHandler">
<li class="item">1</li>
<li class="item">2</li>
<li class="item">3</li>
</ul>
clickHandler(e) {
// 點擊獲取的子元素
let target=e.target
// 輸出子元素內容
consoel.log(target.textContent)
}
防抖用于減少函數調用次數,對于頻繁的調用,只執行這些調用的最后一次。
/**
* @param {function} func - 執行函數
* @param {number} wait - 等待時間
* @param {boolean} immediate - 是否立即執行
* @return {function}
*/
function debounce(func, wait=300, immediate=false){
let timer, ctx;
let later=(arg)=> setTimeout(()=>{
func.apply(ctx, arg)
timer=ctx=null
}, wait)
return function(...arg){
if(!timer){
timer=later(arg)
ctx=this
if(immediate){
func.apply(ctx, arg)
}
}else{
clearTimeout(timer)
timer=later(arg)
}
}
}
節流用于減少函數請求次數,與防抖不同,節流是在一段時間執行一次。
/**
* @param {function} func - 執行函數
* @param {number} delay - 延遲時間
* @return {function}
*/
function throttle(func, delay){
let timer=null
return function(...arg){
if(!timer){
timer=setTimeout(()=>{
func.apply(this, arg)
timer=null
}, delay)
}
}
}
Currying(柯里化)是把接受多個參數的函數變換成接受一個單一參數的函數,并且返回接受余下的參數而且返回結果的新函數的技術。
通用柯里化函數:
function currying(fn, arr=[]) {
let len=fn.length
return (...args)=> {
let concatArgs=[...arr, ...args]
if (concatArgs.length < len) {
return currying(fn, concatArgs)
} else {
return fn.call(this, ...concatArgs)
}
}
}
使用:
let sum=(a,b,c,d)=> {
console.log(a,b,c,d)
}
let newSum=currying(sum)
newSum(1)(2)(3)(4)
優點:
堆分為新生代和老生代,分別由副垃圾回收器和主垃圾回收器來負責垃圾回收。
一般剛使用的對象都會放在新生代,它的空間比較小,只有幾十MB,新生代里還會劃分出兩個空間:form空間和to空間。
對象會先被分配到form空間中,等到垃圾回收階段,將form空間的存活對象復制到to空間中,對未存活對象進行回收,之后調換兩個空間,這種算法稱之為 “Scanvage”。
新生代的內存回收頻率很高、速度也很快,但空間利用率較低,因為讓一半的內存空間處于“閑置”狀態。
老生代的空間較大,新生代經過多次回收后還存活的對象會被送到老生代。
老生代使用“標記清除”的方式,從根元素開始遍歷,將存活對象進行標記。標記完成后,對未標記的對象進行回收。
經過標記清除之后的內存空間會產生很多不連續的碎片空間,導致一些大對象無法存放進來。所以在回收完成后,會對這些不連續的碎片空間進行整理。
定義:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
JavaScript 作為一門無類的語言,傳統的單例模式概念在 JavaScript 中并不適用。稍微轉換下思想:單例模式確保只有一個對象,并提供全局訪問。
常見的應用場景就是彈窗組件,使用單例模式封裝全局彈窗組件方法:
import Vue from 'vue'
import Index from './index.vue'
let alertInstance=null
let alertConstructor=Vue.extend(Index)
let init=(options)=>{
alertInstance=new alertConstructor()
Object.assign(alertInstance, options)
alertInstance.$mount()
document.body.appendChild(alertInstance.$el)
}
let caller=(options)=>{
// 單例判斷
if(!alertInstance){
init(options)
}
return alertInstance.show(()=>alertInstance=null)
}
export default {
install(vue){
vue.prototype.$alert=caller
}
}
無論調用幾次,組件也只實例化一次,最終獲取的都是同一個實例。
定義:定義一系列的算法,把它們一個個封裝起來,并且使它們可以相互替換。
策略模式是開發中最常用的設計模式,在一些場景下如果存在大量的 if/else,且每個分支點的功能獨立,這時候就可以考慮使用策略模式來優化。
就像就上面手寫深拷貝就用到策略模式來實現:
function deepClone(obj, map=new WeakMap()) {
if (obj===null || typeof obj !=="object") return obj;
const type=Object.prototype.toString.call(obj).slice(8, -1)
// 策略對象
let strategy={
Date: (obj)=> new Date(obj),
RegExp: (obj)=> new RegExp(obj),
Array: clone,
Object: clone
}
function clone(obj){
// 防止循環引用,導致棧溢出,相同引用的對象直接返回
if (map.get(obj)) return map.get(obj);
let target=new obj.constructor();
map.set(obj, target);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
target[key]=deepClone(obj[key], map);
}
}
return target;
}
return strategy[type] && strategy[type](obj)
}
這樣的代碼看起來會更簡潔,只需要維護一個策略對象,需要新功能就添加一個策略。由于策略項是單獨封裝的方法,也更易于復用。
定義:為一個對象提供一個代用品,以便控制對它的訪問。
當不方便直接訪問一個對象或者不滿足需要的時候,提供一個代理對象來控制對這個對象的訪問,實際訪問的是代理對象,代理對象對請求做出處理后,再轉交給本體對象。
使用緩存代理請求數據:
function getList(page) {
return this.$api.getList({
page
}).then(res=> {
this.list=res.data
return res
})
}
// 代理getList
let proxyGetList=(function() {
let cache={}
return async function(page) {
if (cache[page]) {
return cache[page]
}
let res=await getList.call(this, page)
return cache[page]=res.data
}
})()
上面的場景是常見的分頁需求,同一頁的數據只需要去后臺獲取一次,并將獲取到的數據緩存起來,下次再請求同一頁時,便可以直接使用之前的數據。
定義:它定義對象間的一種一對多的依賴關系,當一個對象的狀態發送改變時,所有依賴于它的對象都將得到通知。
發布訂閱模式主要優點是解決對象間的解耦,它的應用非常廣泛,既可以用在異步編程中,也可以幫助我們完成松耦合的代碼編寫。像 eventBus 的通信方式就是發布訂閱模式。
let event={
events: [],
on(key, fn){
if(!this.events[key]) {
this.events[key]=[]
}
this.events[key].push(fn)
},
emit(key, ...arg){
let fns=this.events[key]
if(!fns || fns.length==0){
return false
}
fns.forEach(fn=> fn.apply(this, arg))
}
}
上面只是發布訂閱模式的簡單實現,還可以為其添加 off 方法來取消監聽事件。在 Vue 中,通常是實例化一個新的 Vue 實例來做發布訂閱中心,解決組件通信。而在小程序中可以手動實現發布訂閱模式,用于解決頁面通信的問題。
定義:動態地為某個對象添加一些額外的職責,而不會影響對象本身。
裝飾器模式在開發中也是很常用的設計模式,它能夠在不影響源代碼的情況下,很方便的擴展屬性和方法。比如以下應用場景是提交表單。
methods: {
submit(){
this.$api.submit({
data: this.form
})
},
// 為提交表單添加驗證功能
validateForm(){
if(this.form.name==''){
return
}
this.submit()
}
}
想象一下,如果你剛接手一個項目,而 submit 的邏輯很復雜,可能還會牽扯到很多地方。冒然的侵入源代碼去擴展功能會有風險,這時候裝飾器模式就幫上大忙了。
MVVM 對應 3個組成部分,Model(模型)、View(視圖) 和 ViewModel(視圖模型)。
View 不能和 Model 直接通信,它們只能通過 ViewModel 通信。Model 和 ViewModel 之間的交互是雙向的,ViewModel 通過雙向數據綁定把 View 層和 Model 層連接起來,因此 View 數據的變化會同步到 Model 中,而 Model 數據的變化也會立即反應到 View 上。
題外話,你可能不知道 Vue 不完全是 MVVM 模式:
嚴格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 在組件提供了 $refs 這個屬性,讓 Model 可以直接操作 View,違反了這一規定。
流程主要分為三個部分:
當一個組件被定義,data 必須聲明為返回一個初始數據對象的函數,因為組件可能被用來創建多個實例。如果 data 仍然是一個純粹的對象,則所有的實例將共享引用同一個數據對象!通過提供 data 函數,每次創建一個新實例后,我們能夠調用 data 函數,從而返回初始數據的一個全新副本數據對象。
JavaScript 中的對象作為引用類型,如果是創建多個實例,直接使用對象會導致實例的共享引用。而這里創建多個實例,指的是組件復用的情況。因為在編寫組件時,是通過 export 暴露出去的一個對象,如果組件復用的話,多個實例都是引用這個對象,就會造成共享引用。使用函數返回一個對象,由于是不同引用,自然可以避免這個問題發生。
“計算屬性Watcher”會帶有一個 dirty 的屬性,在初始化取值完成后,會將 dirty 設置為 false。只要依賴屬性不更新,dirty 永遠為 false,重復取值也不會再去執行求值函數,而是直接返回結果,從而實現緩存。相反,依賴屬性更新會將“計算屬性 Watcher”的 dirty 設置為 true,在頁面渲染對計算屬性取值時,再次觸發求值函數更新計算屬性。
Object.defineProperty(target, key, {
get() {
const watcher=this._computedWatchers && this._computedWatchers[key]
// 計算屬性緩存
if (watcher.dirty) {
// 計算屬性求值
watcher.evaluate()
}
return watcher.value
}
})
雙向綁定是視圖變化會反映到數據,數據變化會反映到視圖,v-model 就是個很好理解的例子。其實主要考查的還是響應式原理,響應式原理共包括3個主要成員,Observer 負責監聽數據變化,Dep 負責依賴收集,Watcher 負責數據或視圖更新,我們常說的收集依賴就是收集 Watcher。
響應式原理主要工作流程如下:
Vue 內部重寫數組原型鏈,當數組發生變化時,除了執行原生的數組方法外,還會調用 dep.notify 通知 Watcher 更新。觸發數組更新的方法共7種:
keep-alive 是 Vue 的內置組件,同時也是一個抽象組件,它主要用于組件緩存。當組件切換時會將組件的VNode緩存起來,等待下次重新激活時,再將緩存的組件VNode渲染出來,從而實現緩存。
常用的兩個屬性 include 和 exclude,支持字符串、正則和數組的形式,允許組件有條件的進行緩存。還有 max 屬性,用于設置最大緩存數。
兩個生命周期 activated 和 deactivated,在組件激活和失活時觸發。
keep-alive 的緩存機制運用LRU(Least Recently Used)算法,
在下次 dom 更新結束之后執行延遲回調。nextTick 主要使用了宏任務和微任務。根據執行環境分別嘗試采用:
nextTick 主要用于內部 Watcher 的異步更新,對外我們可以使用 Vue.nextTick 和 vm.$nextTick。在 nextTick 中可以獲取更新完成的 dom。
所有的 prop 都使得其父子 prop 之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外變更父級組件的狀態,從而導致你的應用的數據流向難以理解。
單向數據流只允許數據由父組件傳遞給子組件,數據只能由父組件更新。當數據傳遞到多個子組件,而子組件能夠在其內部更新數據時,在主觀上很難知道是哪個子組件更新了數據,導致數據流向不明確,從而增加應用調試的難度。
但子組件更新父組件數據的場景確實存在,有3種方法可以使用:
Object.definedProperty 只能檢測到屬性的獲取和設置,對于新增和刪除是沒辦法檢測的。在數據初始化時,由于不知道哪些數據會被用到,Vue 是直接遞歸觀測全部數據,這會導致性能多余的消耗。
Proxy 劫持整個對象,對象屬性的增加和刪除都能檢測到。Proxy 并不能監聽到內部深層的對象變化,因此 Vue 3.0 的處理方式是在 getter 中去遞歸響應式,只有真正訪問到的內部對象才會變成響應式,而不是無腦遞歸,在很大程度上提升了性能。
路由懶加載是性能優化的一種手段,在編寫代碼時可以使用 import() 引入路由組件,使用懶加載的路由會在打包時單獨出來成一個 js 文件,可以使用 webpackChunkName 自定義包名。在項目上線后,懶加載的 js 文件不會在第一時間加載,而是在訪問到對應的路由時,才會動態創建 script 標簽去加載這個 js 文件。
{
path:'users',
name:'users',
component:()=> import(/*webpackChunkName: "users"*/ '@/views/users'),
}
路由進入前調用
const router=new VueRouter({ ... })
router.beforeEach((to, from, next)=> {
// ...
})
在所有組件內守衛和異步組件被解析之后調用
router.beforeResolve((to, from, next)=> {
// ...
})
路由在確認后調用
router.afterEach((to, from)=> {
// ...
})
路由進入前調用,beforeEnter 在 beforeEach 之后執行
const router=new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next)=> {
// ...
}
}
]
})
路由確認前調用,組件實例還沒被創建,不能獲取組件實例 this
beforeRouteEnter (to, from, next) {
// ...
// 可以通過回調訪問實例
next(vm=> {
// vm 為組件實例
})
},
路由改變時調用,可以訪問組件實例
beforeRouteUpdate (to, from, next) {
// ...
},
離開該組件的對應路由時調用,可以訪問組件實例 this
beforeRouteLeave (to, from, next) {
// ...
}
vue-router原理是更新視圖而不重新請求頁面。vue-router共有3種模式:hash模式、history模式、abstract模式。
hash模式使用 hashchange 監聽地址欄的hash值的變化,加載對應的頁面。每次的hash值變化后依然會在瀏覽器留下歷史記錄,可以通過瀏覽器的前進后退按鈕回到上一個頁面。
history模式基于History Api實現,使用 popstate 監聽地址欄的變化。使用 pushState 和 replaceState 修改url,而無需加載頁面。但是在刷新頁面時還是會向后端發起請求,需要后端配合將資源定向回前端,交由前端路由處理。
不涉及和瀏覽器地址的相關記錄。通過數組維護模擬瀏覽器的歷史記錄棧。
跨模塊調用是指當前命名空間模塊調用全局模塊或者另一個命名空間模塊。在調用 dispatch 和 commit 時設置第三個參數為 {root:true}。
modules: {
foo: {
namespaced: true,
actions: {
someAction ({ dispatch, commit, getters, rootGetters }) {
// 調用自己的action
dispatch('someOtherAction') // -> 'foo/someOtherAction'
// 調用全局的action
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
// 調用其他模塊的action
dispatch('user/someOtherAction', null, { root: true }) // -> 'user/someOtherAction'
},
someOtherAction (ctx, payload) { ... }
}
}
}
vuex存儲的狀態在頁面刷新后會丟失,使用持久化技術能保證頁面刷新后狀態依然存在。
這里只記錄常用的兩種模塊:CommonJS模塊、ES6模塊。
Node.js 采用 CommonJS 模塊規范,在服務端運行時是同步加載,在客戶端使用需要編譯后才可以運行。
module.exports 不為空:
// nums.js
exports.a=1
module.exports={
b: 2
}
exports.c=3
let nums=require('./nums.js') // { b: 2 }
module.exports 為空:
// nums.js
exports.a=1
exports.c=3
let nums=require('./nums.js') // { a: 1, c: 3 }
值拷貝的體現:
// nums.js
let obj={
count: 10
}
let count=20
function addCount() {
count++
}
function getCount() {
return count
}
function addObjCount() {
obj.count++
}
module.exports={ count, obj, addCount, getCount, addObjCount }
let { count, obj, addCount, getCount, addObjCount }=require('./nums.js')
// 原始類型不受影響
console.log(count) // 20
addCount()
console.log(count) // 20
// 如果想獲取到變化的值,可以使用函數返回
console.log(getCount()) // 21
// 引用類型會被改變
console.log(obj) // { count: 10 }
addObjCount()
console.log(obj) // { count: 11 }
ES6 模塊的設計思想是盡量的靜態化,使得編譯時就能確定模塊的依賴關系,以及輸入和輸出的變量。
// nums.js
export let count=20
export function addCount() {
count++
}
export default {
other: 30
}
// 同時引入 export default 和 export 的變量
import other, { count, addCount } from './async.js'
console.log(other) // { other: 30 }
console.log(count) // 20
addCount()
console.log(count) // 21
數據變化過程:字節 → 字符 → 令牌 → 樹 → 頁面
在布局完成后,對DOM布局進行修改(比如大小或位置),會引起頁面重新計算布局,這個過程稱為“回流”。
對DOM進行不影響布局的修改引起的屏幕局部繪制(比如背景顏色、字體顏色),這個過程稱為“重繪”。
回流一定會引起重繪,而重繪不一定會引起回流。由于回流需要重新計算節點布局,回流的渲染耗時會高于重繪。
對于回流重繪,瀏覽器本身也有優化策略,瀏覽器會維護一個隊列,將回流重繪操作放入隊列中,等隊列到達一定時間,再按順序去一次性執行隊列的操作。
但是也有例外,有時我們需要獲取某些樣式信息,例如:
offsetTop,offsetLeft,offsetWidth,offsetHeight,scrollTop/Left/Width/Height,clientTop/Left/Width/Height,getComputedStyle(),或者 IE 的 currentStyle。
這時,瀏覽器為了反饋準確的信息,需要立即回流重繪一次,所以可能導致隊列提前執行。
在瀏覽器的實現上,諸如渲染任務、JavaScript 腳本執行、User Interaction(用戶交互)、網絡處理都跑在同一個線程上,當執行其中一個類型的任務的時候意味著其他任務的阻塞,為了有序的對各個任務按照優先級進行執行瀏覽器實現了我們稱為 Event Loop 調度流程。
簡單來說,Event Loop 就是執行代碼、收集和處理事件以及執行隊列中子任務的一個過程。
在一次新的事件循環的過程中,遇到宏任務時,宏任務將被加入任務隊列,但需要等到下一次事件循環才會執行。
常見宏任務:setTimeout、setInterval、requestAnimationFrame
當前事件循環的任務隊列為空時,微任務隊列中的任務就會被依次執行。在執行過程中,如果遇到微任務,微任務被加入到當前事件循環的微任務隊列中。簡單來說,只要有微任務就會繼續執行,而不是放到下一個事件循環才執行。
微任務隊列屬于任務運行環境內的一員,并非處于全局的位置。也就是說,每個任務都會有一個微任務隊列。
常見微任務:Promise.then、Promise.catch、MutationObserver
在當前任務運行環境內,微任務總是先于宏任務執行;
requestAnimationFrame 回調在頁面渲染之前調用,適合做動畫;
requestIdleCallback 在渲染屏幕之后調用,可以使用它來執行一些不太重要的任務。
源是由 URL 中協議、主機名(域名)以及端口共同組成的部分。
同源策略是瀏覽器的行為,為了保護本地數據不被JavaScript代碼獲取回來的數據污染,它是存在于瀏覽器最核心也最基本的安全功能。
所謂同源指的是:協議、域名、端口號必須一致,只要有一個不相同,那么就是“跨源”。
最常見的同源策略是因為域名不同,也就是常說的“跨域”。一般分為請求跨域和頁面跨域。
常用方法是CORS和代理轉發。
對于CORS請求,瀏覽器將其分成兩個類型:簡單請求和非簡單請求。
簡單請求符合下面 2 個特征:
任意一條要求不符合的即為非簡單請求。常見是自定義 header,例如將token 設置到請求頭。
在處理非簡單請求時,瀏覽器會先發出“預檢請求”,預檢請求為OPTIONS方法,以獲知服務器是否允許該實際請求,避免跨域請求對服務器產生預期外的影響。如果預檢請求返回200允許通過,才會發真實的請求。
預檢請求并非每次都需要發送,可以使用 Access-Control-Max-Age 設置緩存時間進行優化,減少請求發送。
增加頭部設定,頭部內容以鍵值對的形式設置。請求頭部通過 Accept 字段來告訴服務端可以接收的文件類型,響應頭部再通過 Content-Type 字段來告訴瀏覽器返回文件的類型。
HTTP1.0中每次通信都需要經歷建立連接、傳輸數據和斷開連接三個階段,這會增加大量網絡開銷。
HTTP1.1增加持久化連接,即連接傳輸完畢后,TCP連接不會馬上關閉,而是其他請求可以復用連接。這個連接保持到瀏覽器或者服務器要求斷開連接為止。
HTTP1.1雖然減少連接帶來的性能消耗,但是請求最大并發受到限制,同一域下的HTTP連接數根據瀏覽器不同有所變化,一般是6 ~ 8個。而且一個TCP連接同一時刻只能處理一個請求,當前請求未結束之前,其他請求只能處于阻塞狀態。
HTTP2.0中增加“多路復用”的機制,不再受限于瀏覽器的連接數限制?;诙M制分幀,客戶端發送的數據會被分割成帶有編號的碎片(二進制幀),然后將這些碎片同時發送給服務端,服務端接收到數據后根據編號再合并成完整的數據。服務端返回數據也同樣遵循這個過程。
第一次握手:客戶端向服務端發起連接請求報文,報文中帶有一個連接標識(SYN);
第二次握手:服務端接收到客戶端的報文,發現報文中有連接標識,服務端知道是一個連接請求,于是給客戶端回復確認報文(帶有SYN標識);
第三次握手:客戶端收到服務端回復確認報文,得知服務端允許連接,于是客戶端回復確認報文給服務端,服務端收到客戶端的回復報文后,正式建立TCP連接;
如果是兩次握手,在第二次握手出現確認報文丟失,客戶端不知道服務端是否準備好了,這種情況下客戶端不會給服務端發數據,也會忽略服務端發過來的數據。
如果是三次握手,在第三次握手出現確認報文丟失,服務端在一段時間沒有收到客戶端的回復報文就會重新第二次握手,客戶端收到重復的報文會再次給服務端發送確認報文。
三次握手主要考慮是丟包重連的問題。
第一次揮手:客戶端向服務端發出連接釋放報文,報文中帶有一個連接釋放標識(FIN)。此時客戶端不能再發送數據,但是可以正常接收數據;
第二次揮手:服務端接收到客戶端的報文,知道是一個連接釋放請求。服務端給客戶端回復確認報文,但要注意這個回復報文未帶有FIN標識。此時服務端處于關閉等待狀態,這個狀態還要持續一段時間,因為服務端可能還有數據沒發完;
第三次揮手:服務端將最后的數據發送完畢后,給客戶端回復確認報文(帶有FIN標識),這個才是通知客戶端可以釋放連接的報文;
第四次揮手:客戶端收到服務端回復確認報文后,于是客戶端回復確認報文給服務端。而服務端一旦收到客戶端發出的確認報文就會立馬釋放TCP連接,所以服務端結束TCP連接的時間要比客戶端早一些。
服務端需要確保數據完整性,只能先回復客戶端確認報文告訴客戶端我收到了報文,進入關閉等待狀態。服務端在數據發送完畢后,才回復FIN報文告知客戶端數據發送完了,可以斷開了,由此多了一次揮手過程。
HTTPS之所以比HTTP安全,是因為對傳輸內容加密。HTTPS加密使用對稱加密和非對稱加密。
對稱加密:雙方共用一把鑰匙,可以對內容雙向加解密。但是只要有人和服務器通信就能獲得密鑰,也可以解密其他通信數據。所以相比非對稱加密,安全性較低,但是它的效率比非對稱加密高。
非對稱加密:非對稱加密會生成公鑰和私鑰,一般是服務端持有私鑰,公鑰向外公開。非對稱加密對內容單向加解密,即公鑰加密只能私鑰解,私鑰加密只能公鑰解。非對稱加密安全性雖然高,但是它的加解密效率很低。
CA證書:由權威機構頒發,用于驗證服務端的合法性,其內容包括頒發機構信息、公鑰、公司信息、域名等。
對稱加密不安全主要是因為密鑰容易泄露,那只要保證密鑰的安全,就可以得到兩全其美的方案,加解密效率高且安全性好。所以HTTPS在傳輸過程中,對內容使用對稱加密,而密鑰使用非對稱加密。
HTTP 緩存包括強緩存和協商緩存,強緩存的優先級高于協商緩存。緩存優點在于使用瀏覽器緩存,對于某些資源服務端不必重復發送,減小服務端的壓力,使用緩存的速度也會更快,從而提高用戶體驗。
強緩存在瀏覽器加載資源時,先從緩存中查找結果,如果不存在則向服務端發起請求。
HTTP/1.0 中可以使用響應頭部字段 Expires 來設置緩存時間。
客戶端第一次請求時,服務端會在響應頭部添加 Expirss 字段,瀏覽器在下一次發送請求時,會對比時間和Expirss的時間,沒有過期使用緩存,過期則發送請求。
HTTP/1.1 提出了 Cache-Control 響應頭部字段。
一般會設置 max-age 的值,表示該資源需要緩存多長時間。Cache-Control 的 max-age 優先級高于 Expires。
協商緩存的更新策略是不再指定緩存的有效時間,而是瀏覽器直接發送請求到服務端進行確認緩存是否更新,如果請求響應返回的 HTTP 狀態為 304,則表示緩存仍然有效。
Last-Modified 和 If-Modified-Since 對比資源最后修改時間來實現緩存。
ETag 和 If-None-Match 對比資源哈希值,哈希值由資源內容計算得出,即依賴資源內容實現緩存。
跨站腳本(Cross Site Scripting,XSS)指攻擊者在頁面插入惡意代碼,當其他用戶訪問時,瀏覽會器解析并執行這些代碼,達到竊取用戶身份、釣魚、傳播惡意代碼等行為。一般我們把 XSS 分為反射型、存儲型、DOM 型 3 種類型。
反射型 XSS 也叫“非持久型 XSS”,是指攻擊者將惡意代碼通過請求提交給服務端,服務端返回的內容,也帶上了這段 XSS 代碼,最后導致瀏覽器執行了這段惡意代碼。
反射型 XSS 攻擊方式需要誘導用戶點擊鏈接,攻擊者會偽裝該鏈接(例如短鏈接),當用戶點擊攻擊者的鏈接后,攻擊者便可以獲取用戶的 cookie 身份信息。
案例:
服務端直接輸出參數內容:
<? php
$input=$_GET["param"];
echo "<div>".$input."</div>";
惡意代碼鏈接:
http://www.a.com/test.php?param=<srcipt src="xss.js"></script>
存儲型 XSS 也叫“持久型XSS”,會把用戶輸入的數據存儲在服務端,這種XSS具有很強的穩定性。
案例:
比如攻擊者在一篇博客下留言,留言包含惡意代碼,提交到服務端后被存儲到數據庫。所有訪問該博客的用戶,在加載出這條留言時,會在他們的瀏覽器中執行這段惡意的代碼。
DOM 型 XSS 是一種特殊的反射型 XSS,它也是非持久型 XSS。相比于反射型 XSS,它不需要經過服務端,而是改變頁面 DOM 來達到攻擊。同樣,這種攻擊方式也需要誘導用戶點擊。
案例:
目標頁面:
<html>
<body>hello</body>
</html>
<script>
let search=new URLSearchParams(location.search)
document.write("hello, " + search.get('name') + '!')
</script>
惡意代碼鏈接:
http://www.a.com/test.index?name=<srcipt src="xss.js"></script>
CSRF 攻擊就是在受害者毫不知情的情況下以受害者名義偽造請求發送給受攻擊站點,從而在并未授權的情況下執行在權限保護之下的操作。CSRF 并不需要直接獲取用戶信息,只需要“借用”用戶的登錄信息相關操作即可,隱蔽性更強。
案例:
假設現在有一個博客網站,得知刪除博文的 URL 為:
http://blog.com?m=delete&id=123
攻擊者構造一個頁面,內容為:
<img src="http://blog.com?m=delete&id=123"></img>
攻擊者偽裝該網站鏈接并誘導用戶進行點擊,用戶恰好訪問過 blog.com,與該網站的 cookie 身份驗證信息還未過期。這時進入攻擊者的網站,img 發起請求,請求里攜帶上cookie,成功刪除博文。但是對于用戶是無感知的,當用戶返回到博客時會發現博文不見了,而這個請求是屬于合法請求,因為攻擊者借用受害者的身份信息進行操作。
攻擊者創建一個網頁利用 iframe 包含目標網站,然后通過設置透明度等方式隱藏目標網站,使用戶無法察覺目標網站的存在,并且把它遮罩在網頁上。在網頁中誘導用戶點擊特定的按鈕,而這個按鈕的位置和目標網站的某個按鈕重合,當用戶點擊網頁上的按鈕時,實際上是點擊目標網站的按鈕。
if (top.location !=location) {
top.location=self.location
}
轉自 https://www.cnblogs.com/chanwahfung/p/13616849.html
超級熱鍵可通過簡單編程 —— 自動化完成復雜操作,提升效率。
本教程需要一個很小的開源軟件 ImTip ( 體積 639 KB ),了解 ImTip 請點擊:電腦必備:通用輸入法狀態提示,再也不怕按錯,再也不用看右下角
請右鍵點開 ImTip 托盤菜單,然后點擊「管理超級熱鍵」:
然后將熱鍵配置改為如下代碼,并且勾選「啟用超級熱鍵」,再點擊「保存」按鈕使熱鍵生效。
//大寫金額、日期、時間
["Ctrl+$"]=function(hFocus){
win.dlg.chineseNumber().show();
};
//打開調色器
["Ctrl+#"]=function(hFocus){
//創建選色器
var dlg=win.ui.ctrl.pick();
dlg.show();
//置頂
win.setTopmost(dlg.hwnd,true);
};
上面的配置定義了 "Ctrl+$","Ctrl+#" 這兩個熱鍵。中括號里是按鍵名字符串,等號的后面指定要輸入的文本或者需要執行的函數對象。
以上熱鍵配置將自動轉換為以下 aardio 代碼,然后編譯執行:
//導入超級熱鍵
import key.hotkey;
//創建超級熱鍵
var superHotkey=key.hotkey(winform);
//批量加載熱鍵配置表
superHotkey.loadTable({
["Ctrl+$"]=function(hFocus){
win.dlg.chineseNumber().show();
};
["Ctrl+#"]=function(hFocus){
var dlg=win.ui.ctrl.pick();
dlg.show();
win.setTopmost(dlg.hwnd,true);
};
});
超級熱鍵配置其實就是一個表對象 ( table )。每個熱鍵配置由等號分隔的鍵值對組成,等號前面指定熱鍵,等號后面用一個函數指定要執行的代碼。多個熱鍵配置使用分號隔開。
如果不會編程沒關系,下面會提供可直接復制粘貼的范例,建議先看幾個范例 —— 再看一下:aardio 編程語言快速入門——語法速覽
下面看一下 Ctrl + $ 熱鍵運行效果:
Ctrl + # 熱鍵運行效果:
1、如果首個按下的鍵不是控制鍵,則不必同時按住多個鍵。如果按下的鍵是已注冊的熱鍵前半部分,則阻止當前按鍵繼續發送。如果繼續按鍵已不符合任何熱鍵,則釋放已捕獲的按鍵并按原順序重新發送。
2、如果首次按住的是控制鍵( CTRL,ALT,SHIFT,WIN 之一),則必須同時按住多個鍵才算已完成熱鍵。如果這樣同時按住的多個鍵是一個已完成的熱鍵,但同時又是其他熱鍵的前半部分,則必須放開所有鍵才會生效。
3、如果注冊單個控制鍵熱鍵,并且加上 @ 前綴,則放開該鍵(且中間沒有按其他鍵)才算完成熱鍵。
4、超鍵熱鍵中任何鍵名都只表示該鍵名所在的按鍵,不區分上檔鍵。例如熱鍵 "~hi" 指連續按 3 個鍵,其中的 ~ 鍵不必按 Shift + ~。
5、超級熱鍵會檢測按鍵順序,["Ctrl+Shift"] 與 ["Shift+Ctrl"] 是不同的熱鍵。如果希望不同順序觸發同一個回調函數,則需要添加多個熱鍵配置。
示例:
//按連續按 3 個鍵,每個鍵都要放開。
["~AA"]=function(){
};
//按下Shift不放,再按2下Q。
["SHIFT+Q,Q"]=function(){
};
//按下Ctrl不放,再按K, 然后都放開。
["Ctrl+K"]=function(){
};
//按下Ctrl不放,再按2次K。
//因為不是其他熱鍵的前半部分,不需要等待放開。
["Ctrl+K,K"]=function(){
};
//表示按下 Shift 鍵再放開,中間不按其他鍵,
//通常不會阻止 Shift 切換輸入法狀態的默認熱鍵。
["@Shift"]=function(){
};
運行 「aardio 自帶范例 > Windows 窗口 > 快捷鍵 > 超級熱鍵」看下效果:
熱鍵回調函數返回 true 表示允許系統繼續發送按鍵,否則取消該按鍵,不再繼續發送。
例如把斜杠“/”改為頓號的熱鍵配置:
["/"]=function(hFocus){
var openState,mode=key.ime.state();//
if( !openState /*&&(mode==3) */ ) return true;
key.sendString("、")
};
上面的代碼檢測到當前不是中文標點就執行 return true 允許系統繼續發送按鍵。
如果熱鍵回調函數返回一個函數對象,則取消該按鍵不再發送,并在返回函數以后異步執行返回的函數對象。
例如輸入法糾錯熱鍵配置就是這樣做的:
["Ctrl+,"]=function(hFocus){
//... 省略其他代碼
//通過返回函數異步執行耗時操作
return function(){
key.combine("SHIFT","HOME");
key.combine("CTRL","C");
//... 省略其他代碼
};
};
超級熱鍵基于低級鍵盤鉤子,在熱鍵回調中不允許做耗時操作(在熱鍵回調中返回異步執行的函數則不受限制)。
//運行計算器
["~calc"]=function(hFocus){
process.execute("calc.exe")
};
//運行word
["~word"]=function(hFocus){
var word=com.CreateObject("Word.Application")
if(word)word.Visible=true;
};
["Ctrl+P"]=function(){
var dpSchemes=tsfInput.getDoublePinyinSchemes();
tsfInput.enableoublePinyinScheme(dpSchemes.default===null);
}
["Ctrl+."]=function(hFocus){
key.up("Ctrl"); //先把已經按下的鍵彈起來
key.combine("CTRL","A"); //換成別的鍵,具體看 aardio 庫函數文檔
return false; //阻止按鍵事件
};
[";"]=function(hFocus){
if( winex.msCandidate.isVisible() ){
key.send(" ;")
}
else return true;
};
["SHIFT+BACK"]=function(hFocus){
key.up("SHIFT"); //先把已經按下的鍵彈起來
key.combine("CTRL","Z")
};
["/"]=function(hFocus){
var openState,mode=key.ime.state();//
if( !openState /*&&(mode==3) */ ) return true;
key.sendString("、")
};
//增加音量
["Ctrl+F6"]=function(hFocus){
key.press("VOLUME_UP");
}
//降低音量
["Ctrl+F7"]=function(hFocus){
key.press("VOLUME_DOWN");
}
//切換靜音
["Ctrl+F8"]=function(){
key.press("VOLUME_MUTE");
}
["~date"]=function(hFocus){
var zh=string.chineseNumber('〇一二三四五六七八九');
key.sendString(zh.date()); //改為 zh.time() 輸出大寫的當前時間
};
注意超鍵熱鍵中任何鍵名都只表示該鍵名所在的按鍵,不區分上檔鍵。所以 ~ 鍵不需要同時按 Shift + ~ 。
["CAPSLK"]=function(hFocus){
key.ime.setOpenStatus(false);
key.ime.setConversionMode(0);
return true;
};
["RSHIFT"]=function(hFocus){
key.ime.setOpenStatus(false);
key.ime.setConversionMode(0);
};
["LSHIFT"]=function(hFocus){
var openState,mode=key.ime.state();
if( openState && !key.ime.capital() ) return true; //當前已經是中文輸入模式,不改變默認行為
key.up("SHIFT");//先放開 SHIFT 鍵
//如果是大寫狀態,切換為小寫
if(key.ime.capital()) key.press("CAPSLK")
//英文直接切中文 + 中文標點
key.ime.setOpenStatus(true); //打開輸入法
key.ime.setConversionMode(1|0x400); //切換到中文狀態,這一步不能省略
//再次嘗試用鍵盤切換中文標點,這一步不能省略
key.combine("CTRL",".");
//現在再次檢測中文標點狀態
var openState,mode=key.ime.state();
if(mode!=3/*_IME_SYMBOLMODE_SYMBOL*/){
//說明切換到了英文標點,再切換回去
key.combine("CTRL",".")
}
};
["Ctrl+."]=function(hFocus){
var openState,mode=key.ime.state();
if( openState && !key.ime.capital() ) return true; //當前已經是中文輸入模式,不改變默認行為
key.up("Ctrl");//先放開 Ctrl 鍵
//如果是大寫狀態,切換為小寫
if(key.ime.capital()) key.press("CAPSLK")
//英文直接切中文 + 中文標點
key.ime.setOpenStatus(true); //打開輸入法
key.ime.setConversionMode(1|0x400); //切換到中文狀態,這一步不能省略
//再次嘗試用鍵盤切換中文標點,這一步不能省略
key.combine("CTRL",".");
//現在再次檢測中文標點狀態
var openState,mode=key.ime.state();
if(mode!=3/*_IME_SYMBOLMODE_SYMBOL*/){
//說明切換到了英文標點,再切換回去
key.combine("CTRL",".")
}
};
["Ctrl+,"]=function(hFocus){
var openState,mode=key.ime.state();
if( openState && !key.ime.capital() ) return true; //當前已經是中文輸入模式,不改變默認行為
key.up("Ctrl");//先放開 Ctrl 鍵
//如果是大寫狀態,切換為小寫
if(key.ime.capital()) key.press("CAPSLK")
//英文直接切中文 + 中文標點
key.ime.setOpenStatus(true); //打開輸入法
key.ime.setConversionMode(1|0x400); //切換到中文狀態,這一步不能省略
//再次嘗試用鍵盤切換中文標點,這一步不能省略
key.combine("CTRL",".");
//現在再次檢測中文標點狀態
var openState,mode=key.ime.state();
if(mode!=3/*_IME_SYMBOLMODE_SYMBOL*/){
//說明切換到了英文標點,再切換回去
key.combine("CTRL",".")
}
//通過返回函數異步執行耗時操作(避免系統刪除熱鍵鉤子)
return function(){
key.combine("SHIFT","HOME");//選中當前行
key.combine("CTRL","C");//復制當前行
var line=win.clip.read(); //讀取剪貼板文本
var str=string.match(line,"[a-zA-Z]+$"); //查找尾部連續字母
key.press("RIGHT");//按右方向鍵,取消選區
//如果字符串非空
if(#str){
key.repeat("BACK",#str);//按退格鍵指定次數(取字符串長度)
key.send(str);//字符串轉換為發送按鍵
//key.press("SPACE");//發果需要按空格,請取消前面的注釋符號
}
};
};
["Ctrl+V"]=function(){
var str=win.clip.read();
if(str && string.find(str,"abcd")){
str=string.replace(str,"abcd","");
win.clip.write(str);
}
return true; //執行默認操作
}
[`SHIFT+"`]=function(hFocus){
if( checkImeProcess(hFocus,"Code.exe") ){
return true;//允許繼續發送按鍵
}
var o,s=key.ime.state();
key.sendString(s==3 ? `“”` : `""`);
//與目標窗口共享輸入狀態
winex.attach(hFocus,true);
//設置LSHIFT,RSHIFT 為彈起狀態
key.up("RSHIFT","LSHIFT","SHIFT");
key.setState(false,"RSHIFT","LSHIFT","SHIFT");
//移動光標
key.press("LEFT");
//取消共享輸入狀態
winex.attach(hFocus,false);
}
["Ctrl+,"]=function(hFocus){
import win.reg;
var reg=win.reg("HKEY_CURRENT_USER\Software\Microsoft\InputMethod\Settings\CHS");
var mode=!reg.queryValue("PinyinMixEnable") ? 1 : 0
reg.setDwValue("PinyinMixEnable",mode)
key.ime.changeRequest(0x4090409)
key.ime.changeRequest(0x8040804)
};
["Ctrl+SHIFT+RIGHT"]=function(hFocus){
::User32.SwapMouseButton(!::User32.GetSystemMetrics(23));
}
["`"]=function(hFocus){
var openState,mode=key.ime.state();//用法請查看 aardio 文檔
if(!openState
|| mode !=3 || key.getState("Shift")
|| key.getState("Ctrl")
|| key.getState("CAPSLK") ) {
return true; //允許此按鍵繼續發送
}
key.combine("SHIFT","LEFT"); //向后選一個字
key.combine("CTRL","C"); //復制
key.press("RIGHT"); //取消選中
key.combine("CTRL","V"); //粘貼
};
ImTip 體積雖然小,但已自帶了大量 #aardio# 庫。您也可以下載 aardio ,將 aardio 目錄下的 /lib/ 目錄復制到 ImTip.exe 所在目錄 —— 超級熱鍵就可以使用全部的 aardio 庫了。
也可以在 /lib/ 目錄下新建用戶庫,然后通過 import 語句導入超級熱鍵。
超級熱鍵配置本質是一個表對象 (table),不但可以包含鍵值對,也可以包含數組成員。利用這個特性可在超級熱鍵配置內直接執行代碼。例如添加一個檢測當前窗口啟動程序名的全局函數 checkImeProcess,然后使用該函數檢測目標窗口的啟動程序文件名:
(function(){
var lastFocus,lastPath,lastClass;
checkImeProcess=function(hFocus,exeFile){
if(lastFocus !=hFocus){
lastClass=win.getClass(hFocus);
var tid,pid=win.getThreadProcessId(hFocus);
lastPath=process.getPath(pid);
lastFocus=hFocus
}
return (lastPath && io.splitpath(lastPath).file==exeFile )
}
})();
//引號配對
[`SHIFT+"`]=function(hFocus){
if( checkImeProcess(hFocus,"Code.exe") ){
return true;//允許繼續發送按鍵
}
var o,s=key.ime.state();
key.sendString(s==3 ? `“”` : `""`);
//與目標窗口共享輸入狀態
winex.attach(hFocus,true);
//設置LSHIFT,RSHIFT 為彈起狀態
key.up("RSHIFT","LSHIFT","SHIFT");
key.setState(false,"RSHIFT","LSHIFT","SHIFT");
//移動光標
key.press("LEFT");
//取消共享輸入狀態
winex.attach(hFocus,false);
}
比較常用的按鍵、文本輸入函數:
//發送字符串
key.sendString("這里是要發送的字符串")
//發送按鍵
key.send("/")
//發送按鍵
key.press("ENTER")
//判斷中文輸入法是否打開,打開則執行花括號中的語句
if( key.ime.state() ){ }
更多庫函數用法請參考 aardio 自帶的《庫函數文檔》,或者 aardio 自帶的范例:
aardio 也支持很多第三方編程語言,例如 C語言、C++、C#、Java、Python、R、Javascript、Node.Js、Fortran、VB、PHP、VBScript、PowerShell、NewLISP、AutoLISP、Delphi、FreeBASIC、Ruby、Rust、Julia、Nim、Go 語言、批處理 ...... 用法請參考 aardio 自帶范例。
如果您對超級熱鍵還有任何疑問,歡迎在評論區留言,我會盡快解答。