Прежде чем читать исходный код Vue, вы должны знать основное содержание javascript.

внешний интерфейс исходный код JavaScript Vue.js

предисловие

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()метод возвращает только что созданныйEventObject, поддерживает один параметр, указывающий тип события, как показано в следующей таблице:

  参数	        事件接口	        初始化方法
  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.definePropertyAPI стандартизирован.

Вернемся к теме, на самом деле, если это массив, то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