leowangj / blog Goto Github PK
View Code? Open in Web Editor NEW紀錄學習文章
紀錄學習文章
<body>
<div id="app">
</div>
</body>
let vm = new Vue({
el: '#app',
template: '<p>hello</p>'
})
通常我們在掛載Vue元素時會這樣寫,但為什麼我們不能將el掛載在body或html元素上呢?
原因是你的template裡面的html會替換掉
而且在vue源碼中也有做相關判斷,避免將el掛載在html與body上
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
一般我們在導入vuex的module時是使用import的方式一個一個去手動導入,但當每次新增一個module我們就必須記得去import,對於工程的角度來說能夠自動導入的話是最方便的,也不會忘記去導入。
手動添加module:
//moduleA.js
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
//moduleB.js
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
// index.js
import moduleA from './moduleA'
import moduleB from './moduleB'
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
所幸webpack中這個方法能夠讓我們自動導入檔案
require.context接受三個參數
usage:
//將遍歷當前目錄下的modules文件夾下的所有js檔案,包含子目錄
const modulesFiles = require.context('./modules', true, /\.js$/)
將modulesFiles打印出來後會發現requrie.context返回的是一個函數,這函數可以接受一個參數request且該函數包含了三個屬性resolve、keys、id,而此地方只會用到keys。
// index.js
const modulesFiles = require.context('./modules', true, /\.js$/)
// 將匹配成功的模組名稱陣列組合成我們想要的物件
// modulesFiles.keys() => ["./moduleA.js","./moduleB.js"]
const API = modulesFiles.keys().reduce((modules, modulePath) => {
// 將模組路徑名稱正則成想要的名稱
// ex: ./moduleA.js => moduleA
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
// 取得模組資料
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules
}, {})
export default API
此時index.js 格式將會是
{
moduleA:{
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
},
moduleB:{
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
}
參考:
待完成
函數劫持意思是在要調用某個函數時,額外的添加一些我們想要執行的功能後才會執行調用的函數,
用意是你依舊可以使用原先函數的功能,並且在此功能上再添加一些想要做的事情。
函數劫持的原理:
使用變量先保存原先的函式
重新複寫要劫持的函式
在覆寫的函式內重新呼叫使用變量儲存的原先函式
例子:
var test = function() { console.log('原本的函式')}
var temp = test;
test = function() {
console.log('添加的判斷')
temp()
}
test()
通過上面例子,我們會打印出兩個console.log,證明了我們會執行原有的函式,以及後來添加的方法
在功能頻繁觸發的情況下,當只有在功能觸發的時間間隔超過我們所指定的時間,才會觸發此功能
簡單來說就是某個函數在某個指定的時間內不管觸發多少次, 都只會執行最後一次的觸發。
舉例來說: 我們設了一個五秒的指定時間, 則在這五秒內每觸發了一次函式,就會重新計算這五秒, 直到最後一次五秒內都沒有重新觸發函式時就會執行任務。
常用場景:
有時候我們只是想知道最後的結果,但上述這些方法在發生變動時會頻繁觸發,因此我們需要使用防抖來節省資源。
例如註冊帳號時,我們需要去檢驗此帳號是否已經被註冊過了,但需求方希望可以在使用者打字時就可以監測,而不是輸入框失去焦點時才判斷。
這時我們可能會這樣寫
$(".register").on("input", function(){
// 請求後端來判斷是否使用者已經有被註冊,但這個函式不是我們主要探討的部分
handleRegisterName(this.value)
})
當今天我想要將帳號名稱取為leowang時,會發現我們每次輸入文字都會觸發handleRegisterName這個方法,這樣就多請求許多次不必要的資源。
這時我們不就可以使用防抖來解決這個問題了嗎? 那防抖怎麼寫呢?
你想到了上面的那行 "當只有在功能觸發的時間間隔超過我們所指定的時間,才會觸發此功能"。
這時需要setTimeout來判斷時間是否有超過間隔以及定義兩個參數,分別是要觸發的函式以及指定的時間
// 防抖函式
function debounce(fn , wait){
var timeout
return function(){
if(timeout) clearTimeout(timeout)
timeout = setTimeout(fn, wait)
}
}
$("#register").on("input", debounce(function(){
console.log(this.value)
handleRegisterName(this.value)
}, 300))
完成後發現了this.value 怎麼是undefined? 檢查一下發現this被指向了window, 這是因為是setTimeout呼叫了這個回呼函式,除了這個問題之外,我們還想要傳遞一些參數進去函式中,例如event。所以我們必須調整一下debounce函式。
透過apply方式綁定上下文情境以及使用arguments取得函數參數。
function debounce(fn, wait) {
var timeout, args
return function(){
args = arguments
if(timeout) clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(this, args)
}, wait)
}
}
這時需求方又說希望一輸入文字時,就先執行一次。所以我們就當時間到時將timeout 設成null,使得可以直接執行fn方法
function debounce(fn, wait, imd) {
var timeout,args
return function(){
args = arguments
if(timeout) clearTimeout(timeout)
if(imd) {
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) fn.apply(this, args)
} else {
timeout = setTimeout(() => {
fn.apply(this, args)
}, wait)
}
}
}
但上面還有一個問題, 就是我們的fn假如有回傳值時, 上面那一個版本是無法接收的, 所以我們必須修正這個問題。
但要注意的是, 回傳值版本只提供給立即執行, 因為在非立即執行中fn是在setTimeout內, 導致我們的return的值永遠都是undefined 。
var debounce = function(fn, wait, immediate){
var timeout,result,args
return fuction() {
args = arguments
if(timeout) clearTimeout(timeout)
if(immediate) {
var callNow = !timeout
timeout = setTimeout(function(){
timeout = null;
}, wait)
if(callNow) result = fn.apply(this, args)
} else {
timeout = setTimeout(() => {
fn.apply(this, args)
}, wait)
}
return result
}
}
eventBus又稱eventHub,中文意思為訂閱發布模式
class eventEmitter {
private cache = {}
on (eventName, fn) {
this.cache[eventName] = this.cache[eventName] || []
this.cache[eventName].push(fn)
}
emit (eventName, data) {
let array = this.cache[eventName] || []
array.forEach(fn=>{
fn(data)
})
}
off(eventName, fn){
this.cache[eventName] = this.cache[eventName] || []
let index = this.cache[eventName].indexOf(fn)
if(index === -1) return
this.cache[eventName].splice(index, 1)
}
}
let event = new eventEmitter()
let fn1 =(data) => console.log(data)
event.on('test', fn1) // 訂閱
event.emit('test','你好') // 發布
event.off('test', fn1) // 取消訂閱
event.emit('test','你好')
主要思路是
物件導向(Object Oriented Programming 簡稱OOP)的概念是將現實生活中的各種複雜的情況,抽象成各個物件,然後由這些物件進行分工與合作來模擬現實生活的情況。
對於物件的描述
例如: 一輛車變成物件的樣子
const car = {
name: 'toyota',
color: 'black',
amount: 700000
tax: function(countryTax) {
return amount * countryTax
}
}
那我們想要造一台黃色的bmw的車子就必須又要重複定義這個結構
const bmwCar = {
name: 'bmw',
color: 'yellow',
amount: 1400000
tax: function(countryTax) {
return amount * countryTax
}
}
那有什麼辦法能夠解決重複結構的問題呢?
構造函數(constructor)使我們不需要再重複定義物件,是製造物件的模板
let Car = function(name,color,amount,countryTax){
this.name = name
this.color = color
this.amount = amount
this.tax = function(){
return amount * countryTax
}
}
構造函數與一般函數來說是沒什麼差別的,只是呼叫方式的不同,構造函數必須使用new來調用一個實例
let toyotaCar = new Car('toyota','black',700000,1.2)
兩個div元素,parent代表父層,child代表子層
<div id="parent">
<div id="child">
</div>
</div>
預設的css
body{
height:300px;
}
#child{
background-color:#111;
width:50px;
height:50px;
}
#parent{
height:100%;
background-color:#d5d7de;
}
#parent{
display:flex;
justify-content:center;
align-items:center;
#parent{
position:relative;
width:300px;
}
#child{
position:absolute;
top:50%;
left:50%;
margin-top:-25px;
margin-left:-25px;
}
函數的返回值的影響因素有兩個:
let a = 1
function add(x){
return x + a
}
{
let a = 2
add(2) // 是 3 or 4 ?
}
此時add(2)函式的2就是我們傳入的參數,return 的 a 就是由我們在環境中所定義的變數。
那麼答案是多少呢?
我們說影響的因素有兩個,其中有一個是"定義時"的環境env。
這個"定義時"指的是函數在定義時的地方,那麼跟我們函數在相同環境的a為1, 所以答案是3。
假如我們把add函數移至a=2 的區塊環境內,則答案為4。
其實在函數外面我們能夠訪問函數外面的變數,就是我們常見的閉包
閉包就是該函數與在函數外面被訪問的變數
那閉包有什麼用呢? 他能夠幫我們儲存環境變數的值,使得能夠重複使用該參數。
先看一個經典的題目:
for(var i = 0; i < 6; i++){
setTimeout(()=>console.log(i))
}
答案是什麼?
有一定經驗的前端一定知道setTimeout是非同步的,等到印出console.log(i)時i 已經都增加到6了,而且setTimeout六個函式中的i 都是共用相同的環境變數i,所以此時答案就是6個6。
那要怎麼正常打印出012345呢?
可以使用立即執行函數或者ES6的let。
for(let i = 0; i < 6; i++){
setTimeout(()=>console.log(i))
}
由於let是區域環境也就是在{ }那就是一個環境,所以我們所宣告的i會在該環境下保持住當前i的值,所以當setTimeout的函數執行時能夠取得到i = 0,1,2,3,4,5的值。
falsy的意思是在Boolean中可以轉變為false的值,總共有五個。
console.log(!!0) // false
console.log(!!NaN) //false
console.log(!!null) //false
console.log(!!undefined) //false
console.log(!!"") //false
CommonJS : 同步加載模塊。由於在服務器端中,模塊載入的速度取決於硬碟速度,所以以同步來加載模塊的話並沒什麼大礙。
AMD: 非同步加載模塊。AMD的出現主要是要解決在瀏覽器上載入模塊的問題,因為假如是同步的話,會使得畫面卡死,直到加載完成。
UMD: 主要是解決跨平台問題。 會判斷目前環境是否支持CommonJS或AMD,而去使用其中一種方法
把函式當作參數或者返回函式的函式, 就是為高階函式。
let add = function(a){
return function(b){
return a+b
}
}
add(2)(3) // return 5
JS原生的高階函數有bind,call,apply,
Array的sort,reduce,map,filter..等等
let newArr = [1,2,3,4].map(function(item){
return item+2
})
console.log(newArr) //[3,4,5,6]
為什麼使用new 創建一個vue實例時data可以直接使用物件來定義,而在組件中的data卻必須使用function來return 一個物件呢?
原因是物件是引用類型,每個組件中的data都是共用相同內存,所以一個數值改變時,其他組件就會跟著變。
先看一下下面的範例
var component = function(){}
component.prototype.data = {
a:1,
b:2
}
var test1 = new component()
var test2 = new component()
test1.prototype.data == test2.prototype.data // true
test1.prototype.data = 10;
console.log(test2.prototype.data) // 10
上述data就是類似我們傳入vue組件的data,所以當創建組件時你傳入是物件時,會將每個組件的data指向同一個地址。
若使用function return 一個物件時,則會造成作用域,來確保每個組建都用自己的物件,而不會共用。
上一篇物件導向與構造函數(constructor)中提到構造函數,但由於構造函數創造出來的實例中方法與屬性無法共用。所以當如果有一個方法需要公用時,只能重複創建相同方法,這樣很耗費記憶體空間。而原型則是解決無法共用屬性的問題。
是儲存公用方法與屬性的地方,只需要創建一次即可使子類使用。
let Car = function(name,color,amount){
this.name = name
this.color = color
this.amount = amount
}
Car.prototype.tax = function(countryTax){
return this.amount * countryTax
}
let car1 = new Car('toyota','black',700000)
let car2 = new Car('bmw','black',1400000)
car1.tax(1.2) // 840000
car2.tax(1.3) // 1820000
car1.tax === car2.tax // true, 說明方法來自同一個地方
淺拷貝: 僅能拷貝物件中第一層的資料,第二層以後的資料一樣會與舊的物件共用相同參考(reference)。
深拷貝:複製一個物件,使得兩個物件的參考完全沒有相關(不共用相同記憶體)。
var obj = {
name:'leo',
child:{
name:'jack'
}
}
var obj2 = Object.assign({},obj)
console.log(obj2) // 與obj值相同,代表成功複製
obj2.name = 'change'
console.log(obj.name) // leo,obj值未被修改
obj2.child.name = 'fake'
console.log(obj.child.name) // fake,修改obj2的值但obj的值也一起被修改了
var obj = {
name:'leo',
child:{
name:'jack'
}
}
var obj2 = {...obj} // 展開運算子複製物件
console.log(obj2) // 與obj值相同,代表成功複製
obj2.name = 'change'
console.log(obj.name) // leo,obj值未被修改
obj2.child.name = 'fake'
console.log(obj.child.name) // fake,修改obj2的值但obj的值也一起被修改了
var obj = {
name:'leo',
child:{
name:'jack'
}
}
var obj2 =JSON.parse(JSON.stringify(obj))// JSON.parse與JSON.stringify進行複製
console.log(obj2) // 與obj值相同,代表成功複製
obj2.name = 'change'
console.log(obj.name) // leo,obj值未被修改
obj2.child.name = 'fake'
console.log(obj.child.name) // jack,obj值未被修改,是我們想要的效果
但這種方式進行深拷貝的話,有些資料是無法複製的ex: function,Date,正則 ...等
原因是JSON格式沒有提供上述方式,所以在轉換時會被忽略,導致無法複製。
那麼要怎麼解決無法複製的那些值呢?
可以使用lodash提供的_. cloneDeep或者自行實作(敬請期待)
使用HTTP header主要是給瀏覽器與服務器提供報文主體大小、所使用的語言、驗證訊息等。
指的是請求與回應的報文雙方都會使用的首部
從客戶端項服務端發送請求報文時使用的首部。補充了請求的附加內容、客戶端訊息
待完成...
上一篇介紹構造函數時有提到必須使用new 來呼叫,那麼為什麼需要new呢? new 到底做了什麼事情?
new是一種語法糖,它幫我們做了四件事情,讓我們先看代碼
let polyfillNew = (constructor,...args)=>{
let obj = {}
Object.setPrototypeOf(obj, constructor.prototype) // 等同於 obj.__proto__ = constructor.prototype
let result = constructor.call(obj,...args)
return typeof result === 'object' ? result : obj
}
其實new 所做的事情很簡單,拆解new所做的事情之後就清楚為什麼我們要創建實例時必須使用new操作符,它幫我們處理原型鏈以及this的指向,最後返回我們所需要的實例。
持續觸發某個任務, 每隔一段時間觸發一次。
簡單來說 就是多久執行一次
開始執行時會先呼叫一次, 接下來依照時間戳來判斷是否重新觸發。
throttle (fn, wait) {
var prev = 0
return function () {
var now = +new Date()
if (now - prev > wait) {
fn.apply(this, arguments)
prev = now
}
}
}
經過設定的時間數後才會執行,我們透過setTimeout來判斷隔了多久觸發一次。
throttle (fn, wait) {
var timeout
return function () {
var args = arguments
if (timeout) {
return false
}
timeout = setTimeout(() => {
fn.apply(this, args)
timeout = null
}, wait)
}
}
立即執行: 執行任務時會先執行一次,但當事件停止觸發時任務就會立即停止。
例如: 在輸入註冊帳號時,設置每秒執行一次驗證註冊帳號是否符合資格,若在第2.5秒停止輸入這時只會執行兩次驗證。
非立即執行: 執行任務時會先等到指定時間到才會執行,但當事件停止觸發時依舊還會執行最後一次事件。
意思為2.5秒停止輸入等到第三秒時還會再執行一次驗證
我們想要實現出可以立即執行並且在停止輸入時還會觸發最後一次事件的方法
throttle (fn, wait) {
var timeout, remaining, context, args
var prev = 0
// 執行最後一次事件
var later = function () {
prev = +new Date()
timeout = null
fn.apply(context, args)
console.log('最後執行')
}
var throttled = function () {
context = this
args = arguments
var now = +new Date()
// 下次觸發任務(fn)的剩餘時間
remaining = wait - (now - prev)
// 剩餘時間小於0,代表可以觸發事件
if (remaining <= 0) {
// 確保下次還會進入setTimeout
if (timeout) {
clearTimeout(timeout)
timeout = null
}
prev = now
fn.apply(context, args)
} else if (!timeout) {
timeout = setTimeout(later, remaining)
}
}
return throttled
}
有時候並不想要上面的版本, 只想要原先的非立即執行的版本, 為了兼容這個問題我們想到了使用參數來控制想要使用哪種版本。
我們使用options作為第三個參數,而options中有兩個key:
throttle (fn, wait, options) {
var timeout, remaining, context, args
var prev = 0
if (!options) options = {}
// 執行最後一次事件
var later = function () {
prev = options.leading === false ? 0 : +new Date()
timeout = null
fn.apply(context, args)
console.log('最後執行')
}
var throttled = function () {
context = this
args = arguments
var now = +new Date()
if (!prev && options.leading === false) prev = now
// 下次觸發任務(fn)的剩餘時間
remaining = wait - (now - prev)
// 剩餘時間小於0,代表可以觸發事件
if (remaining <= 0) {
// 確保下次還會進入setTimeout
if (timeout) {
clearTimeout(timeout)
timeout = null
}
prev = now
fn.apply(context, args)
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining)
}
}
return throttled
}
由於我們在閉包中儲存了許多變量都沒有回收, 所以在結束這個事件時順便回收閉包變量
throttle (fn, wait, options) {
var timeout, remaining, context, args
var prev = 0
if (!options) options = {}
// 執行最後一次事件
var later = function () {
prev = options.leading === false ? 0 : +new Date()
timeout = null
fn.apply(context, args)
if (!timeout) context = args = null
console.log('最後執行')
}
var throttled = function () {
context = this
args = arguments
var now = +new Date()
if (!prev && options.leading === false) prev = now
// 下次觸發任務(fn)的剩餘時間
remaining = wait - (now - prev)
// 剩餘時間小於0,代表可以觸發事件
if (remaining <= 0) {
// 確保下次還會進入setTimeout
if (timeout) {
clearTimeout(timeout)
timeout = null
}
prev = now
fn.apply(context, args)
if (!timeout) context = args = null
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining)
}
}
return throttled
}
google後發現是mysql版本密碼算法不同,導致會出現這個錯誤!
解決方法:
進入mysql cmd
/usr/local/mysql/bin/mysql -u root -p
輸入mysql password
修改mysql密碼
USE mysql;
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密碼';
FLUSH PRIVILEGES;
修改完成後再重新開啟node服務器連接mysql 就成功了
序列化是將物件狀態轉為可保存或可傳輸格式的處理方式而反序列化則是將資料轉換成物件,兩種處理續搭配使用,可使資料易於儲存和傳輸。
讓我們先將全部過程列出來,在一個一個去解析過程。
先看一下維基百科的費波那契解釋:
F0 = 0
F1 = 1
Fn = F(n-1) +F(n-2)(n≧2)
用文字來說,就是費氏數列由0和1開始,之後的斐波那契系數就是由之前的兩數相加而得出。
ex: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233.......
0代表第0項
簡單來說就是第一項與第二項相加就為第三項的值。
讓我們來看程式是怎麼寫的:
let fib = (n) =>
n === 0 ? 0 :
n === 1 ? 1 :
fib(n-1) + fib(n-2)
fib(4) // 3
上面的例子意思是說, 假如n是0則返回0,若是1則返回1,若都不是則返回下兩項的相加。
但是使用遞迴有一個問題就是當傳入的參數越大則計算時間會變非常的久。
console.time('時間1')
fib(40) // 花了2230.783935546875ms
console.timeEnd('時間1')
console.time('時間2')
fib(45) //花了25279.218017578125ms
console.timeEnd('時間2')
fib(40與fib(45)的計算足足差了23秒,這是因為遞迴雖然裡面有一些函數我們已經計算過了但是電腦沒有那麼聰明,所以還是會重複計算,所以有人發明尾遞迴來優化。
尾遞迴的思路是我們從前面第二項開始計算到要計算的那一個項目,並且將計算完的第一項與第二項函式的參數一起傳到執行的參數中。
fib = (n) => fib_inner(2,n,1,0)
/*
* start - 起始項目
* end - 結束項目
* prev1 - 計算的第一項
* prev2 - 計算的第二項
*/
fib_inner = (start, end , prev1, prev2) =>
start === end ? prev1 + prev2 :
fib_inner(start + 1, end , prev1 + prev2, prev1 )
上面fib_inner 的意思就是當起始項目與最後要計算的項目相等時,就將計算好的第一項與第二項的值相加,否則就繼續執行下一個起始項目並且將下一個起始項目的第一項改成上一個第一項與第二項相加,第二項變成上一個第一項。
用文字敘述可能看不太懂,我們用數字n= 3 帶入進去:
fib(3) // 呼叫
fib = (3) => fib_inner(2,3,1,0) // n = 3
fib_inner = (2, 3 , 1, 0) =>
2 === 3 ? 1 + 0 : // 不相等
fib_inner(2 + 1, 3 , 1 + 0, 1 ) // 此時fib(2+1)的prev1 已經變成 1 + 0, prev2 變成 1
又在執行 fib_inner(2 + 1, 3 , 1 + 0, 1 )
fib_inner(2 + 1, 3 , 1 + 0, 1 ) =>
( 2 +1) === 3 ? (1 + 0) + 1 : // 相等
fib_inner(3 + 1, 3 , 1 + 1, 1 ) // 不執行
將數值帶入進去後應該就會明白他的意思是什麼了。
那麼真的有比較快嗎?
console.time('時間2')
fib(45) // 0.07421875ms
console.timeEnd('時間2')
比我們剛剛用遞迴快了超級多!
把支持多個參數的函式轉成只支持單一參數的函式形式,大多應用在函數式編程。
假設我們目前有一個相加函式
let add = (a,b) => a + b
add(1,2) // return 3
使用curry方式來表達的話則是
let add = a => b => a + b
add(1)(2)
使用閉包來記錄第一個參數使得執行返回的函數時能夠調用第一個參數來相加。
那假如我們希望使用一個函式能夠將多個參數形式的函式轉變成curry函式時該怎麼做呢?
let currify = (fn,params = []){
let newParams = params.conact(arg)
return (arg) = >{
if(newParams.length === fn.length){
return fn(...newParams)
} else {
return currify(fn, newParams)
}
}
}
addTwo = (a,b)=>a+b
addThree = (a,b,c)=>a+b+c
let newAddTwo = currify(addTwo)
newAddTwo(1)(2) // return 3
let newAddThree = currify(addThree)
newAddThree(1)(2)(3) // return 6
上面currify的思路是
由於AJAX的出現,使得我們在同一個頁面上不用刷新就能夠處理資料(CRUD),來進行畫面的互動效果,進而達到單頁面的效果。
但是透過AJAX只能達到單頁面的操作,切換到別的路徑時畫面還是會重整一次才顯示,這樣會照成使用者的體驗不好,那現今各大框架的路由是怎麼解決這個問題的呢?
透過兩種方式來解決:
路徑hash的判斷。由於路徑後面加上#之後,不會再隨著路徑不同而去向後端發出請求,也不會重新加載頁面,透過onhashchange方法可以監聽hash值有沒有發生改變。
由於HTML5 histroy API 多了pushState,replaceState這兩個方法,可以改變當前的url,但是不會向後端發出請求。
let vm = new Vue({
el: '#app',
mounted () {
console.log(this)
console.log(this.message)
},
data () {
return {
message: 'hello'
}
}
})
為什麼我們呼叫this.message時,可以直接取得data中的message資料呢? 而console.log(this) 卻又找不到這個message屬性? Vue到底在這個地方做了什麼處理?
vue的資料都是儲存在this._data中,而為了使用者方便取用,且避免訪問私有變數,Vue在初始化data時有替data中每個key做getter與setter,所以當我們在調用this.message時,其實getter就觸發並返回this._data.message,導致我們能夠取得到值
以下是源碼:
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
// vm是Vue元件 , _data是要代理的路徑, key是要代理的變數
proxy(vm, `_data`, key)
先看一個例子:
let n = 1
n.toString() // 印出 "1"
直接印出n的值時也沒有看到有任何的prototype可以使用,那為什麼n可以使用到toString方法呢?
而且n是一個基本類型怎麼能夠使用物件的方法呢?
首先我們要先說明let n = new Number(1)與 let n = 1 的差別。
使用new Number 會創建一個物件,而這個物件會擁有Number的prototype且Number的prototype又指向Object的prototype,所以使得我們可以同時使用Number與Object的prototype。
let n = new Number(1)
n -----> Number.prototype -----> Object.prototype
而使用let n = 1 時,他並不會生成一個物件,所以JS作者就用了一個方法解決這個問題。
就是當基本類型要使用prototype的東西時,會去new一個對應類型的物件,這個物件會提供n取得protoype的方法,等到取用結束後這個物件就會釋放掉避免佔太多記憶體。
再看一個例子:
let n = 1
n.xxx = 2
console.log(n.xxx) // undefined
由於上面我們已經說過當要對基本類型進行物件的操作時,會先創建一個暫時的物件且當操作結束後該物件就會被釋放掉,所以要再次調用n.xxx時就會是undefined。
在開發時多少有使用過bind這個方法,但是卻沒有想過他是怎麼實現的,今天就要來研究一下。
先來看mdn 的定義:
bind() 方法,會建立一個新函式。該函式被呼叫時,會將 this 關鍵字設為給定的參數,並在呼叫時,帶有提供之前,給定順序的參數。
polyfill
var slice = Array.prototype.slice
function bind(){
var that = arguments[0]
var thatArgs = slice.call(arguments, 1)
var fn = this
if(typof fn !== function){
throw new Error('bind 必須調用在函數上')
}
return function(){
var funcArgs = slice.call(arguments,0)
return fn.apply(that, thatArgs.concat(funcArgs))
}
}
我們來一次拆分函數:
但是這個bind還有一個問題是當我們使用new時會出現錯誤。
var slice = Array.prototype.slice
function bind(){
var that = arguments[0]
var thatArgs = slice.call(arguments, 1)
var fn = this
if(typof fn !== function){
throw new Error('bind 必須調用在函數上')
}
function resultsFn(){
var funcArgs = slice.call(arguments,0)
return fn.apply(
resultsFn.prototype.isPrototypeOf(this) ? this : that,
thatArgs.concat(funcArgs)
)
}
resultsFn.prototype = fn.prototype
return resultsFn
}
上面程式碼就使我們支援new 方法呼叫bind
由於模組可能在你npm install 之後有在更新過版本,這樣可能會導致你的專案在每個時期安裝到的模組版本都是不同的,所以package-lock.json是為了解決這個問題,確保你之後clone或deploy專案時,模組版本是固定的(package-lock.json會紀錄)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.