Разобрать значение JavaScript по умолчанию и поведение присваивания из спецификации ECMA.

внешний интерфейс JavaScript

предисловие

Если вы опытный разработчик Vue, то должны знать, что принцип отзывчивости Vue достигается за счет перехвата получения и набора объектов.

// src/core/observer/index.js
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
        //...
    },
    set: function reactiveSetter (newVal) {
        //...
    }
  })

Поэтому, когда значение присваивается реактивной переменной, функция set запускается для обновления представления.

<template>
    <div >{{message}}</div>
</template>

<script>
    export default {
        data() {
            return {
                message:'hello world'
            }
        },
       mounted() {
            this.message = 'hello Vue'
       }
    }
</script>

Эта статья не имеет ничего общего с фреймворком Vue, но давайте подумаем о проблеме

为什么给响应式变量赋值会触发 set 函数,而不是直接赋值?

Если вы определите функцию set для свойств объекта, логика назначения по умолчанию не будет выполнена.Разве это не проблема с моим братом?

Фактически, JavaScript будет выполнять операции [[Get]] и [[Put]] соответственно при доступе к свойствам объекта или присвоении им значений. Это два встроенных поведения объектов по умолчанию, которые нельзя изменить.

Затем мы используем спецификацию ECMA, чтобы проанализировать, что JavaScript делает внутри, когда объект оценивается и назначается.

[[Get]]

Когда значение выполнения получено от объекта, выполняется операция [[Get]], которая определена в стандарте как таковая.

Судя по моему низкому уровню английского, приблизительно переведенный результат выглядит так:

  1. Сначала будет выполнена операция [[GetProperty]]. Ее функция состоит в том, чтобы определить, существует ли свойство объекта в текущем объекте. Если оно существует, она вернет свойство напрямую. В противном случае будет рекурсивно искать цепочку прототипов объекта. объекта и вернуть свойство после его нахождения.Возвращает значение undefined до конца цепочки прототипов.
  2. Если результат первого шага не определен, результат [[Get]] не определен, то есть в этом объекте нет такого свойства
  3. Если он не не определен, он определит, определено ли это свойство с помощью дескриптора данных, и если да, то вернет атрибут value дескриптора данных.
  4. Если это свойство определено с помощью дескриптора доступа, то есть функции get, функция get будет запущена, и после выполнения будет возвращен результат.

Через стандарт очевидно, что JavaScript выполняет логику при доступе к свойствам объекта.Когда это свойство не существует в текущем объекте, он будет искать по цепочке прототипов, поэтому пустые объекты также могут вызывать toString, valueOf и другие методы. , потому что все эти методы существуют в цепочке прототипов объекта, и если свойство определяет функцию get, она будет напрямую возвращать результат выполнения.

[[CanPut]]

[[Put]] немного сложнее, чем [[Get]] , как сказано в исходной спецификации.

Метод [[Put]] опирается на внутреннее поведение, называемое [[CanPut]], давайте посмотрим на его определение.

Во-первых, он определит, существует ли текущий атрибут в текущем объекте. Если да, продолжайте судить о том, имеет ли атрибут дескриптор доступа, то есть функцию set. Если функция set существует [[CanPut]], результатом будет true, в противном случае, если дескриптор доступа имеет значение false, если он не определен или недействителен. Или, когда свойство существует в текущем объекте, но дескриптор доступа не определен, тогда свойство должно быть определено с дескриптором данных, результатом [[CanPut]] является доступное для записи значение дескриптора данных, и, наконец, когда свойство не существует с текущим объектом, то же, что и [[Get]], он будет проходить по цепочке прототипов до конечной точки и повторно выполнять предыдущую логику

С точки зрения непрофессионала [[CanPut]] возвращает логическое значение, указывающее, может ли текущее свойство быть назначено.

[[Put]]

Back [[of Put]], когда значение [[CanPut]] выведет логику присваивания на false, и на основе этого параметра Throw, когда Throw равно true, выбрасывается исключение, и наоборот тишина,И этот Throw соответствует тому, включен ли строгий режим, а также проверяет, что сбой присваивания в строгом режиме вызовет ошибку

Когда значение [[CanPut]] равно true, это означает, что текущее свойство может быть назначено, и выполняется следующая логика.

  1. Если свойство находится в текущем объекте и имеет дескриптор данных, свойство value дескриптора данных возвращается напрямую, и запускается внутренний метод [[DefineOwnProperty]].

Как правило, присваивание свойства объекта обычно выполняет эту логику и возвращает свойство значения в качестве результирующего значения оператора присваивания, например

Присвойте номер 123 свойству a объекта obj, тогда 123 — это значение значения в дескрипторе данных свойства a, а окончательное значение, возвращаемое операцией [[Put]] — 123, что соответствует результирующее значение последней строки оператора присваивания

а также触发 [[DefineOwnProperty]] 这个内部方法Как понимать это предложение?Поведение [[DefineOwnProperty]] в спецификации очень сложное, здесь я приведу еще один небольшой пример

Перехватывая defineProperty и getOwnPropertyDescriptor, можно обнаружить, что поведение присваивания по умолчанию вызовет эти два перехватчика.Друзья, которые заинтересованы в большем поведении, могут просмотреть их самостоятельно по ссылке внизу

  1. В противном случае, если свойство находится в текущей цепочке объектов или прототипов и имеет дескриптор доступа, передайте значение в правой части выражения присваивания в качестве единственного параметра функции set и верните результат.

  2. В противном случае, если свойство находится в цепочке прототипов текущего объекта и имеет дескриптор данных, создайте новое свойство для текущего объекта со значением его дескриптора данных.{[[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}., и отбросить исходный дескриптор данных, и запустить внутренний метод [[DefineOwnProperty]] и вернуть

Что это значит, рассмотрим следующее

let obj = {}
Object.defineProperty(Object.prototype, 'a', {
    configurable: false,
    enumerable: false,
    value: "",
    writable: true
})

obj.a = 1
console.log(Object.getOwnPropertyDescriptor(obj,'a'))

// {value: 1, writable: true, enumerable: true, configurable: true}

Объект OBJ не имеет атрибута A, и свойство A определено в объекте-прототипе объекта, а дескриптор данных настраивается, ENUMERABLE имеет значение FALSE, но в объекте OBJ есть свойство A, когда оно окончательно назначено, в то время как Configurable , ПЕРЕЧИСЛИМОЕ True

Суммировать

Объединив определения [[Get]] и [[Put]] в JavaScript You Don’t Know Volume 1, можно сделать следующие выводы.

Когда объекту присваивается значение, запускается операция [[Get]]. Если это свойство существует в текущем объекте, оно будет оцениваться

  • Когда функция get включена, выполните функцию get и верните результат выполнения,
  • Если функция get отсутствует, вернуть атрибут value дескриптора данных.

Если на текущем объекте такого свойства нет, цепочка прототипов будет искаться вверх до конца, и в процессе поиска будут повторяться два вышеуказанных шага.

При присвоении значения объекту будет срабатывать [[Put]] (не идеальный [[Set]] ), если текущий объект имеет это свойство, то судить

  • Когда для записи установлено значение true, выполните операцию присваивания
  • Если для записи установлено значение false, строгий режим выдаст ошибку, нестрогий режим завершится с ошибкой.
  • Когда функция набора включена, выполните функцию набора

Если текущий объект не имеет этого свойства, он будет искать цепочку прототипов.Если свойство найдено в верхнем слое цепочки прототипов, оно будет оцениваться

  • Когда для записи установлено значение true, свойство будет создано для текущего объекта (непрототипная цепочка), а для дескриптора данных configurable, enumerable, writable будет установлено значение true, а значением будет присвоенное значение.
  • Если для записи установлено значение false, строгий режим выдаст ошибку, нестрогий режим завершится с ошибкой.
  • Когда функция набора включена, выполните функцию набора

Если свойство является дескриптором данных, также будет запущена внутренняя операция [[DefineOwnProperty]]. Если определены defineProperty и getOwnPropertyDescriptor, будут запущены эти два перехватчика.

использованная литература

Сводка JavaScript, о которой вы не знали

Стандарт ECMA-262