preface
Vue is one of the most frequently used front-end MVVM frameworks, providing convenient functions and apis such as data responsiveness, Watch, computed, etc. Then, how does VUE realize these functions? Before delving into the vue source code, it is important to understand the following javascript basics to make reading the Vue source code easier.
Flow type detection
Flow is a static type checking tool for JavaScript, first proposed by the Facebook team at the 2014 Scale Conference. The goal of this library is to check for type errors in JavaScript, and developers usually don’t need to modify the code to use it, so it’s very cheap to use. It also provides additional syntactic support that allows developers to leverage Flow to a greater extent. To sum up: change javascript from a weakly typed language to a strongly typed language.
Basic detection type
Flow supports primitive data types, where void corresponds to undefined in JS. There are basically the following types:
boolean
number
string
null
void
Copy the code
When defining a variable, you only need to declare the desired type in key places. The basic use is as follows:
let str:number = 1;
let str1:string = 'a';
// reassign
str = 'd' // error
str1 = 3 // error
Copy the code
Complex type detection
Flow supports complex type detection, which is basically as follows:
Object Array Function User-defined ClassCopy the code
Basically use the following sample code:
/ / the Object definition
let o:Object = {
key: 123
}
// Declare the key of the Object
let o2:{key:string} = {
key: '111'
}
/ / Array definitions
// Based on basically similar arrays of the same type
let numberArr:number[] = [12.3.4.5.2];
// Another way to write it
let numberAr2r:Array<number> = [12.3.2.3];
let stringArr:string[] = ['12'.'a'.'cc'];
let booleanArr:boolean[] = [true.true.false];
let nullArr:null[] = [null.null.null];
let voidArr:void[] = [,,undefined.void(0)];
// The array contains different types of data
// The fourth element is not declared, so it can be of any type
let arr:[number,string,boolean] = [1.'a'.true.function(){},];
Copy the code
Function definition is written as follows, which appears most frequently in vue source code:
/** * declare a function (fn) that specifies the type of its argument and return value. * /
function fn(arg:number,arg2:string) :Object{
return {
arg,
arg2
}
}
/ * * * vue source code snippet * SRC/core/instance/lifecycle. Js * /
export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
/ / to omit
}
Copy the code
Custom class, declare a custom class, then use as a basic type, basic code as follows:
/ * * * vue source code snippet * SRC/core/observer/index. The js * /
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
/ / to omit}}Copy the code
Javascript cannot be run on the browser side, and the Babel plugin must be used, and babel-flow-vue is used in vue source and is configured in babelrc, the fragment code is as follows:
Json file // omit "devDependencies": {// omit "babel-flow-vue ": "^ 1.0.0"} / / government / / babelrc file {" presets ": [" es2015", "flow - vue]", "plugins" : ["transform-vue-jsx", "syntax-dynamic-import"], "ignore": [ "dist/*.js", "packages/**/*.js" ] }Copy the code
object
Only object creation, property manipulation on objects, getter/setter methods, and object tags are reanalyzed here. Prototype chains and prototype inheritance principles are not important content of this article.
Create an object
Generally, there are three ways to create an object, the basic code is as follows:
// The first way is the simplest
let obj = { a: 1 }
obj.a / / 1
typeof obj.toString // 'function'
/ / the second
let obj2 = Object.create({ a: 1 })
obj2.a / / 1
typeof obj2.toString // 'function'
/ / the third kind
let obj3 = Object.create(null)
typeof obj3.toString // 'undefined'
Copy the code
The diagram is basically as follows:
Object. Create can be understood as inheriting an Object. It is a new feature of ES5, which needs to be compatible with older browsers.
if (!Object.create) {
Object.create = function (o) {
function F() {} // defines an implicit constructor
F.prototype = o;
return new F(); // This is done with new
};
}
Copy the code
Use object.create (null) to create an empty Object, which has the same name as an attribute on the prototype chain.
// src/core/global-api/index.js
// Define static options on Vue and assign null objects. ASSET_TYPES are 'Component ','directive','filter' and other attributes defined on Vue
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type= > {
Vue.options[type + 's'] = Object.create(null)})Copy the code
Attribute operation correlation
The enumeration type of the current object is set by default when the object is created. If this is not set, all enumeration types are false by default. The main use is the new ES5 feature Object.defineProperty.
Object. DefineProperty (obj,prop, Descriptor) has the following parameters:
- The description of this property can be changed and deleted from any additional device only if and if the property of a different device is true, exercising any additional control system. The default is false
- An enumerable property can appear in an object’s enumerated property if and only if its Enumerable is true. The default is false
- Value Indicates the value of the attribute. It can be any valid JavaScript value (numeric value, object, function, etc.). The default is undefined.
- Writable Value can be changed by the assignment operator if and only if the writable of the property is true. The default is false.
- Get A method that provides a getter for a property, undefined if there is no getter. When the property is accessed, the method is executed with no arguments, but with this object (which is not necessarily the object defining the property because of inheritance). The default is undefined.
- Set a method that provides a setter for a property, or undefined if there is none. This method is triggered when the property value is modified. This method takes a unique parameter, the new parameter value for the property. The default value is undefined
Note: Accessors (get and set) and value cannot be set at the same time in descriptor.
The complete sample code is as follows:
Object.defineProperty(obj,prop,
configurable: true.enumerable: true.writable: true.value: ' '.get: function() {},set: function() {})Copy the code
Through the use of the Object. GetOwnPropertyDescriptor enumeration values to view the Object properties, specific use the example code is as follows:
// If the enumeration type is not set, the default is false
let obj = {}
Object.defineProperty(obj, 'name', {
value : "wqzwh"
})
Object.getOwnPropertyDescriptor(obj, 'name')
// {value: "wqzwh", writable: false, enumerable: false, configurable: false}
let obj2 = {}
Object.defineProperty(obj2, 'name', {
enumerable: true.writable: true.value : "wqzwh"
})
Object.getOwnPropertyDescriptor(obj2, 'name')
// {value: "wqzwh", writable: true, enumerable: true, configurable: false}
Copy the code
Keys () to retrieve the key of an Object, you must set Enumerable to True or return an empty array:
let obj = {}
Object.defineProperty(obj, 'name', {
enumerable: true.value : "wqzwh"
})
Object.keys(obj) // ['name']
Copy the code
PropertyIsEnumerable You can use propertyIsEnumerable to determine if a defined object is enumerable:
let obj = {}
Object.defineProperty(obj, 'name', {
value : "wqzwh"
})
obj.propertyIsEnumerable('name') // false
let obj = {}
Object.defineProperty(obj, 'name', {
enumerable: true.value : "wqzwh"
})
obj.propertyIsEnumerable('name') // true
Copy the code
HasOwnProperty checks whether an object has a specific property of its own. Unlike the IN operator, this method ignores attributes inherited from the stereotype chain. The code is as follows:
// Create Object attributes with object.defineProperty
let obj = {}
Object.defineProperty(obj, 'name', {
value : "wqzwh".enumerable: true
})
let obj2 = Object.create(obj)
obj2.age = 20
for (key in obj2) {
console.log(key); // age, name
}
for (key in obj2) {
if (obj2.hasOwnProperty(key)) {
console.log(key); // age}}// Create a common attribute
let obj = {}
obj.name = 'wqzwh'
let obj2 = Object.create(obj)
obj2.age = 20
for (key in obj2) {
console.log(key); // age, name
}
for (key in obj2) {
if (obj2.hasOwnProperty(key)) {
console.log(key); // age}}Copy the code
Note: If the inherited Object property is created through Object.defineProperty and Enumerable is not set to true, then for in still cannot enumerate the property on the stereotype. (Thank @sunguoqiang123 for pointing out the wrong problem, it has been changed)
Getter/setter methods
To detect attribute changes through get/set methods, the basic code is as follows:
function foo() {}
Object.defineProperty(foo.prototype, 'z',
{
get: function(){
return 1}})let obj = new foo();
console.log(obj.z) / / 1
obj.z = 10
console.log(obj.z) / / 1
Copy the code
The z property is a property on foo.prototype and has a get method. The second pass through obj. Z = 10 does not create a Z property on obj itself, but instead directly triggers the get method on the prototype.
The diagram is basically as follows:
The z property, writable and 64x are different, and can be configured without any additional information, without any additional information. If the z property is configured without any additional information, the writable and writable object can be configured without any additional information.
function foo() {}
Object.defineProperty(foo.prototype, 'z',
{
get: function(){
return 1}})let obj = new foo();
console.log(obj.z) / / 1
Object.defineProperty(obj, 'z',
{
value: 100.writable: true.configurable: true})console.log(obj.z) / / 100
obj.z = 300
console.log(obj.z) / / 300
delete obj.z
console.log(obj.z) / / 1
Copy the code
The diagram is basically as follows:
Object.defineProperty
In theconfigurable
,enumerable
,writable
,value
,get
,set
How do these parameters relate to each other? This can be illustrated clearly with a picture:
Object label
Create an Object with a __proto__ prototype tag. Create an Object using object.create (null).
let obj = {x: 1.y: 2}
obj.__proto__.z = 3
console.log(obj.z) / / 3
Copy the code
The Object. PreventExtensions method is used to lock Object properties so that they are not extensible, i.e. new properties cannot be added, but properties can still be changed or deleted. Object.
let obj = {x : 1.y : 2};
Object.isExtensible(obj); // true
Object.preventExtensions(obj);
Object.isExtensible(obj); // false
obj.z = 1;
obj.z; // undefined, add new property failed
Object.getOwnPropertyDescriptor(obj, 'x');
// Object {value: 1, writable: true, enumerable: true, configurable: true}
Copy the code
The Object. Seal method is used to seal the Object so that no additional attributes can be added or deleted. The additional attributes of the Object can be modified without any additional information.
let obj = {x : 1.y : 2};
Object.seal(obj);
Object.getOwnPropertyDescriptor(obj, 'x');
// Object {value: 1, writable: true, enumerable: true, configurable: false}
Object.isSealed(obj); // true
Copy the code
Object. Freeze Completely freezes the Object. Based on seal, the property value cannot be changed (wirtable for each property is also set to false).
let obj = {x : 1.y : 2};
Object.freeze(obj);
Object.getOwnPropertyDescriptor(obj, 'x');
// Object {value: 1, writable: false, enumerable: true, configurable: false}
Object.isFrozen(obj); // true
Copy the code
DOM custom events
Before the introduction to this thesis, first see a vue source model of instruction, open platforms/web/runtime/directives/model. The js, fragments of code is as follows:
/* istanbul ignore if */
if (isIE9) {
// http://www.matts411.com/post/internet-explorer-9-oninput/
document.addEventListener('selectionchange', () = > {const el = document.activeElement
if (el && el.vmodel) {
trigger(el, 'input')}}}/ / to omit
function trigger (el, type) {
const e = document.createEvent('HTMLEvents')
e.initEvent(type, true.true)
el.dispatchEvent(e)
}
Copy the code
Where document.activeElement is the element currently in focus, you can use the document.hasfocus () method to check whether the current element is in focus.
Standard browsers provide methods that can be triggered by elements: element.dispatchEvent(). However, before we can use this method, we need to do two other things, as well as create and initialize. So, to sum it up:
document.createEvent()
event.initEvent()
element.dispatchEvent()
Copy the code
The createEvent() method returns the newly created Event object, which supports one parameter representing the Event type, as described in the following table:
Parameter Event interface initialization method HTMLEvents HTMLEvent initEvent() MouseEvents MouseEvent initMouseEvent() UIEvents UIEvent initUIEvent()Copy the code
The initEvent() method is used to initialize the value of the Event created through the DocumentEvent interface. Support three parameters: initEvent(eventName, canBubble, preventDefault). Indicates the event name, whether it can bubble, and whether it prevents the default action of the event.
DispatchEvent () is the Event object created by the createEvent() method, which is called el.dispatchEvent(e).
So how exactly should this thing be used? For example, customize a click method as follows:
// Create an event.
let event = document.createEvent('HTMLEvents');
// Initializes a click event that can bubble and cannot be cancelled
event.initEvent('click'.true.false);
let elm = document.getElementById('wq')
// Set the event listener.
elm.addEventListener('click', (e) => {
console.log(e)
}, false);
// Triggers event listening
elm.dispatchEvent(event);
Copy the code
Array extension method
Every method /some method
The first argument is the function (which takes three arguments: the value of the current item in the array, the current item’s index in the array, and the array object itself). The second argument is the scope object that executes the first function argument, which is the value referred to by this in the above function.
Neither method changes the original array
- Every () : This method runs the given function on each item in the array, and returns true if the function returns true for each item.
- Some () : This method runs the given function on each item in the array, returning true if the function returns true for any item.
Example code is as follows:
let arr = [ 1.2.3.4.5.6 ];
console.log( arr.some( function( item, index, array ){
console.log( 'item=' + item + ',index='+index+',array='+array );
return item > 3;
}));
console.log( arr.every( function( item, index, array ){
console.log( 'item=' + item + ',index='+index+',array='+array );
return item > 3;
}));
Copy the code
Some returns a value of true and does not proceed. Every returns a value of false, so there is no need to proceed.
getBoundingClientRect
This method returns a rectangular object with four attributes, left, top, right, and bottom, representing the distance between each edge of the element and the top and left side of the page, and x and y representing the position of the upper-left point.
The left, top, right, bottom, x, and y values calculated using this method change with the scrolling operation in the viewport area. If you want to get the value of the top and left attributes relative to the upper left corner of the entire page, just add the current scrolling position to the top and left attributes.
For cross-browser compatibility, use window.pageXOffset and window.pageYOffset instead of window.scrollX and window.scrollY. Scripts that do not have access to these attributes can use the following code:
// For scrollX
(((t = document.documentElement) || (t = document.body.parentNode))
&& typeof t.scrollLeft == 'number' ? t : document.body).scrollLeft
// For scrollY
(((t = document.documentElement) || (t = document.body.parentNode))
&& typeof t.scrollTop == 'number' ? t : document.body).scrollTop
Copy the code
In IE, the default coordinates are calculated from (2,2), resulting in a final distance of two pixels more than in other browsers, as follows:
document.documentElement.clientTop; // Non-IE is 0, IE is 2
document.documentElement.clientLeft; // Non-IE is 0, IE is 2
// To keep all browsers consistent, you need to do the following
functiongGetRect (element) {
let rect = element.getBoundingClientRect();
let top = document.documentElement.clientTop;
let left= document.documentElement.clientLeft;
return{
top: rect.top - top,
bottom: rect.bottom - top,
left: rect.left - left,
right: rect.right - left
}
}
Copy the code
performance
Vue fragment source code is as follows:
if(process.env.NODE_ENV ! = ='production') {
const perf = inBrowser && window.performance
/* istanbul ignore if */
if (
perf &&
perf.mark &&
perf.measure &&
perf.clearMarks &&
perf.clearMeasures
) {
mark = tag= > perf.mark(tag)
measure = (name, startTag, endTag) = > {
perf.measure(name, startTag, endTag)
perf.clearMarks(startTag)
perf.clearMarks(endTag)
perf.clearMeasures(name)
}
}
}
Copy the code
The performance. Mark method creates a buffer with the given name in the browser’s performance item buffer, and performance. Measure creates a name in the browser’s performance item buffer between two specified tags (called start and end tags, respectively).
let _uid = 0
const perf = window.performance
function testPerf() {
_uid++
let startTag = `test-mark-start:${_uid}`
let endTag = `test-mark-end:${_uid}`
// Execute the mark function to mark
perf.mark(startTag)
for(let i = 0; i < 100000; i++) {
}
// Execute the mark function to mark
perf.mark(endTag)
perf.measure(`test mark init`, startTag, endTag)
}
Copy the code
The test results are available in Google ChromePerformance
The effect picture is as follows:
The browserperformance
The processing model is basically as follows (More detailed parameter description) :
The Proxy related
The get method
The get method intercepts a read of a property and can take three parameters, the target object, the property name, and the proxy instance itself (strictly speaking, the object against which the action is directed), the last of which is optional.
Intercepts reading of object properties, such as proxy.foo and proxy[‘foo’]
The basic usage is as follows:
let person = {
name: "Zhang"
};
let proxy = new Proxy(person, {
get: (target, property) = > {
if (property in target) {
return target[property];
} else {
throw new ReferenceError("Property \"" + property + "\" does not exist."); }}}); proxy.name// "/"
proxy.age // Throw an error
Copy the code
If a property cannot be configured and writable, the Proxy cannot modify the property. Otherwise, an error will be reported when accessing the property through the Proxy object. Example code is as follows:
const target = Object.defineProperties({}, {
foo: {
value: 123.writable: false.configurable: false}});const handler = {
get(target, propKey) {
return 'abc'; }};const proxy = new Proxy(target, handler);
proxy.foo // TypeError: Invariant check failed
Copy the code
From the way
This method can take two parameters: the target object and the name of the property to be queried. It intercepts the following operations:
- Attribute query: Foo in Proxy
- Inheritance attribute query: foo in object.create (proxy)
- With check: with(proxy) {(foo); }
- Reflect.has()
Has intercepts an error if the original object is not configurable or if extension is disabled. The basic sample code is as follows:
let obj = { a: 10 };
Object.preventExtensions(obj);
let p = new Proxy(obj, {
has: function(target, prop) {
return false; }});'a' in p // TypeError is thrown
Copy the code
Has interception works only for the IN operator, for… The in loop does not take effect. The basic sample code is as follows:
let stu1 = {name: 'Joe'.score: 59};
let stu2 = {name: 'bill'.score: 99};
let handler = {
has(target, prop) {
if (prop === 'score' && target[prop] < 60) {
console.log(`${target.name}Fail `);
return false;
}
return prop intarget; }}let oproxy1 = new Proxy(stu1, handler);
let oproxy2 = new Proxy(stu2, handler);
'score' in oproxy1
// Zhang SAN failed
// false
'score' in oproxy2
// true
for (let a in oproxy1) {
console.log(oproxy1[a]);
}
/ / zhang SAN
/ / 59
for (let b in oproxy2) {
console.log(oproxy2[b]);
}
/ / li si
/ / 99
Copy the code
The purpose of using the with keyword is to make it easier to write multiple times to access the same object.
let qs = location.search.substring(1);
let hostName = location.hostname;
let url = location.href;
with (location){
let qs = search.substring(1);
let hostName = hostname;
let url = href;
}
Copy the code
The use of the with keyword will lead to code performance degradation. Using let to define variables can improve some performance compared to using var. Example code is as follows:
// Do not use with
function func() {
console.time("func");
let obj = {
a: [1.2.3]};for (let i = 0; i < 100000; i++) {
let v = obj.a[0];
}
console.timeEnd("func");/ / 1.310302734375 ms
}
func();
// Use with and let to define variables
function funcWith() {
console.time("funcWith");
const obj = {
a: [1.2.3]};with (obj) {
let a = obj.a
for (let i = 0; i < 100000; i++) {
let v = a[0]; }}console.timeEnd("funcWith");/ / 14.533935546875 ms
}
funcWith();
/ / to use with
function funcWith() {
console.time("funcWith");
var obj = {
a: [1.2.3]};with (obj) {
for (var i = 0; i < 100000; i++) {
var v = a[0]; }}console.timeEnd("funcWith");/ / 52.078857421875 ms
}
funcWith();
Copy the code
The JS engine has a compilation phase before the code execution. When the with keyword is not used, the JS engine knows that a is an attribute on OBj, and it can statically analyze the code to enhance the resolution of identifiers, thus optimizing the code, so that the efficiency of code execution is improved. With the with keyword, the JS engine cannot tell whether the a variable is a local variable or a property of OBj. Therefore, when the JS engine encounters the with keyword, it will give up optimizing the code, so the execution efficiency is reduced.
Example code for intercepting the with keyword with the HAS method is as follows:
let stu1 = {name: 'Joe'.score: 59};
let handler = {
has(target, prop) {
if (prop === 'score' && target[prop] < 60) {
console.log(`${target.name}Fail `);
return false;
}
return prop intarget; }}let oproxy1 = new Proxy(stu1, handler);
function test() {
let score
with(oproxy1) {
return score
}
}
test() // Zhang SAN failed
Copy the code
When using the with keyword, it is mainly because of the js engine’s performance loss in resolving the scope of variables in the code block, so we can improve its performance by defining local variables. Modify the sample code as follows:
/ / modified
function funcWith() {
console.time("funcWith");
const obj = {
a: [1.2.3]};with (obj) {
let a = obj.a
for (let i = 0; i < 100000; i++) {
let v = a[0]; }}console.timeEnd("funcWith");/ / 1.7109375 ms
}
funcWith();
Copy the code
However, in practice, it is not feasible to define local variables in the with code block, so removing the frequent lookup scope function should improve the performance of the code part. After testing, the running time is almost the same, modify the code as follows:
function func() {
console.time("func");
let obj = {
a: [1.2.3]};let v = obj.a[0];
console.timeEnd("func");/ / 0.01904296875 ms
}
func();
/ / modified
function funcWith() {
console.time("funcWith");
const obj = {
a: [1.2.3]};with (obj) {
let v = a[0];
}
console.timeEnd("funcWith");/ / 0.028076171875 ms
}
funcWith();
Copy the code
How does it work with the has function? The snippet code is as follows:
// The first code actually does not use the HAS method, just for comparison purposes
console.time("Test");
let stu1 = {name: 'Joe'.score: 59};
let handler = {
has(target, prop) {
if (prop === 'score' && target[prop] < 60) {
console.log(`${target.name}Fail `);
return false;
}
return prop intarget; }}let oproxy1 = new Proxy(stu1, handler);
function test(oproxy1) {
return {
render: (a)= > {
return oproxy1.score
}
}
}
console.log(test(oproxy1).render()) // Zhang SAN failed
console.timeEnd("Test"); / / 0.719970703125 ms
console.time("Test");
let stu1 = {name: 'Joe'.score: 59};
let handler = {
has(target, prop) {
if (prop === 'score' && target[prop] < 60) {
console.log(`${target.name}Fail `);
return false;
}
return prop intarget; }}let oproxy1 = new Proxy(stu1, handler);
function test(oproxy1) {
let score
return {
render: (a)= > {
with(oproxy1) {
return score
}
}
}
}
console.log(test(oproxy1).render()) // Zhang SAN failed
console.timeEnd("Test"); / / 0.760009765625 ms
Copy the code
The fragment code that uses the with keyword in VUE is as follows. It mainly intercepts variables and methods involved in the AST language tree through proxy, and determines whether there are defined variables and methods in the AST language tree. As for why vue uses the with keyword, you can click to view the details
export function generate (ast: ASTElement | void, options: CompilerOptions) :CodegenResult {
const state = new CodegenState(options)
const code = ast ? genElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${code}} `.staticRenderFns: state.staticRenderFns
}
}
Copy the code
outerHTML
Open platforms/web/entry – the runtime – width – the compile. Js, view getOuterHTML method, fragments of code is as follows:
function getOuterHTML (el: Element) :string {
if (el.outerHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
Copy the code
Since there are no innerHTML and outerHTML attributes for SVG tag elements in Internet Explorer 9-11, there are else statements followed by them
The 2018-07-17 supplement
Proxy and Object.defineProperty are used in vue source code. The data defined in vue is actually listening for changes through Object.defineProperty. If the data defined is purely an Object, it makes sense to follow object.definePropertyAPI, but what about an array? How does this work?
Note: Object.defineProperty has certain drawbacks: data hijacking can only be done for attributes in OBJ. If the Object hierarchy is too deep, the entire Object needs to be deeply traversed. You can’t listen for data changes with arrays
Trying to make here is that the Object. DefineProperty unable to monitor the change of the array, with this question to view the source code, check the SRC/core/instance/state. The js method of initData, fragments of code is as follows:
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)
}
/ / to omit
function initData (vm: Component) {
/ / to omit
while (i--) {
const key = keys[i]
if(process.env.NODE_ENV ! = ='production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if(props && hasOwn(props, key)) { process.env.NODE_ENV ! = ='production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if(! isReserved(key)) { proxy(vm,`_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)}Copy the code
Important here is that the proxy and observe, so the question, why the proxy has been listening, why still need to observe listen again, continue to open the SRC/core/observer/index. The js, fragments of code is as follows:
export function observe (value: any, asRootData: ? boolean) :Observer | void {
if(! isObject(value) || valueinstanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if( shouldObserve && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) && ! value._isVue ) { ob =new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
Copy the code
Ob = New Observer(value); ob = new Observer(value); Then according to the method, finally found the, open the SRC/core/observer/array. Js core code is as follows:
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
/** * Intercept mutating methods and emit events */
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (. args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
Copy the code
ArrayMethods (arrayProto, arrayProto); arrayMethods (arrayProto, arrayProto); The first argument to def(SRC /core/util/lang. Js) is an Object. And wrap all of the array’s methods once again with Object.defineProperty, so that you can respect the Object.DefinePropertyAPI specification.
$set = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array splice = array
<div>
{{arr}}
</div>
let vm = new Vue({
el: '#app'.data() {
return{arr: [1, 2]}}}) // Only vm.$setTo update the vm.$set(vm.arr, 0, 31)
Copy the code
This implementation feels like a performance problem in that the array needs to be iterated over and the Object.defineProperty method called.
Object. DefineProperty is superior to proxy because it can intercept data of array type. The test code is as follows:
// Since proxy can definitely intercept objects, only arrays are used for testing
const handler = {
get (target, key) {
console.log('----get-----')
return target[key];
},
set (target, key, value) {
console.log('----set-----')
target[key] = value;
return true; }};const target = [1.2];
const arr = new Proxy(target, handler);
arr[0] = 3 // '----set-----'
Copy the code
So I think vue could have used a proxy instead of Object.defineProperty, and the performance would have been improved.
The above is a supplement I made to proxy and Object.defineProperty, I hope to point out if there is anything wrong.
conclusion
The above is mainly in reading the source code, found not very clear API and some methods, everyone can according to their own actual situation selective reading, the above is all content, if there is anything wrong, welcome to mention issues