【相關推薦:、】
Object.defineProperty(obj, prop, descriptor)
方法會直接在一個物件上定義一個新屬性,或者修改一個物件的現有屬性,並返回此物件。
obj——要定義屬性的物件
prop——要定義或修改的屬性的名稱或Symbol
descriptor——物件,要定義或修改的屬性描述符
// descriptor{ value: undefined, // 屬性的值 get: undefined, // 獲取屬性值時觸發的方法 set: undefined, // 設定屬性值時觸發的方法 writable: false, // 屬性值是否可修改,false不可改 enumerable: false, // 屬性是否可以用for...in 和 Object.keys()列舉 configurable: false // 該屬性是否可以用delete刪除,false不可刪除,為false時也不能再修改該引數}
通過賦值操作新增的普通屬性是可列舉的,在列舉物件屬性時會被列舉到(for…in 或 Object.keys 方法),可以改變這些屬性的值,也可以刪除這些屬性。這個方法允許修改預設的額外選項(或設定)。
而預設情況下,使用 Object.defineProperty() 新增的屬性值是不可修改(immutable)的。
範例:
const a = {b : 1}console.log(Object.getOwnPropertyDescriptor(a, 'b'))// {value: 1, writable: true, enumerable: true, configurable: true}Object.defineProperty(a, 'c', {value: '2'})console.log(Object.getOwnPropertyDescriptor(a, 'c'))// {value: '2', writable: false, enumerable: false, configurable: false}a.c = 3console.log(a.c)// 2Object.defineProperty(a, 'c', {value: '4'})console.log(a.c)// error: Uncaught TypeError: Cannot redefine property: c
// 模擬vue響應式過程const app = document.getElementById('app')const data = { a: { b: { c: 1 } }}function render () { const virtualDom = `這是我的內容${data.a.b.c}` app.innerHTML = virtualDom}function observer (obj) { let value for (const key in obj) { // 遞迴設定set和get value = obj[key] if (typeof value === 'object'){ arguments.callee(value) } else { Object.defineProperty(obj, key, { get: function(){ return value }, set: function(newValue){ value = newValue render() } }) } }}render()observer(data)setTimeout(() => { data.a.b.c = 22}, 2000)setTimeout(() => { data.a.b.c = 88}, 5000)
上述方法實現了資料的響應,但存在很大的問題,我們觸發一次set,就需要整個頁面重新渲染,然而這個值可能只在某一個元件中使用了。
所以將get和set優化:
Object.defineProperty(data, key, { get: function(){ dep.depend() // 這裡進行依賴收集 return value }, set: function(newValue){ value = newValue // render() dep.notify() // 這裡進行virtualDom更新,通知需要更新的元件render }});
dep是Vue負責管理依賴的一個類
補充: Vue 無法檢測 property 的新增或移除。由於 Vue 會在初始化範例時對 property 執行 getter/setter 轉化,所以 property 必須在 data 物件上存在才能讓 Vue 將它轉換為響應式的
const vm = new Vue({ data: { a: 1 }})// vm.a是響應式的vm.b = 2// vm.b是非響應式的
vue 中處理陣列的變化,直接通過下標觸發檢視的更改,只能使用push、shift等方法,而陣列不能使用Object.defineProperty()
其實 Vue用裝飾者模式來重寫了陣列這些方法
Object.create(proto,[propertiesObject])
方法是建立一個新物件,使用現有的物件來提供新建立的物件的__proto__
proto
——新建立物件的原型物件;propertiesObject
——選填,型別是物件,如果該引數被指定且不為 undefined,該傳入物件的自有可列舉屬性(即其自身定義的屬性,而不是其原型鏈上的列舉屬性)將為新建立的物件新增指定的屬性值和對應的屬性描述符
const a = {}// 相當於const a = Object.create(Object.prototype)const person = { isHuman: false, printIntroduction: function () { console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`); }};const me = Object.create(person);me.name = 'Matthew'; // "name" is a property set on "me", but not on "person"me.isHuman = true; // inherited properties can be overwrittenme.printIntroduction();// expected output: "My name is Matthew. Am I human? true"
const o = Object.create(Object.prototype, { foo: { // foo會成為所建立物件的資料屬性 writable:true, configurable:true, value: "hello" }, bar: { // bar會成為所建立物件的存取器屬性 configurable: false, get: function() { return 10 }, set: function(value) { console.log("Setting `o.bar` to", value); } }});console.log(o) // {foo: 'hello'}
vue中的裝飾者模式
const arraypro = Array.prototype // 獲取Array的原型const arrob = Object.create(arraypro) // 用Array的原型建立一個新物件,arrob.__proto__ === arraypro,免得汙染原生Array;const arr=['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] // 需要重寫的方法arr.forEach(function(method) { arrob[method] = function () { arraypro[method].apply(this, arguments) // 重寫時先呼叫原生方法 dep.notify() // 並且同時更新 }})// 對於使用者定義的陣列,手動將陣列的__proto__指向我們修改過的原型const a = [1, 2, 3]a.__proto__ = arrob
上面對於新物件arrob的方法,我們是直接賦值的,這樣會有一個問題,就是使用者可能會不小心改掉我們的物件,所以我們可以用到我們前面講到的Object.defineProperty來規避這個問題,我們建立一個公用方法def專門來設定不能修改值的屬性
function def (obj, key, value) { Object.defineProperty(obj, key, { // 這裡我們沒有指定writeable,預設為false,即不可修改 enumerable: true, configurable: true, value: value, });}// 陣列方法重寫改為arr.forEach(function(method){ def(arrob, method, function () { arraypro[method].apply(this, arguments) // 重寫時先呼叫原生方法 dep.notify()// 並且同時更新 })})
資料屬性: 它包含的是一個資料值的位置,在這可以對資料值進行讀寫
資料屬性的四個描述符:
含義 | |
---|---|
configurable | 表示能否通過delete刪除屬性從而重新定義屬性,能否修改屬性的特性,或能否把屬性修改為存取器屬性,預設為true |
enumerable | 表示能否通過for-in迴圈返回屬性,預設為true |
writable | 表示能否修改屬性的值,預設為true |
value | 包含該屬性的資料值,預設為undefined |
存取器屬性: 這個屬性不包含資料值,包含的是一對get和set方法,在讀寫存取器屬性時,就是通過這兩個方法來進行操作處理的。
存取器屬性的四個描述符:
含義 | |
---|---|
configurable | 表示能否通過delete刪除屬性從而重新定義屬性,能否修改屬性的特性,或能否把屬性修改為存取器屬性,預設為false |
enumerable | 表示能否通過for-in迴圈返回屬性,預設為false |
get | 在讀取屬性時呼叫的函數,預設值為undefined |
set | 在寫入屬性時呼叫的函數,預設值為undefined |
vue3.0的響應式和vue2.0響應式原理類似,都是在get中收集依賴,在set中通知依賴更新檢視,但vue3.0使用了es6新增的proxy來代替Object.defineProperty()
proxy相對於Object.defineProperty()的好處:
- Object.defineProperty需要指定物件和屬性,對於多層巢狀的物件需要遞迴監聽,Proxy可以直接監聽整個物件,不需要遞迴;
- Object.defineProperty的get方法沒有傳入引數,如果我們需要返回原值,需要在外部快取一遍之前的值,Proxy的get方法會傳入物件和屬性,可以直接在函數內部操作,不需要外部變數;
- set方法也有類似的問題,Object.defineProperty的set方法傳入引數只有newValue,也需要手動將newValue賦給外部變數,Proxy的set也會傳入物件和屬性,可以直接在函數內部操作;
- new Proxy()會返回一個新物件,不會汙染源原物件
- Proxy可以監聽陣列,不用單獨處理陣列
proxy劣勢: vue3.0將放棄對低版本瀏覽器的相容(相容版本ie11以上)
這樣上邊的observe方法就可以優化成:
function observer () { var self = this; data = new Proxy(data, { get: function(target, key){ dep.depend() // 這裡進行依賴收集 return target[key] }, set: function(target, key, newValue){ target[key] = newValue; // render() dep.notify() // 這裡進行virtualDom更新,通知需要更新的元件render } });}
按照功能(或按照複用性)把一個頁面拆成各個板塊(模組),每一個模組都是一個單獨的檔案(單獨的元件),最後把各個模組(元件)拼在一起即可!!
目的 :方便團隊共同作業開發 實現複用
功能型元件「UI元件庫中提供的一般都是功能型元件:element/iview/antdv/vant/cube..」
業務型元件
以後開發專案,拿到設計稿的第一件事情:劃分元件「按照功能版塊劃分、本著複用性原則,拆的越細越好(這樣才能更好的實現複用)」
元件的建立及使用
建立一個 Xxx.vue 就是建立一個vue元件{區域性元件、私有元件},元件中包含:結構、樣式、功能
結構:基於template構建
+ 只能有一個根元素節點(vue2)
+ vue的檢視就是基於template語法構建的(各種指令&小鬍子...),最後vue會把其編譯為真實的DOM插入到頁面指定的容器中
首先基於 vue-template-compiler 外掛把template語法編譯為虛擬DOM「vnode」
其次把本次編譯出來的vnode和上一次的進行對比,計算出差異化的部分「DOM-DIFF」
最後把差異化的部分變為真實的DOM放在頁面中渲染
樣式:基於style來處理
功能:通過script處理
+ 匯出的這個物件是VueComponent類的範例(也是Vue的範例):物件 -> VueComponent.prototype -> Vue.prototype
+ 在物件中基於各種 options api 「例如:data、methods、computed、watch、filters、生命週期函數...」實現當前元件的功能
+ 在元件中的data不再是一個物件,而是一個「閉包」
+ 各個元件最後會合並在一起渲染,為了保證元件中指定的響應式資料是「私有的」,元件之間資料即使名字相同,也不會相互汙染...所以需要基於閉包來管理
注意;App.vue頁面入口相當於首頁,寫好的元件都匯入到這個裡面
<template> <p class="box"> {{ msg }} </p> </template> <script> export default { name: "Test", data() { return { //編寫響應式資料 msg: "你好,世界", }; }, }; </script> <style lang="less" scoped> .box { font-size: 20px; color: red; } </style>
私有元件(使用的時候首先進行匯入,然後註冊,這樣檢視中就可以呼叫元件進行渲染了)
需要使用私有元件的時候,需要先匯入import Test from "./Test.vue";
然後註冊:這樣就可以呼叫元件進行渲染了
<template> <p id="app"> //3.使用元件:可以使用單閉合或雙閉合 <Test></Test> <Test> </p> </template> <script> //1、匯入元件 import Test from "./Test.vue"; export default { name: "App", components:{ //2、註冊使用的元件 Test, } }; </script>
建立全域性元件
1. 建立一個區域性元件
<template> <p>{{ msg }}</p> </template> <script> export default { name: "Vote", data() { return { msg: "今夜陽光明媚", }; }, }; </script>
@2 在main.js入口中,匯入區域性元件Vote,把其註冊為全域性元件
import Vote from './Vote.vue'; Vue.component('Vote', Vote)
@3 這樣在任何元件(檢視中),無需基於components註冊,直接可以在檢視中呼叫
<template> <Vote></Vote></template>
呼叫元件的方式
呼叫元件的時候,可以使用:
雙閉合 <Test></Test>
雙閉合的方式可以使用插槽slot
@1 在封裝的元件中,基於 <slot> 標籤預留位置
@2 呼叫元件的時候,基於雙閉合的方式,把要插入到插槽中的內容寫在雙閉合之間
單閉合 <Test/>
元件的名字可以在「kebab-case」和「CamelCase」來切換:官方建議元件名字基於CamelCase命名,渲染的時候基於kebab-case模式使用!
插槽的作用
插槽分為了預設插槽、具名插槽、作用域插槽,
預設插槽:只需要在呼叫元件<Test><Test>
內插入我們想要的插入的html程式碼,會預設放到元件原始碼的<slot name="default"></slot>
插槽中
元件內部 slot預留位置 預設name:default <slot></slot> 呼叫元件的時候 //只有一個的時候可以不用template包裹 <Test> <p class="top">頭部導航</p> </Test>
具名插槽:元件中預設好多插槽位置,為了後期可以區分插入到哪,我們把插槽設定名字
<Test><Test>
內自己寫的程式碼,我們用template包裹程式碼,並把v-slot:xxx寫在template上,這時就會將xxx裡面的程式碼,包裹到元件原始碼的<slot name=」xxx「></slot>
的標籤中 <slot name="xxx">
預設名字是default元件內部 <slot name="xxx"> 預設名字是default 呼叫元件:需要把v-slot寫在template上 <template v-slot:xxx> ... </template> <template> ... </template> v-slot可以簡寫為#:#xxx
作用域插槽:把元件內部定義的資料,拿到呼叫元件時候的檢視中使用
元件中data內的資料只能在本模組中使用,如果想讓呼叫元件的插槽也能獲取資料,就需要對元件內對的slot做bind繫結資料,呼叫元件的template標籤做#top="AAA"
,獲取數
==元件內部==: <slot name="top" :list="list" :msg="msg"></slot>
==呼叫元件==: <template #top="AAA"></template>
v-slot="AAA"
或:default="AAA"
獲取資料元件內部 <slot name="top" :list="list" :msg="msg"></slot> 把元件中的list賦值給list屬性,把msg賦值給msg屬性,插槽中提供了兩個作用域屬性:list/msg 呼叫元件 <template #top="AAA"></template> 定義一個叫做AAA的變數,來接收插槽中繫結的資料 AAA={ list:[...], msg:... }
呼叫元件的時候
每建立一個元件其實相當於建立一個自定義類,而呼叫這個元件就是建立VueCommponent(或者Vue)類的範例
元件中的script中存在的狀態值和屬性值?
_vode
物件的私有屬性中(所以狀態值和屬性值名字不能重複)template標籤
中呼叫狀態值和屬性值,不需要加this,直接呼叫狀態名或屬性名script標籤
中呼叫狀態值和屬性值,需要加this呼叫vue中的單向資料流
父子元件傳遞資料時,只能由父元件流向子元件,不能由子元件流向父元件。這樣會防止從子元件意外改變父級元件的狀態,從而導致你的應用的資料流向難以理解。
元件傳參的分類7種:
1.父元件向子元件傳參
父元件向子元件傳參:props
VueComponent
的範例第一步:父元件在元件呼叫標籤中自定義屬性
//如果想把data中的狀態值傳遞過去需要v-bind繫結 <coma msg="hello" :num="num"></coma>
注意 如果想把data中的狀態值傳遞過去需要v-bind繫結
第二步:子元件通過props接收(陣列,物件)
// props的值是唯讀的 能改,會報錯 <input type="text" v-model="num" /> //陣列格式 props:["msg","num"] //物件格式 props: { msg: { //傳參型別必須是字串 type: String, //必須傳參 required: true, }, num: { type: Number, //如果不傳參預設是102 default: 102, }, }, //---------------------------------------- //用自定義變數numa接收num,然後頁面使用numa(解決唯讀問題) <h1>我是子元件 coma------{{ msg }}----{{ numa }}</h1> <input type="text" v-model="numa" /> props: ["msg", "num"], data() { return { numa: this.num, }; },
2.子元件向父元件傳參
子元件向父元件傳參,基於==釋出訂閱(@xxx給子元件標籤自定義事件、$emit)==
第一步:父元件在呼叫子元件的標籤上需要自定義一個事件,這個事件及繫結的方法就會新增到子元件的事件池中:底層實質上是呼叫了this.$on("myEvent",fn)
<Coma @myEvent="getData"></Coma> methods: { getData() {}, },
第二步:子元件用this.$emit()接受(this.$emit(myEvent,引數1,引數2)), 引數可以是子元件的,順便傳給父元件,實現子元件向父元件傳值
<button @click="goParentData">向父元件傳送資料</button> data() { return { flag: "你很美", n: 101, }; methods: { goParentData() { //執行父元件自定義的事件 this.$emit("myEvent", this.flag, this.n); }, },
第三步:父元件使用傳遞過來的資料
data() { return { n: 0, flag: "", }; }, methods: { getData(...parans) { console.log(parans); //傳遞過來的是陣列 this.n = parans[0]; this.flag = parans[1]; }, },
3.元件之間相互傳參 原生事件法 (釋出訂閱)
b--->c傳送資料
第一步:全域性的main.js中建立一個全域性的EventBus,掛載 vue的原型上 this.$bus
$bus.$on()
繫結的事件函數,在哪個vue範例上都可以基於$bus.$empty()
執行,還可以傳值//建立一個全域性的 Eventbus let Eventbus=new Vue(); //掛載 vue的原型上 後期基於this.$bus Vue.prototype.$bus=Eventbus;
第二步:comc向事件池中繫結事件:this.$bus.$on("事件名",函數)
created() { //向事件池中新增方法 this.$bus.$on("myEvent", () => {}); },
第三步:comb從事件池中獲取事件函數並執行:this.$bus.$emit("事件名",想傳的引數)
<button @click="send">傳送資料給comc</button> data() { return { msg: "我是comb", }; methods: { send() { //執行事件池中的方法,並且傳參 this.$bus.$emit("myEvent", this.msg); },
第四步 comc使用傳遞過來的資料
<h1>元件 comc----{{ msg }}</h1> data() { return { msg: "", }; //建立之後的勾點函數向事件池中新增方法 created() { //向事件池中新增方法 this.$bus.$on("myEvent", (value) => { console.log(value); this.msg = value; }); },
4.祖先和後代相互傳參
data() { return { title: "我是about祖先", }; }, provide() { return { title: this.title, }; },
第二步:後代使用inject屬性接受祖先中的引數,inject是data中的資料,是陣列型別
inject: ["title"],因為inject是陣列型別,所以它符合如果資料項不是物件型別,則不做劫持,如果資料項是物件,則這個物件中的屬性會做劫持。
data() { return { title: "我是about祖先", }; }, //祖先 傳遞的title是非響應式 provide() { return { title: this.title, }; }, //------------------------------ data() { return { //obj非響應式 obj: { //title是響應式 title: "我是about祖先", }, }; }, //祖先 傳遞的引數失去響應式,但裡面的值會是響應式 provide() { return { obj: this.obj, }; },
vue的範例中存在一些屬效能夠獲取不同關係的元素,獲取之後就可以基於這個元素獲取其中的資料或方法了:
created() { console.log(this.$parent.title); },
this.$children[n]
:獲取第n個子元素的vm範例mounted() { console.log(this.$children[0].msg); },
this.$root
:獲取根元素的vm範例(main.js中new 的Vue範例)et mv = new Vue({ router, data() { return { rootmsg: "我是草根" } }, render: h => h(App) }).$mount('#app') --------------------- mounted() { console.log(this.$root.rootmsg); },
this.$refs
:this的子元素中需要定義ref
屬性:比如ref="xxx"
:this.$refs.xxx
獲取的是DOM物件this.$refs.xxx
獲取的是子元件的vm範例//獲取的是dom元素 <p ref="one">11111</p> mounted() { console.log(this.$refs.one); }, ----------------------------------- 獲取的是元件 <comb ref="b"></comb> mounted() { console.log(this.$refs.b); }, //如果不是元件獲取的就是dom元素,如果是元件,獲取的就是元件的範例
重點:父元件更新預設不會觸發子元件更新,但是**==如果子元件中繫結呼叫了父元件的資料aaa,父元件的aaa資料更新觸發重新渲染時,使用aaa資料{{$parent.aaa}}的子元件也會觸發更新==**
一、父子元件生命週期執行過程:
beforeCreated
created
beforeMount
beforeCreate
created
beforeMount
mounted
mounted
二、子元件更新過程:
berforeUpdate
berforeUpdate
updated
updated
三、父元件更新過程:
berforeUpdate
updated
四、父元件銷燬過程:
beforeDestory
beforeDestory
destoryed
destoryed
@xxx.native
: 監聽元件根元素的原生事件。
例子:<my-component @click.native="onClick"></my-component>
原理:在父元件中給子元件繫結一個==原生(click/mouseover...)==的事件,就將子元件變成了普通的HTML標籤,不加'. native'父元件繫結給子元件標籤的事件是無法觸發的
虛擬DOM
虛擬DOM物件:_vnode
,作用:
第一步:vue內部自己定義的一套物件,基於自己規定的鍵值對,來描述檢視中每一個節點的特徵:
- tag標籤名
- text文位元組點,儲存文字內容
- children:子節點
- data:屬性
vue-template-compiler
去渲染解析 template 檢視,最後構建出上述的虛擬DOM物件元件庫
element-ui
:餓了麼antdv
:螞蟻金服iview
:京東
- Element - The world's most popular Vue UI framework
- ==vue2.xx==:elemnetui
- ==vue3.xx==:element plus
如何在專案中使用功能性元件?
==第一步==:安裝element-ui
:$npm i element-ui -s
==第二步==:匯入:
完整匯入:整個元件庫都匯入進來,想用什麼直接用Vue.use(xxx)即可
缺點:如果我們只用幾個元件,則無用的匯入元件會造成專案打包體積變大[不好],所以專案中推薦使用按需匯入
按需匯入:
1、需要安裝依賴$ npm install babel-plugin-component
樣式私有化
在Vue中我們基於scoped設定樣式私有化之後:
會給元件建立一個唯一的ID(例如:data-v-5f109989
)
在元件檢視中,我們編寫所有元素(包含元素呼叫的UI元件),都設定了這個ID屬性;但是我們呼叫的元件內部的元素,並沒有設定這個屬性!!
<p data-v-5f1969a9 class="task-box"> <button data-v-5f1969a9 type="button" class="el-button el-button--primary"> <span>新增任務</span> </button> </p>
而我們編寫的樣式,最後會自動加上屬性選擇器:
.task-box { box-sizing: border-box; ... } ---------編譯後成為:--------- .task-box[data-v-5f1969a9]{ box-sizing: border-box; }
/deep/
:/deep/.el-textarea__inner, /deep/.el-input__inner{ border-radius: 0; }
在真實專案中,我們會把資料請求和axios的二次封裝,都會放到src/api路徑下進行管理
//main.js import api from '@/api/index'; // 把儲存介面請求的api物件掛載搭配Vue的原型上: 後續在各個元件基於this.$api.xxx()就可以傳送請求了,無需在每個元件中再單獨匯入這個api物件。 Vue.prototype.$api=api;
【相關推薦:、】
以上就是VUE兩大核心之響應式與元件化開發詳解的詳細內容,更多請關注TW511.COM其它相關文章!