vue
vue中v-if和v-for的優先級那個更高,應該怎么優化提升性能
1、在vue2.x中v-for的優先級高
2、可以將v-if寫到v-for的外面,也可以將數據在computed計算屬性中進行過濾,在循環計算方法就可以了
3、在vue3.x中v-if的優先級更高-
vue中data為什么必須是函數,而vue根實例是對象
image.png
1、組件中data要不是一個函數的話,那么會進行報錯
2、在多實例的時候,保護狀態不被污染,干擾
3、函數的話,可以讓我們每次調用組件都會創建一個新的data實例
4、要是對象的話,組件調用多次,改變一個,都會進行改變觸發,導致狀態會被污染,干擾
5、根實例跟組件不一樣,是單例,每次new vue只會創建一次 -
vue中key的作用和工作原理
image.png
1、key的作用主要是高效的更新虛擬dom,通過key可以精準的判斷兩個節點是否是同一個,要是沒有加key的話,會認為是相同的,從而進行強制的更新,導致多做一個dom的更新,消耗性能
2、不加的話,會觸發一些隱蔽的bug,還有不加的話,會報錯
3、vue中使用相同標簽名元素過渡切換的時候,也會使用到key的屬性,目的就是為了讓vue可以區分他們,否則vue只會替換內部屬性,而不會觸發過渡效果 你怎么理解vue中的diff算法(需要看看課程中,對diff算法更深的理解)
1、必要性,為了降低組件的watcher,一個組件一個watcher,所以要是用diff
2、深度優先,同層比較(先從根找,有孩子往下找,有的話,繼續,沒有的話,同層比較,之后在看父節點的,另一邊有沒有子節點,有往下找,沒有同層比較)vue組件化的理解
1、高內聚、低耦合,提升代碼的健壯性
2、有屬性props,自定義事件,插槽等,用于組件通信,擴展等
3、可復用性
4、全局組件和局部組件MVVM
1、解決model和view耦合的問題
2、model發生改變會自動更新view,減少dom操作,提高開發效率,可讀性還保持了優越的性能表現vue性能優化有哪些?
1、路由懶加載
2、keep-alive進行緩存頁面
3、v-show和v-if合理的使用
4、大數據列表,使用虛擬滾動列表(vue-virtual-scroll,vue-virtual-scroll-list)
5、有定時器的,組件頁面銷毀前進行清除
6、圖片的懶加載
7、第三方UI組件庫的按需引入
8、將無狀態的UI組件可以標記為函數式組件,在template標簽上添加functional就可以了
9、組件中的方法進行調用多次的時候,使用變量將方法進行保存,這樣調用多次的時候,我們只用了這一次的方法vue3.0的新特性了解
1、更快:虛擬dom的重寫,優化slots的生成,靜態樹的提升,靜態屬性的提升,proxy響應式系統
2、更小:搖樹優化
3、更容易維護:ts+模塊化
4、composition Api-
vuex的使用及其理解
1、模塊state mutations getters action modlues(namespace)
-
vue 組件之間的通信方式
1、props
2、on
3、parent
4、provide/inject
5、listeners
6、ref
7、$root
8、eventbus
9、 vuex-
vue-router保護路由的安全
1、獲取組件實例只有組件內守衛可以進行獲取到,全局守衛,路由獨享都是獲取不到的
image.png
2、觸發順序
image.png
-
vue響應式的理解
1、通過數據響應式加上虛擬DOM和patch算法,可以使我們只需要操作數據就可以達到更新視圖,完全不需要操作dom,從而大大提升開發效率,降低開發難度
2、vue2中的數據響應式機制會根據類型來進行不同的處理,如果是對象的話,使用Obiect.defineProperty()的方式定義數據的攔截和響應,如果是數組的話,通過覆蓋數組原型的方法,擴展他的7個變更方法,使這些方法額外的做更新通知,可以很好的解決了數據響應式的問題,但是也有一些缺點:比如初始化時遞歸 遍歷時會造成性能損失,新增或者是刪除屬性時需要使用vue.set/delete這樣的api才能生效,對于es6的新增map,,set結構不支持等問題
3、為解決這些問題,vue3中利用es6的proxy機制代理響應化的數據,不在需要使用vue.set/delete等api,初始化性能和內存消耗都得到了大幅改善,另外將響應式的代碼單獨抽離為獨立的reactivity包,可以讓我們更加靈活的使用他,我們甚至不要要引入vue就可以進行體驗vue的雙向數據綁定原理
1、首先通過object.defineProperty()來設置和讀取數據
2、通過監聽器Observe監聽所有的屬性,如果屬性發生變化了,就需要告訴訂閱者是否更新,因為訂閱者有很多,此時我們需要Dep專門進行收集訂閱者,然后對監聽器Objserve和watcher之間進行統一的管理
3、當watcher接收到相應的屬性變化,就會進行更新對應的函數
es6的語法總結
深淺拷貝
1、引用類型的數據直接賦值就是淺拷貝
2、基本數據類型直接賦值是深拷貝
3、使用Object.prototype.toString.call()可以檢測到當前數據類型,適用于所有數據類型的檢測,返回'[objec Object]','[objec Array]','[objec String]' 等
4、也可以使用Object.getPrototypeOf()進行檢測出當前的數據是對象還是數組
5、使用Object.create(),創建出的對象或者是數組,都是將對方的屬性,方法放到了原型上,沒有這個屬性可以直接從原型上獲取,有的話就不會再使用原型中的屬性值
6、如果是對象的話,可以使用Object.assign()進行克隆,但是,當前的對象有多層,也就是說對象中還有對象或者是數組,那么里面的對象或者數組就是淺拷貝
7、擴展運算符跟6是一樣的,單層是深拷貝,多層嵌套就是淺拷貝了
8、如果是數組的話,可以使用數組中的方法concat,slice等方法,也可以使用Object.assign(),擴展運算符,情況跟6一樣函數的變量提升
1、函數的變量提升優先于變量的提升
function test(a,b){
console.log(a);//function a(){}
var a=10;
function a(){}
console.log(a);//10
function c(){}
console.log(b);//undefined
var b=function b(){}
console.log(b)//function b(){}
}
test(1);
var a=121;
console.log(a);//121
/**
* 函數的變量提升:
* 1、函數的變量提升比變量的提升優先級高,在變量的前面
* 2、找形參和變量,相同的只需要一個就可以了,值為undefined
* a:undefined b:undefined
* 3、將實參和形參統一
* a:1 b:沒有傳遞實參,值為undefined
* 4、在函數中找函數,將值賦值給函數體
* a:function a(){} b:由于函數中的b是字面量函數,所以值還是undefined
* 5、在函數的內部開始運行
* a:function a(){}----10
* b:undefined-----function b(){}
*/
var a = 10 ;
function a(){
console.log(a);//function a(){}
a=20;
function a(){}
console.log(a)//20
}
a();
console.log(a)//10
a = 20;
var a = 10;
console.log(a);//10 本來a是全局變量20,然后下面有聲明了一下,a=10;所以最終輸出為10
-
字符串模板
1、可以進行簡單的運算
2、比字符串拼接好使
// todo 字符串模板的使用
const a = 1;
const b = 2;
const c = `${a+b}`
console.log(c);//3
const d = 18;
const e = `小明今年$rkmfppl歲`
const f = '小明今年'+d+'歲'
console.log(e);//小明今年18歲
console.log(f);//小明今年18歲
- 判斷當前是否有這個字符串
// 判斷當前是否有這個字符串
const str = '我現在正在練習es6';
const str1 = '現在'
const str2 = '我'
const str3 = 'es6'
if(str.indexOf(str1)>-1) console.log('indexOf有返回當前下標,沒有返回-1---',str.indexOf(str1));//indexOf有返回當前下標,沒有返回-1
if(str.includes(str1)) console.log('includes有返回true,沒有返回false---',str.includes(str1));//includes有返回true,沒有返回false
if(str.startsWith(str2)) console.log('startsWith頭部有返回true,沒有返回false---',str.startsWith(str2));//startsWith頭部有返回true,沒有返回false
if(str.endsWith(str3)) console.log('endsWith尾部有返回true,沒有返回false---',str.endsWith(str3));//endsWith尾部有返回true,沒有返回false
- 字符串重復多次的寫法(復制字符串)
// 字符串重復多次
const str = '重復我10次'
console.log(str.repeat(10));//重復我10次重復我10次重復我10次重復我10次重復我10次重復我10次重復我10次重復我10次重復我10次重復我10次
let str1 = ''
for (let i = 0; i < 10; i++) {
str1+=str;
}
console.log(str1);
-
Array.from()
1、可以將對象轉化為數組
// 使用Array.from(),將對象轉化為數組
const obj = {
0:'key要是用數字',
1:'必須有length這個屬性,否則轉化不了',
length:2
}
2、可以將偽數組轉化為數組
3、可以將set,map,string等數據結構轉化為數組
4、第二個參數是一個回調函數,相當于數組的map方法,返回一個新的數組,將第一個參數中的數據進行處理
-
Array.of
1、這個方法不管你是什么數據和類型,都會創建一個新的數組
2、當我們使用new Array(10);進行聲明的時候,這個時候數組的長度是10,在進行傳遞參數的時候,就會產生一些問題
const arr = new Array(10);
console.log(arr);//[empty × 10]
arr.push(20);
console.log(arr);//[empty × 10, 20]
3、此時代碼中的數組長度就是11了,在添加的時候,前面都是10個空的
4、而我們使用Aarray.of就可以解決上面的問題了
const arr = Array.of(10);
console.log(arr);//[10]
arr.push(20);
console.log(arr);//[10, 20]
- Array.fill()填充
let arr =[1,2,4,5,6,7];
arr.fill(10);//一個參數默認將數組中的數據全部改變為10
//第一個參數是填充的參數,第二個參數是填充的位置,第三個下標是填充的個數,從下標0開始算起
// 只能是數組內的長度,不能超過數組內的下標或者是長度,
arr.fill('3',0,1);//["3", 2, 4, 5, 6, 7]
arr.fill('3',1,2);//----[1, "3", 4, 5, 6, 7]
arr.fill('3',1,4);//---- [1, "3", "3", "3", 6, 7],第三個參數是4個但是從下標1開始填充,又是從0開始計算
console.log(arr);
-
判斷對象或者是數組中是否有某個值
1、對象中的檢測某個屬性使用in或者是object.hasOwnProperty()都可以檢測,但是in是從對象的原型和原型鏈上查找,不太推薦使用
let obj = {
id:1,
title:'1111'
}
console.log('id' in obj);
console.log(obj.hasOwnProperty('id'));
let arr =[1,2,4,5,6,7];
console.log( 1 in arr);
console.log(arr.indexOf(1)>-1);
console.log(arr.includes(1));
const index = arr.findIndex(item=>item===1)
console.log(index!==-1)
....
- object.is()對象比較
let obj = {id:1,title:'1111'};
let obj2 = {id:1,title:'2222'};
console.log(obj.id === obj2.id);//true
console.log(Object.is(obj.id,obj2.id));//true
-
objec.is和===區別
1、===為同值相等,is()為嚴格相等
console.log(+0 === -0); //true
console.log(NaN === NaN ); //false
console.log(Object.is(+0,-0)); //false
console.log(Object.is(NaN,NaN)); //true
- reduce簡單的計算及數組去重
//reduce 簡單的計算,第一個參數是我們傳遞的初始值,莫有的話,默認是數組的第一個數
//reduce 第二個參數是數組中的每一項
//reduce 第三個參數是數組本身
let arr = [1,1,1,1,2,2,3,4,5,5,6,7,8,9];
// 計算出現的次數
let arr1 = arr.reduce((pre,next)=>{
if(next in pre){//可以將in替換為hasOwnProperty()
pre[next]+=1;
}else{
pre[next] = 1;
}
return pre
},{})
console.log(arr1);//返回的是對象
arr1.length = 10;//給當前的對象添加一個length屬性,可以轉化為數組
console.log(Array.from(arr1));
// 數組去重
let arr = [1,1,1,1,2,2,3,4,5,5,6,7,8,9];
// 使用雙循環進行判斷,當前的數值跟下一個數值進行比較,是否相同,相同的話進行去重
for (let i = 0; i < arr.length; i++) {
for (let j = i+1; j < arr.length; j++) {
if(arr[i]===arr[j]){
arr.splice(i,1);
i--;
j--;
}
}
}
console.log(arr);
const arr1 = [...new Set(arr)];//使用new Set 進行去重
console.log(arr1);
let arr2 = arr.reduce((pre,next)=>{
// if(!pre.includes(next)){//當數組中沒有這個值的時候進行添加就好
// pre.push(next);
// }
// indexOf()有的話返回下標,沒有的話返回-1,所以沒有的時候進行添加
if(pre.indexOf(next)===-1){
pre.push(next);
}
return pre;
},[])
console.log(arr2);
// 簡單的計算數組中的和
let arr = [1,2,3,4,5,6,7];
let s = arr.reduce((pre,next)=>{
return pre + next
},0)
console.log(s);
- new Proxy()簡單的代理
let obj = {
id:1,
title:'練習proxy代理'
}
let obj2 = new Proxy(obj,{
get(target,key){
console.log(`你正在讀取obj中的${key}屬性`);
// return target[key];//必須要有返回值
return Reflect.get(target,key);//一般我們推薦使用這種映射的方式進行返回
},
set(target,key,val){
console.log(`你正在對obj的${key}進行設置`);
// target[key] = val;
// return target[key];
return Reflect.set(target,key,val);
},
has(target,key){
console.log(`obj中是否有這個${key}屬性`);
return Reflect.has(target,key);
},
ownKeys(target){
return Reflect.ownKeys(target);
},
})
console.log(obj2.id);
obj2.name = '張三';
obj2.id = 333;//設置屬性
console.log(obj2.name);
console.log(obj2.id);//獲取屬性
console.log('id' in obj2);//這種方法進行調用has方法
console.log(Object.keys(obj2))//返回所有屬性
-
web安全攻擊手段有哪些?
1、XSS跨站腳本攻擊- 定義:就是攻擊者可以在我們的網站中嵌入惡意腳本,在進行分享出去,當用戶進行瀏覽的時候,會被攻擊或者是泄漏隱私
解決辦法:設置httpOnly,防止獲取我們的cookie信息。可以將我們頁面中的input框等可輸入的進行代碼,特殊符號的轉義,還有就是長度進行限制
2、CSRF跨站請求偽造定義:就是我們使用localhost:3000進行訪問,攻擊者可以在localhost:3000進行訪問我們的數據及惡意的攻擊
解決辦法:使用token進行驗證。http頭中自定義屬性進行驗證
TCP的三次連接
直接舉例說明
小明——客戶端,小紅——服務端
1、小明給小紅打電話,接通了之后,小明說喂,能聽到嗎,這就是相當于連接建立了
2、小紅給小明回應,能聽到,你能聽到我說話嗎,這就相當于是請求響應
3、小明聽到小紅的回應后,好的,這相當于是連接確認,在這之后小明和小紅就可以通話/交換信息了TCP的四次揮手
直接舉例說明
1、小明對小紅說,我所有的東西說完了,我要掛電話了
2、小紅說,收到,我這邊還有一些東西沒說
3、經過若干秒后,小紅也說完了,小紅說,我說完了,現在可以掛斷了
4、小明收到消息后,又等了若干時間后,掛斷了電話地址欄輸入URL發生了什么
首先輸入URL之后,去查找域名是否被本地DNS緩存,解析域名,tcp的連接(三次握手,四次揮手),請求服務器,服務器響應,HTML進行重繪和回流,渲染頁面
template 和 JSX的區別
- 首先他們兩個都是render的一種表現形式,最終都會編譯成render進行渲染
- template更加符合我們的視圖,結構分離
- JSX相對于template更加的靈活
SPA單頁面的優缺點
- 優點:
1、用戶體驗好
2、快
3、內容的改變不用重新加載整個頁面,避免了不必要的跳轉到渲染 - 缺點:
1、首屏加載慢(loading進行解決/骨架屏)
2、對seo不友好
Vue中的data中某一個屬性值發生改變,視圖會立即更新渲染嗎?
- 不會立即同步更新渲染
- vue在更新dom的時候是異步執行的,只要偵聽到數據的變化,vue將開啟一個隊列,并緩沖在同一個事件循環中發生的所有數據的變更
- 如果同一個watcher被多次進行觸發,只會被推入到隊列中一次。(這種在緩沖時去除重復的數據對于避免不重要的計算和dom操作是非常重要的)
- 然后在下一次的事件循環tick中,vue刷新隊列并執行實際的工作(已經進行去重的數據)
vue中的watch,computed和methods的區別
- 首先methods沒有緩存,只要調用就會進行觸發
- computed 有緩存,只有里面的數據發生了改變,才會進行更新,不支持異步,只支持data,props
- watch沒有緩存,支持異步的數據,只要數據發生變化就會觸發,有兩個參數新老值
如何減少重繪和回流
- 重繪:就是頁面的顏色啥的進行改變,不會影響頁面的布局
- 回流:就是頁面的布局發生改變,需要重新進行計算和渲染
- 使用transfrom替代top等
- 使用visibility替換display:none;(因為前者只會引起重繪,后者會觸發回流)
url輸入到渲染的過程?
- url解析(是否是合法的http)
- DNS查詢(將http解析為ip地址)
- tcp鏈接(三次揮手,4次握手)
- 向服務器發送請求
- 服務器響應
- 頁面進行渲染:
1、解析html,構建dom樹
2、解析css,生成css樹
3、計算html,css元素的尺寸,位置
4、進行繪制
rsa加密是非對稱加密
- 非對稱加密:就是需要我們知道公鑰和私鑰,然后通過算法進行加密
- 對稱加密:就是我們只知道公鑰,私鑰是我們請求的時候進行傳遞過來的,但是要是被劫持了,那么就會有泄露的風險
- 總結:一般都是使用非對稱加密,安全
文件的大小
- 文件的大小我們可以通過file文件中的屬性size進行指導,在通過slice進行截取切割,
- 也可以進行判斷文件的大小
- 要是文件要判斷是不是png圖片,使用二進制流讀取文件前六位,每個文件都是不同的
Vue組件模板會在某些情況下受到HTML的限制,如在table表格中使用組件是無效的,常見的還有在ul,ol,select中使用組件也是會被限制的
- 我們為了解決在table表格標簽中寫上我們的組件,使用is屬性來進行掛載
- 我們也可以使用is這個屬性,進行動態的渲染組件,寫一個公共的部分,通過綁定is來實現不同的地方進行渲染組件
<table><thead is="組件名"></thead></table>
插槽
默認插槽:
1、就是在組件中可以進行寫入一些標簽,可以在組建的內部進行渲染
2、直接在組件的內部使用<slot></slot>就可以了具名插槽:
1、就是我們要在不同的地方,顯示不同數據,這個時候就要顯示了
2、使用name在slot標簽上進行起不同的名字
3、在使用組件插槽的時候,在標簽上顯示不同的名字就行slot='名字'作用域插槽:
1、就是子組件傳遞數據給父組件,然后在子組件中進行顯示
2、在子組件的slot標簽上動態的綁定我們需要的數據,或者是靜態的傳遞
3、在父組件的子組件標簽中使用template標簽進行渲染,在template標簽上使用v-slot=“隨便起一個名字(aa)”,在里面就是aa.傳遞過來的數據就可以了this.$slot.default 是獲取默認的插槽數據
this.$slot.head 獲取頭部slot的數據
vue3.0的語法的一些總結
vue2.0 ======================= vue3.0
beforeCreatec ================ setup
created ====================== setup
beforeMount ================== onBeforeMount
mounted ====================== onMounted
beforeUpdate ================= onBeforeUpdate
updated ====================== onUpdated
beoreDestory ================= onBeforeUnmount
destory ====================== onUmmounted
flex彈性盒模型布局
- display:flex; //申明彈性盒模型,默認水平排列
- flex-direction:row | row-reverse | column | column-reverse //排列的方向,水平,水平倒序, 垂直, 垂直倒序
- flex-wrap; nowrap | wrap | wrap-reverse //默認不換行, 換行, 換行倒序
- flex-flow: <flex-direction> || <flex-wrap> //這是排列和換行的簡寫 row nowrap 默認水平不換行
- justify-content: flex-start | flex-end | center | space-between | space-around //水平的對齊方式,頭部(左邊)對齊,尾部(右邊)對齊 中間 兩端 均勻對齊
- align-item: flex-start | flex-end | center | baseline | stretch //垂直對齊方式, 頂部(上) 尾部 (下) 中間 文字對齊 上下占滿空間(沒有設置高度或auto,占滿上下空間)
- align-content: flex-start | flex-end | center | space-between | space-around | stretch ///這個屬性是對多個盒子進行垂直對齊的方式
- order: 1 - 9999999 //數值越小,排列順序越往前
- flex-grow:0-1 //填充,默認是0,有剩余空間,也不放大
- flex-shrink: 1 // 縮放默認為1,空間不足,可以進行縮小,如果其他都是1,當前為0,其他進行縮小,當前不變
- flex-basis: auto //默認auto 本來的大小, 分配多余空間之前占據的大小
- flex: <flex-grow> || <flex-shrink> || <flex-basis> //三者簡寫
箭頭函數和普通函數的區別
1、寫法不同,箭頭函數使用()=>{} ,普通函數使用function(){}
2、箭頭函數都是匿名函數,普通函數可以是匿名函數也可以是命名函數
3、箭頭函數不能使用構造函數,使用new進行聲明
4、箭頭函數本身是沒有this的,聲明時可以捕獲上下文的this供自己使用,普通函數的this總是指向調用它的對象
5、箭頭函數沒有arguments對象,普通函數有
6、箭頭函數的this永遠指向其上下文的this,任何方法都改變不了它的指向,如call,apply,bind都不行
vue和react的相同點和不同點
- 相同點
1、使用虛擬dom
2、都是響應式數據和組件化 - 不同點
1、vue是雙向數據綁定
2、react 是單向數據流
3、vue內置dalailng
原生的上拉加載和下拉刷新
-
上拉加載
image.png - 看到上面的圖片,大致的意思就是scrollTop+clientHeight >= scrollHeight-觸底的距離
let clientHeight = document.documentElement.clientHeight; //瀏覽器高度
let scrollHeight = document.body.scrollHeight;
let scrollTop = document.documentElement.scrollTop;
let distance = 50; //距離視窗還用50的時候,開始觸發;
if ((scrollTop + clientHeight) >= (scrollHeight - distance)) {
console.log("開始加載數據");
}
- 下拉刷新
- 首先要經歷3個過程,touchstart,touchmove,touchend,確定用戶開始觸發時候的y軸距離,移動的距離-開始的距離,結束的時候,在進行請求或者是做一些其他的操作
<main>
<p class="refreshText"></p>
<ul id="refreshContainer">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
<li>555</li>
...
</ul>
</main>
- touchstart
var _element = document.getElementById('refreshContainer'),
_refreshText = document.querySelector('.refreshText'),
_startPos = 0, // 初始的值
_transitionHeight = 0; // 移動的距離
_element.addEventListener('touchstart', function(e) {
_startPos = e.touches[0].pageY; // 記錄初始位置
_element.style.position = 'relative';
_element.style.transition = 'transform 0s';
}, false);
- touchmove
_element.addEventListener('touchmove', function(e) {
// e.touches[0].pageY 當前位置
_transitionHeight = e.touches[0].pageY - _startPos; // 記錄差值
if (_transitionHeight > 0 && _transitionHeight < 60) {
_refreshText.innerText = '下拉刷新';
_element.style.transform = 'translateY('+_transitionHeight+'px)';
if (_transitionHeight > 55) {
_refreshText.innerText = '釋放更新';
}
}
}, false);
- touchend
_element.addEventListener('touchend', function(e) {
_element.style.transition = 'transform 0.5s ease 1s';
_element.style.transform = 'translateY(0px)';
_refreshText.innerText = '更新中...';
// todo...
}, false);
- vue中父組件監聽子組件的生命周期
- 方法一
// Parent.vue
<Child @mounted="doSomething"/>
// Child.vue
mounted() {
this.$emit("mounted");
}
- 方法二
// Parent.vue
<Child @hook:mounted="doSomething" ></Child>
doSomething() {
console.log('父組件監聽到 mounted 鉤子函數 ...');
},
// Child.vue
mounted(){
console.log('子組件觸發 mounted 鉤子函數 ...');
},
// 以上輸出順序為:
// 子組件觸發 mounted 鉤子函數 ...
// 父組件監聽到 mounted 鉤子函數 ...
- 可以在mounted中監聽到beforeDistory生命周期進行清除定時器
- 參考鏈接https://blog.csdn.net/T_shiyi/article/details/108668263
- 重繪和回流
- 重繪:一般頁面中的背景,顏色等變化
- 回流:頁面的寬度,高度等發生變化,需要進行重新計算,渲染
- 如何減少回流:
1、用translate替代top改變
2、使用opacity代替visibility
3、不要使用table布局,一個小的改動都需要進行重新計算渲染 - this的指向問題
- 關于這個this的指向問題,要靠自己的理解,不用死記硬背,但是要多注意看題