предисловие
Vue в настоящее время является одним из наиболее часто используемых интерфейсных фреймворков mvvm, обеспечивающих чувствительность к данным,watch
,computed
и другие чрезвычайно удобные функции и API, то как Vue реализует эти функции? Прежде чем изучать исходный код vue, вы должны понять следующее основное содержание javascript.Поняв это содержимое, вы сможете легче читать исходный код vue.
определение типа потока
Flow — это инструмент статической проверки типов для JavaScript, впервые предложенный командой Facebook на конференции Scale в 2014 году. Целью этой библиотеки является проверка ошибок типов в JavaScript, и разработчикам обычно не нужно изменять код для ее использования, поэтому стоимость использования очень низкая. В то же время он также обеспечивает дополнительную поддержку синтаксиса, позволяя разработчикам в большей степени играть роль Flow. Подводя итог в одном предложении: JavaScript превратился из языка со слабой типизацией в язык со строгой типизацией.
Основной тип обнаружения
Flow поддерживает примитивные типы данных, из которых void соответствует undefined в js, и есть в основном следующие типы:
boolean
number
string
null
void
При определении переменных вам нужно только объявить желаемый тип в ключевых местах.Основное использование выглядит следующим образом:
let str:number = 1;
let str1:string = 'a';
// 重新赋值
str = 'd' // error
str1 = 3 // error
обнаружение сложного типа
Flow поддерживает обнаружение сложных типов, в основном следующим образом:
Object
Array
Function
自定义Class
В основном используйте следующий пример кода:
// Object 定义
let o:Object = {
key: 123
}
//声明了Object的key
let o2:{key:string} = {
key: '111'
}
// Array 定义
//基于基本类似的数组,数组内都是相同类型
let numberArr:number[] = [12,3,4,5,2];
//另一个写法
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)];
//数组内包含各个不同的类型数据
//第4个原素没有声明,则可以是任意类型
let arr:[number,string,boolean] = [1,'a',true,function(){},];
Определение функции записывается следующим образом, чаще всего в исходном коде vue:
/**
* 声明带类型的函数
* 这里是声明一个函数fn,规定了自己需要的参数类型和返回值类型。
*/
function fn(arg:number,arg2:string):Object{
return {
arg,
arg2
}
}
/**
* vue源码片段
* src/core/instance/lifecycle.js
*/
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
// 省略
}
Пользовательский класс, объявите пользовательский класс, а затем используйте его как базовый тип, основной код выглядит следующим образом:
/**
* vue源码片段
* src/core/observer/index.js
*/
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
// 省略
}
}
Если вы используете поток.js напрямую, javascript не может работать на стороне браузера. Вы должны использовать подключаемый модуль babel. Плагин babel-preset-flow-vue используется в исходном коде vue и настраивается в babelrc. Фрагмент кода как следует:
// package.json 文件
// 省略
"devDependencies": {
// 省略
"babel-preset-flow-vue": "^1.0.0"
}
// 省略
// babelrc 文件
{
"presets": ["es2015", "flow-vue"],
"plugins": ["transform-vue-jsx", "syntax-dynamic-import"],
"ignore": [
"dist/*.js",
"packages/**/*.js"
]
}
объект
Здесь повторно анализируются только создание объектов, связанные операции со свойствами объектов, методы получения/установки, метки объектов и т. д. Цепочка прототипов и принцип наследования прототипов не являются важным содержанием этой статьи.
создать объект
Как правило, существует три способа записи объекта.Основной код выглядит следующим образом:
// 第一种 最简单的写法
let obj = { a: 1 }
obj.a // 1
typeof obj.toString // 'function'
// 第二种
let obj2 = Object.create({ a: 1 })
obj2.a // 1
typeof obj2.toString // 'function'
// 第三种
let obj3 = Object.create(null)
typeof obj3.toString // 'undefined'
Схема в основном следующая:
Object.create можно понимать как наследование объекта. Это новая функция ES5. Она должна быть совместима со старыми браузерами. Базовый код выглядит следующим образом (vue использует браузеры ie9+, поэтому обработка совместимости не требуется):
if (!Object.create) {
Object.create = function (o) {
function F() {} //定义了一个隐式的构造函数
F.prototype = o;
return new F(); //其实还是通过new来实现的
};
}
Среди них вы увидите использование в исходном коде vueObject.create(null)
Чтобы создать пустой объект, преимущество не нужно рассматривать проблему переименования свойств в цепочке прототипов.Фрагмент кода vue выглядит следующим образом:
// src/core/global-api/index.js
// 再Vue上定义静态属性options并且赋值位空对象,ASSET_TYPES是在vue上定义的'component','directive','filter'等属性
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
Связанная операция атрибута
На самом деле, когда объект создается, значение типа перечисления текущего объекта будет установлено по умолчанию для объекта.Если оно не установлено, все типы перечисления по умолчанию являются ложными, поэтому как определить объект и установить перечисление введите значение? Основное использование — новые возможности ES5.Object.defineProperty
.
Object.defineProperty(obj,prop,descriptor)
серединаdescriptor
Есть следующие параметры:
- configurable Если и только если configurable свойства имеет значение true, дескриптор свойства может быть изменен, а свойство может быть удалено из соответствующего объекта. Значение по умолчанию — ложь
- enumerable Это свойство может появляться в свойстве перечисления объекта тогда и только тогда, когда свойство enumerable истинно. Значение по умолчанию — ложь
- value Значение, соответствующее этому атрибуту. Может быть любым допустимым значением JavaScript (число, объект, функция и т. д.). По умолчанию не определено.
- значение, доступное для записи, может быть изменено оператором присваивания тогда и только тогда, когда свойство, доступное для записи, истинно. По умолчанию ложно.
- get Метод, предоставляющий геттер для свойства, или undefined, если геттера нет. При доступе к свойству будет выполнен метод, при выполнении метода никакие параметры не передаются, но будет передан объект this (из-за отношения наследования this здесь не обязательно является объектом, который определяет свойство ). По умолчанию не определено.
- set Метод, предоставляющий установщик для свойства, или undefined, если установщика нет. Этот метод срабатывает при изменении значения свойства. Метод примет единственный параметр, новое значение параметра для свойства. По умолчанию не определено
Примечание. Аксессоры (get и set) и значение не могут быть установлены в дескрипторе одновременно.
Полный пример кода выглядит следующим образом:
Object.defineProperty(obj,prop,
configurable: true,
enumerable: true,
writable: true,
value: '',
get: function() {
},
set: function() {
}
)
используяObject.getOwnPropertyDescriptor
Чтобы просмотреть значение типа перечисления свойства объекта, код конкретного примера выглядит следующим образом:
// 如果不设置枚举类型,默认都是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}
пройти черезObject.keys()
Для получения ключа объекта необходимо установитьenumerable
Установите значение true для получения, в противном случае возвращается пустой массив, код выглядит следующим образом:
let obj = {}
Object.defineProperty(obj, 'name', {
enumerable: true,
value : "wqzwh"
})
Object.keys(obj) // ['name']
пройти черезpropertyIsEnumerable
Вы можете судить, является ли определенный объект перечисляемым, код выглядит следующим образом:
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
пройти черезhasOwnProperty
чтобы определить, есть ли у объекта определенные собственные свойства; в отличие от оператора in, этот метод игнорирует свойства, унаследованные от цепочки прототипов. код показывает, как показано ниже:
// 使用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
}
}
// 普通创建属性
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
}
}
Примечание. Если унаследованное свойство объекта передается
Object.defineProperty
создан, иenumerable
не установленоtrue
,Такfor in
Все еще не могу перечислить свойства прототипа. (Спасибо @SunGuoQiang123 за указание на неправильную проблему, которая была изменена)
геттер/сеттер методы
пройти черезget/set
метод обнаружения изменений свойств, базовый код выглядит следующим образом:
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
этоz
собственностьfoo.prototype
свойства на и имеютget
метод, для второго проходаobj.z = 10
не будетobj
создать себяz
свойства, а на прямые триггеры прототипаget
метод.
Схема в основном следующая:
Если определено при создании текущего объектаz
свойства и установитьwritable
иconfigurable
заtrue
, тогда вы можете изменитьz
значение свойства и удалитьz
доступ снова после свойстваobj.z
Еще 1, тестовый код выглядит следующим образом:
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
Схема в основном следующая:
Object.defineProperty
серединаconfigurable
,enumerable
,writable
,value
,get
,set
Как несколько параметров связаны друг с другом? Наглядно это можно проиллюстрировать картинкой:
метка объекта
На самом деле, когда объект создается, он будет сопровождаться__proto__
тег proto, за исключением использованияObject.create(null)
Помимо создания объекта код выглядит следующим образом:
let obj = {x: 1, y: 2}
obj.__proto__.z = 3
console.log(obj.z) // 3
Object.preventExtensions
Метод используется для блокировки свойств объекта, чтобы они не могли быть расширены, то есть нельзя было добавлять новые свойства, но значение свойства можно было бы изменить, или свойство можно было бы удалить.Object.isExtensible
Основной код, используемый для определения возможности расширения объекта, выглядит следующим образом:
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}
Object.seal
Метод используется для запечатывания объекта, то есть объект не может ни расширять, ни удалять свойства (установите для каждого свойства значение false), единственное значение свойства по-прежнему можно изменить,Object.isSealed
Поскольку оценивается, запечатан ли объект, основной код выглядит следующим образом:
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
Object.freeze
Объект полностью заморожен, а значение свойства не может быть изменено на основе печати (вирабельность каждого свойства также устанавливается в false),Object.isFrozen
Чтобы определить, заморожен ли объект, основной код выглядит следующим образом:
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
Пользовательские события DOM
Прежде чем представить это предложение, сначала посмотрите на инструкцию модели в исходном коде vue, откройте ее.platforms/web/runtime/directives/model.js
, код фрагмента выглядит следующим образом:
/* 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')
}
})
}
// 省略
function trigger (el, type) {
const e = document.createEvent('HTMLEvents')
e.initEvent(type, true, true)
el.dispatchEvent(e)
}
вdocument.activeElement
элемент, сфокусированный в данный момент, вы можете использоватьdocument.hasFocus()
метод, чтобы увидеть, имеет ли текущий элемент фокус.
Для стандартных браузеров он предоставляет метод, который может быть вызван элементом: element.dispatchEvent().Однако перед использованием этого метода нам нужно сделать еще две вещи: создать и инициализировать. Итак, резюмируя:
document.createEvent()
event.initEvent()
element.dispatchEvent()
createEvent()
метод возвращает только что созданныйEvent
Object, поддерживает один параметр, указывающий тип события, как показано в следующей таблице:
参数 事件接口 初始化方法
HTMLEvents HTMLEvent initEvent()
MouseEvents MouseEvent initMouseEvent()
UIEvents UIEvent initUIEvent()
initEvent()
метод используется для инициализации черезDocumentEvent
интерфейс созданEvent
значение . Поддерживаются три параметра:initEvent(eventName, canBubble, preventDefault)
, Соответственно представлять имя события, может ли оно всплывать, и нужно ли предотвращать действие по умолчанию для события.
dispatchEvent()
Это должно инициировать выполнение в исходном коде vue выше.el.dispatchEvent(e)
, параметр e представляет объект события, которыйcreateEvent()
метод, возвращаемый созданнымEvent
объект.
Итак, как именно следует использовать этот материал? Например, обычайclick
метод, код выглядит следующим образом:
// 创建事件.
let event = document.createEvent('HTMLEvents');
// 初始化一个点击事件,可以冒泡,无法被取消
event.initEvent('click', true, false);
let elm = document.getElementById('wq')
// 设置事件监听.
elm.addEventListener('click', (e) => {
console.log(e)
}, false);
// 触发事件监听
elm.dispatchEvent(event);
метод расширения массива
каждый метод/какой-то метод
Принимает два аргумента, первый — функция (принимает три аргумента:数组当前项的值、当前项在数组中的索引、数组对象本身
), второй параметр — это объект области, который выполняет первый параметр функции, то есть значение, на которое указывает this в функции, упомянутой выше.Если не задано, значение по умолчанию не определено.
Ни один из методов не изменит исходный массив
- Every(): этот метод запускает данную функцию для каждого элемента в массиве и возвращает значение true, если функция возвращает значение true для каждого элемента.
- some(): этот метод запускает данную функцию для каждого элемента массива и возвращает значение true, если функция возвращает значение true для любого элемента.
Пример кода выглядит следующим образом:
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;
}));
Метод some возвращает значение, когда он встречает значение, которое возвращает true, и не продолжает выполняться, и то же самое верно для всех, первое значение — false, поэтому нет необходимости продолжать, и результат возвращается напрямую. .
getBoundingClientRect
Этот метод возвращает прямоугольный объект с четырьмя свойствами:left、top、right、bottom
, которые представляют собой расстояние между каждой стороной элемента и верхней и левой сторонами страницы соответственно.x、y
Указывает координаты положения верхнего левого угла.
рассчитанный по этому методуleft、top、right、bottom、x、y
Он будет меняться при прокрутке в области просмотра.Если вам нужно, чтобы значение свойства располагалось относительно верхнего левого угла всей веб-страницы, просто добавьте текущую позицию прокрутки к верхним и левым значениям свойств.
Для кроссбраузерной совместимости используйте window.pageXOffset и window.pageYOffset вместо window.scrollX и window.scrollY. Сценарии, не имеющие доступа к этим свойствам, могут использовать следующий код:
// 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
В IE координаты по умолчанию рассчитываются из (2,2), в результате чего конечное расстояние на два пикселя больше, чем в других браузерах, код выглядит следующим образом:
document.documentElement.clientTop; // 非IE为0,IE为2
document.documentElement.clientLeft; // 非IE为0,IE为2
// 所以为了保持所有浏览器一致,需要做如下操作
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
}
}
performance
Исходный код фрагмента в vue выглядит следующим образом:
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)
}
}
}
performance.mark
метод создает буфер с заданным именем в буфере ввода производительности браузера,performance.measure
Создает именованный буфер записи производительности между двумя указанными маркерами в браузере (называемыми начальным и конечным маркерами). Код теста выглядит следующим образом:
let _uid = 0
const perf = window.performance
function testPerf() {
_uid++
let startTag = `test-mark-start:${_uid}`
let endTag = `test-mark-end:${_uid}`
// 执行mark函数做标记
perf.mark(startTag)
for(let i = 0; i < 100000; i++) {
}
// 执行mark函数做标记
perf.mark(endTag)
perf.measure(`test mark init`, startTag, endTag)
}
Результаты теста можно найти в Google ChromePerformance
Результаты приведены ниже:
в браузереperformance
Модель обработки в основном выглядит следующим образом (Более подробное описание параметра):
Связанные с прокси
получить метод
get
Метод используется для перехвата операции чтения атрибута и может принимать три параметра: целевой объект, имя атрибута и сам экземпляр прокси (строго говоря, объект, на который нацелено поведение операции), из которых последний параметр является необязательным.
Перехватывать чтение свойств объектов, таких как proxy.foo и proxy['foo']
Основное использование заключается в следующем:
let person = {
name: "张三"
};
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 // 抛出一个错误
Если свойство не настраивается и недоступно для записи, прокси-сервер не может изменить свойство, в противном случае доступ к свойству через объект-прокси вызовет ошибку. Пример кода выглядит следующим образом:
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
имеет метод
Этот метод может принимать два параметра: целевой объект и имя запрашиваемого свойства.В основном он перехватывает следующие операции:
- Запрос атрибута: foo в прокси
- Запрос унаследованного свойства: foo в Object.create(proxy)
- с проверкой: with(proxy) { (foo); }
- Reflect.has()
Если исходный объект не настраивается или его расширение запрещено, то имеет перехват сообщит об ошибке. Основной пример кода выглядит следующим образом:
let obj = { a: 10 };
Object.preventExtensions(obj);
let p = new Proxy(obj, {
has: function(target, prop) {
return false;
}
});
'a' in p // TypeError is thrown
has
блокировать толькоin
оператор вступает в силу, правильноfor...in
Петля не работает. Основной пример кода выглядит следующим образом:
let stu1 = {name: '张三', score: 59};
let stu2 = {name: '李四', score: 99};
let handler = {
has(target, prop) {
if (prop === 'score' && target[prop] < 60) {
console.log(`${target.name} 不及格`);
return false;
}
return prop in target;
}
}
let oproxy1 = new Proxy(stu1, handler);
let oproxy2 = new Proxy(stu2, handler);
'score' in oproxy1
// 张三 不及格
// false
'score' in oproxy2
// true
for (let a in oproxy1) {
console.log(oproxy1[a]);
}
// 张三
// 59
for (let b in oproxy2) {
console.log(oproxy2[b]);
}
// 李四
// 99
использоватьwith
Назначение ключевого слова — упростить работу по написанию множественных обращений к одному и тому же объекту.Основной метод написания следующий:
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;
}
использоватьwith
ключевые слова могут привести к снижению производительности кода, используйтеlet
Определите переменные по сравнению с использованиемvar
Определение переменных может улучшить производительность.Пример кода выглядит следующим образом:
// 不使用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.310302734375ms
}
func();
// 使用with并且使用let定义变量
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.533935546875ms
}
funcWith();
// 使用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.078857421875ms
}
funcWith();
Движок js имеет фазу компиляции перед выполнением кода.with
Когда ключевое слово используется, js-движок знает, что a является атрибутом obj, и может статически анализировать код, чтобы улучшить синтаксический анализ идентификатора, тем самым оптимизируя код и повышая эффективность его выполнения. использовалwith
После ключевого слова js-движок не может различить, является ли переменная a локальной переменной или атрибутом obj, поэтому после того, как js-движок встретит ключевое слово with, он откажется от оптимизации этого кода, поэтому эффективность выполнения снижается. .
использоватьhas
перехват методаwith
ключевое слово, пример кода выглядит следующим образом:
let stu1 = {name: '张三', score: 59};
let handler = {
has(target, prop) {
if (prop === 'score' && target[prop] < 60) {
console.log(`${target.name} 不及格`);
return false;
}
return prop in target;
}
}
let oproxy1 = new Proxy(stu1, handler);
function test() {
let score
with(oproxy1) {
return score
}
}
test() // 张三 不及格
в настоящее время используетwith
При использовании ключевых слов это в основном связано с потерей производительности, вызванной тем, что движок js анализирует область переменных в блоке кода, поэтому мы можем улучшить его производительность, определив локальные переменные. Измените пример кода следующим образом:
// 修改后
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.7109375ms
}
funcWith();
Но в реальном использованииwith
Определять локальные переменные в блоке кода не очень удобно, поэтому удаление функции частого просмотра области видимости должно улучшить производительность некоторых частей кода.После тестирования время выполнения почти такое же.Измененный код следующее:
function func() {
console.time("func");
let obj = {
a: [1, 2, 3]
};
let v = obj.a[0];
console.timeEnd("func");// 0.01904296875ms
}
func();
// 修改后
function funcWith() {
console.time("funcWith");
const obj = {
a: [1, 2, 3]
};
with (obj) {
let v = a[0];
}
console.timeEnd("funcWith");// 0.028076171875ms
}
funcWith();
с участиемhas
Каков эффект выполнения после функции?Код фрагмента выглядит следующим образом:
// 第一段代码其实has方法没用,只是为了对比使用
console.time("测试");
let stu1 = {name: '张三', score: 59};
let handler = {
has(target, prop) {
if (prop === 'score' && target[prop] < 60) {
console.log(`${target.name} 不及格`);
return false;
}
return prop in target;
}
}
let oproxy1 = new Proxy(stu1, handler);
function test(oproxy1) {
return {
render: () => {
return oproxy1.score
}
}
}
console.log(test(oproxy1).render()) // 张三 不及格
console.timeEnd("测试"); // 0.719970703125ms
console.time("测试");
let stu1 = {name: '张三', score: 59};
let handler = {
has(target, prop) {
if (prop === 'score' && target[prop] < 60) {
console.log(`${target.name} 不及格`);
return false;
}
return prop in target;
}
}
let oproxy1 = new Proxy(stu1, handler);
function test(oproxy1) {
let score
return {
render: () => {
with(oproxy1) {
return score
}
}
}
}
console.log(test(oproxy1).render()) // 张三 不及格
console.timeEnd("测试"); // 0.760009765625ms
использовать в vuewith
Фрагмент кода ключевого слова выглядит следующим образом, в основном черезproxy
перехватыватьAST
Переменные и методы, задействованные в дереве языка, и определяют,AST
Существуют ли определенные переменные и методы в дереве языка и почемуvue
Будет использоватьсяwith
ключевые слова, в частностиНажмите, чтобы просмотреть
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
}
}
outerHTML
Открытымplatforms/web/entry-runtime-width-compile.js
,ПроверятьgetOuterHTML
метод, код фрагмента выглядит следующим образом:
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
}
}
Так как в IE9-11SVG
Элемент метки неinnerHTML
иouterHTML
эти два свойства, поэтому будетelse
заявление после
2018-07-17 Дополнение
здесь дляproxy
иObject.defineProperty
существуетvue
Используйте его в исходном коде для дополнительного объяснения.vue
определено вdata
На самом деле черезObject.defineProperty
отслеживать изменения, если они определеныdata
просто объекты, согласноObject.defineProperty
Введение в API разумно, но что, если это массив? Как это достигается?
Уведомление:
Object.defineProperty
Есть определенные недостатки: только дляobj
Если уровень объекта слишком глубокий, необходимо глубоко пройти весь объект; для массивов невозможно отслеживать изменения данных.
Что я хочу сказать здесь, так это то, чтоObject.defineProperty
Не удалось отследить изменения массива, проверьте исходный код с этим вопросом, сначала проверьтеsrc/core/instance/state.js
серединаinitData
метод, код фрагмента выглядит следующим образом:
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)
}
// 省略
function initData (vm: Component) {
// 省略
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 */)
}
Главное здесьproxy
иobserve
, тогда вопрос, почемуproxy
Он уже отслежен, зачем он вам еще нужен?observe
Слушай еще раз, продолжай открыватьsrc/core/observer/index.js
, код фрагмента выглядит следующим образом:
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof 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
}
Судя здесьvalue
тип или напрямую, если значение является объектомreturn
, если это массив, он будет продолжать выполнятьсяob = new Observer(value)
, на самом деле, снова слушает. Затем по найденному методу открываемsrc/core/observer/array.js
Основной код выглядит следующим образом:
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
})
})
почему здесьArray.prototype
назначить вarrayProto
, и переопределить переменнуюarrayMethods
наследоватьarrayProto
, я лично думаю, что это небольшая хитрость, так чтоmethodsToPatch
в методеdef
(src/core/util/lang.js
Метод в файле на самом делеObject.defineProperty
) Первый параметр — это объект, и используются все методы массиваObject.defineProperty
Заверните его снова, чтобы его можно было уважатьObject.defineProperty
API стандартизирован.
Вернемся к теме, на самом деле, если это массив, тоvue
нужно пройтиvm.$set
Чтобы вовремя обновить попытку, после тестирования было обнаружено, что вызовvm.$set
Изменение массива фактически вызывает массивsplice
метод, в то время какsplice
Метод снова отслеживается, поэтому исходный массив вопросов также можно отслеживать.Код теста выглядит следующим образом:
<div>
{{arr}}
</div>
let vm = new Vue({
el: '#app',
data() {
return {
arr: [1, 2]
}
}
})
// 只能通过vm.$set来更新试图
vm.$set(vm.arr, 0, 31)
Эта реализация чувствует, что есть проблема с производительностью, то есть массив нужно пройти и вызвать
Object.defineProperty
метод.
скажи это сноваproxy
, на самом деле, это также имеетget
иset
метод,proxy
Это на самом деле превосходитObject.defineProperty
, так как он может перехватывать данные типа массива, тестовый код выглядит следующим образом:
// 因为proxy肯定能拦截对象,所以这里只用数组来做测试
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-----'
Так что я думаю, vue можно использовать полностью
proxy
заменитьObject.defineProperty
, производительность также может быть улучшена в определенной степени.
Вышеупомянутое мое правоproxy
иObject.defineProperty
Дополнение, если что-то не так, я надеюсь, что смогу указать на это.
Суммировать
Вышеупомянутое в основном при чтении исходного кода, и обнаружилось, что это не очень понятноapi
И некоторые методы, которые каждый может выборочно прочитать в соответствии со своей реальной ситуацией, вышеперечисленное - это все содержание, если что-то не так, добро пожаловать на упоминаниеissues