Объясните прототипы JavaScript

JavaScript

Я помню, как раньше видел вопрос о Zhihu:Интервью с 5-летним фронтендомСеть прототиповЯ не могу понять, там полно Vue, React и других реализаций, стоит ли таким людям его использовать?. Когда я писал статью, я вернулся и прочитал этот вопрос, было более 300 ответов, и многие большие ребята ответили на этот вопрос, что показывает, что этот вопрос все еще привлекает много внимания. В последние годы, с ростом популярности ES6, TypeScript и подобных промежуточных языков, у нас редко есть доступ к прототипам в бизнес-разработке.В основном мы используем классы ES6, которые приходят и уходят более простым и интуитивно понятным способом.Прототипы делают.

На самом деле, на мой взгляд, я считаю цепочку прототипов очень важным базовым знанием. Если человек говорит, что владеет языком C, но не знаком с ассемблером, вы ему поверите? Я думаю, что Зима сказала довольно кратко:

Это также включает в себя навыки интервьюера, о которых я говорил ранее.Интервью представляет собой систематическую оценку способности человека выяснить, в чем он хорош, систематически спрашивать.

Прототипы могут очень хорошо объяснить проблему, как минимум у него будет большой минус в дизайне библиотек, и могут быть проблемы с обучаемостью, или он может вообще не знать язык JS, но это не значит, что полагаясь на проблему Можно судить, что этого человека нельзя использовать.

Эта статья включает в себя следующее:

  1. JavaScript-прототип
  2. конструктор иprototype
  3. Сеть прототипов
  4. Использование прототипа
  5. Связь между классами ES6 и конструкторами
  6. прототипное наследование
  7. Особенности синтаксиса JavaScript и прототипов
  8. Загрязнение прототипа
  9. Добавьте вопрос интервью, связанный с прототипом, с которым вы столкнулись на недавнем собеседовании при приеме на работу в школу.

JavaScript-прототип

Прототип должен называться по-английскиprototype, у любого объекта есть прототип, мы можем передавать нестандартные свойства__proto__Чтобы получить доступ к прототипу объекта:

// 纯对象的原型默认是个空对象
console.log({}.__proto__); // => {}

function Student(name, grade) {
  this.name = name;
  this.grade = grade;
}

const stu = new Student('xiaoMing', 6);
// Student 类型实例的原型,默认也是一个空对象
console.log(stu.__proto__); // => Student {}

__proto__Это нестандартное свойство, если вы хотите получить доступ к прототипу объекта, рекомендуется использовать новый ES6Reflect.getPrototypeOfилиObject.getPrototypeOf()метод. Нестандартный атрибут означает, что атрибут может быть напрямую изменен или удален в будущем, возможно, в будущем будет использоваться новый стандарт.Symbol.protoВ качестве ключа для доступа к прототипу объекта это нестандартное свойство может быть удалено.

console.log({}.__proto__ === Object.getPrototypeOf({})); // => true

мы можем пройти__proto__Измените прототип объекта, назначив свойства напрямую. Более рекомендуемый подход — использовать ES6.Reflect.setPrototypeOfилиObject.setPrototypeOf. В любом случае тип устанавливаемого значения может быть только объектом или нулевым, другие типы не работают:

const obj = { name: 'xiaoMing' };
// 原型为空对象
console.log(obj.__proto__); // => {}

obj.__proto__ = 666;
// 非对象和 null 不生效
console.log(obj.__proto__); // => {}

// 设置原型为对象
obj.__proto__ = { a: 1 };
console.log(obj.__proto__); // => { a: 1 }
console.log(Reflect.getPrototypeOf(obj)); // => { a: 1 }

Выдает, если устанавливаемое значение не является расширяемымTypeError:

const frozenObj = Object.freeze({});
// Object.isExtensible(obj) 可以判断 obj 是不是可扩展的
console.log(Object.isExtensible(frozenObj)); // => false
frozenObj.__proto__ = null; // => TypeError: #<Object> is not extensible

если объект__proto__При присвоении значения null ситуация усложняется, посмотрите на следующий тест, он может показаться вам странным:

const obj = { name: 'xiaoming' };

obj.__proto__ = null;
// !: 为什么不是 null, 就好像 __proto__ 被 delete 了
console.log(obj.__proto__); // => undefined
// 说明确实将原型设置为 null 了
console.log(Reflect.getPrototypeOf(obj)); // => null

// 再次赋值为 null
obj.__proto__ = null;
// 黑人问号???咋不是之前的 undefined 呢?
console.log(obj.__proto__); // => null

obj.__proto__ = { a: 1 };
console.log(obj.__proto__); // => { a: 1 }
// __proto__ 就像一个普通属性一样 obj.xxx = { a: 1 }
// 并没有将原型设置成功
console.log(Reflect.getPrototypeOf(obj)); // => null

Reflect.setPrototypeOf(obj, { b: 2 });
// __proto__ 被设置为 null 后,obj 的 __proto__ 属性和一个普通的属性没有区别
console.log(obj.__proto__); // => { a: 1 }
// 使用 Reflect.setPrototypeOf 是可以设置原型的
console.log(Reflect.getPrototypeOf(obj)); // => { b: 2 }

фактически__proto__определяется вObject.prototypeсвойства средства доступа на , то есть с помощьюgetterа такжеsetterсвойства, определяемые__proto__изgetterмы можем получить объект[[Prototype]], который является прототипом. Ниже моя симуляция__proto__Код поведения, обратите внимание на случай, когда в следующем коде установлено значение null:

const weakMap = new WeakMap();
Object.prototype = {
  get __proto__() {
    return this['[[prototype]]'] === null ? weakMap.get(this) : this['[[prototype]]'];
  },
  set __proto__(newPrototype) {
    if (!Object.isExtensible(newPrototype)) throw new TypeError(`${newPrototype} is not extensible`);

    const isObject = typeof newPrototype === 'object' || typeof newPrototype === 'function';
    if (newPrototype === null || isObject) {
      // 如果之前通过 __proto__ 设置成 null
      // 此时再通过给 __proto__ 赋值的方式修改原型都是徒劳
      /// 表现就是 obj.__proto__ = { a: 1 } 就像一个普通属性 obj.xxx = { a: 1 }
      if (this['[[prototype]]'] === null) {
        weakMap.set(this, newPrototype);
      } else {
        this['[[prototype]]'] = newPrototype;
      }
    }
  },
  // ... 其它属性如 toString,hasOwnProperty 等
};

В общем: если объект__proto__имущество назначается какnull, его прототип в настоящее время действительно изменен на null, но вы хотите передать__proto__Недопустимый метод присваивания при установке прототипа.В настоящее время__proto__ничем не отличается от обычного свойства, только черезReflect.setPrototypeOfилиObject.setPrototypeOfмодифицировать прототип. Прототип — это свойство внутри объекта[[prototype]],а такжеReflect.setPrototypeOfПричина, по которой прототип может быть изменен, заключается в том, что он напрямую изменяет свойство прототипа объекта, то есть внутреннее свойство объекта.[[prototype]]передача имущества без прохождения__proto__изgetter.

Конструкторы и прототипы

Конструктор на английском естьconstructor, в JavaScript,Функции могут использоваться как конструкторы. Конструктор также можно назвать классом, а конструктор Student можно назвать классом Student. Мы можем создать экземпляр с помощью нового конструктора. По соглашению мы используем большой верблюжий кейс для функций, используемых в качестве конструкторов:

function Apple() {}
const apple = new Apple();
console.log(apple instanceof Apple); // => true

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

// 实例的原型即 apple1.__proto__
console.log(apple1.__proto__ === Apple.prototype); // => true
console.log(apple2.__proto__ === Apple.prototype); // => true

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

console.log(Apple.prototype); // => Apple {}
console.log(Object.keys(Apple.prototype)); // => []
console.log(Apple.prototype.__proto__ === {}.__proto__); // true

Конструкторprototypeесть одинconstructorсвойство, указывающее на сам конструктор:

console.log(Apple.prototype.constructor === Apple); // => true

этоconstructorСвойство не проходное, можно понять, что свойство определяется следующим образом:

Object.defineProperty(Apple.prototype, 'constructor', {
  value: Student,
  writable: true,
  // 不可枚举,无法通过 Object.keys() 获取到
  enumerable: fasle,
});

__proto__,prototype,constructor,Appleфункция, примерappleи прототип объекта[[prototype]]Отношение между:

relationship.png

Некоторые люди могут поставить__proto__а такжеprototypeЗапутать. С точки зрения перевода их обоих можно назвать прототипами, но на самом деле это две совершенно разные вещи.

__proto__существует на всех объектах,prototypeСуществует для всех функций, и их отношение таково: функцияprototypeвсе экземпляры созданы с использованием новой функции__proto__. Функции также являются объектами, поэтому функции также имеют__proto__а такжеprototype.

Уведомление: Если я упоминаю прототип конструктора в своей статье, это относится к прототипу конструктора__proto__, а не свойство прототипа конструктора.

Сеть прототипов

Такпрототип объектаКаковы характеристики?

При доступе к свойству объекта obj, если оно не существует в obj, оно перейдет к прототипу объекта, которыйobj.__proto__Найдите это свойство. Если есть, вернуть это свойство, если нет, перейти к прототипу прототипа объекта obj, которыйobj.__proto__.__proto__Найдите его и повторите описанные выше шаги. до визитачистый объектПрототипObject.prototype, если нет, продолжайте поискObject.prototype.__proto__, который на самом деле равен null, напрямую возвращает значение undefined.

Например:

function Student(name, grade) {
  this.name = name;
  this.grade = grade;
}

const stu = new Student();
console.log(stu.notExists); // => undefined

доступstu.notExistsВесь процесс таков:

  1. Первый взглядstuСуществует ли наnotExists, не существует, поэтому см.stu.__proto__
  2. stu.__proto__не существуетnotExistsсвойства см.stu.__proto__.__proto__, по фактучистый объектПрототип:Object.prototype
  3. чистый объектне существует в прототипеnotExistsсвойства, дальше вверх, кstu.__proto__.__proto__.__proto__Поднимитесь, чтобы найти его, на самом деле он нулевой
  4. ноль не существуетnotExistsсвойство, возвращает неопределенное

У некоторых читателей после прочтения вышеизложенного могут возникнуть сомнения, а прототип объекта будет найден в концечистый объектПрототип? Просто проверьте это:

console.log(stu.__proto__.__proto__ === {}.__proto__); // => true

чистый объектПрототип прототипа равен нулю:

console.log(new Object().__proto__.__proto__); // => null

Цепочка, образованная между каждым прототипом, называется цепочкой прототипов.

prototypeChain1.png

Подумайте об этом, функцияStudentКак должна выглядеть цепочка прототипов?

functionPrototypeChain.png

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

При использовании конструктора для определения типа мы обычно определяем метод класса в прототипе, что идеально подходит для функции указания this.

function Engineer(workingYears) {
  this.workingYears = workingYears;
}

// 不能使用箭头函数,箭头函数的 this 在声明的时候就根据上下文确定了
Engineer.prototype.built = function () {
  // this 这里就是执行函数调用者
  console.log(`我已经工作 ${this.workingYears} 年了, 我的工作是拧螺丝...`);
};

const engineer = new Engineer(5);
// this 会正确指向实例,所以 this.workingYears 是 5
engineer.built(); // => 我已经工作 5 年了, 我的工作是拧螺丝...
console.log(Object.keys(engineer)); // => [ 'workingYears' ]

Таким образом, все экземпляры могут получить доступ к этому методу, и этот метод должен только занимать кусок памяти, экономя память, и указатель this также может правильно указывать на экземпляр класса.

Однако к методу, определенному таким образом, нельзя получить доступ через Object.keys(), в конце концов, это не его собственное свойство:

const obj = {
  func() {},
};

console.log(Object.keys(obj)); // => [ 'func' ]

function Func() {}
Func.prototype.func = function () {};
console.log(Object.keys(new Func())); // => []

Если вы просто хотите определить свойства экземпляра, вы можете передать толькоthis.xxx = xxxспособ определения методов экземпляра:

function Engineer(workingYears) {
  this.workingYears = workingYears;
  this.built = function () {
    console.log(`我已经工作 ${this.workingYears} 年了, 我的工作是拧螺丝...`);
  };
}

const engineer = new Engineer(5);
console.log(Object.keys(engineer)); // => [ 'workingYears', 'built' ]

На самом деле многие методы в JavaScript определены в прототипе конструктора, например, Array.prototype.slice, Object.prototype.toString и т. д.

Связь между классами ES6 и конструкторами

Многие языки имеют парадигмы объектно-ориентированного программирования, такие как java, c#, python и т. д. Классы ES6 упрощают объектно-ориентированное программирование для разработчиков, переходящих от них к JavaScript.

ES6 class

фактически,Класс ES6 — это синтаксический сахар для конструкторов.. Давайте посмотрим, во что Babel компилирует классы ES6:

Оригинальный код:

class Circle {
  constructor(x, y, r) {
    this.x = x;
    this.y = y;
    this.r = r;
  }

  draw() {
    console.log(`画个坐标为 (${this.x}, ${this.y}),半径为 ${this.r} 的圆`);
  }
}

babel + babel-preset-es2015-looseСкомпилированный результат:

'use strict';

// Circle class 可以理解为就是一个构造器函数
var Circle = (function () {
  function Circle(x, y, r) {
    this.x = x;
    this.y = y;
    this.r = r;
  }

  var _proto = Circle.prototype;

  // class 方法定义在 prototype 上
  _proto.draw = function draw() {
    console.log(
      '\u753B\u4E2A\u5750\u6807\u4E3A (' +
        this.x +
        ', ' +
        this.y +
        ')\uFF0C\u534A\u5F84\u4E3A ' +
        this.r +
        ' \u7684\u5706'
    );
  };

  return Circle;
})();

С первого взгляда становится ясно, что класс ES6 является конструктором, а методы класса определены в прототипе конструктора.

расширяет наследование

Давайте посмотрим на использованиеextendsКак преобразовать при наследовании.

Оригинальный код:

class Shape {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class Circle extends Shape {
  constructor(x, y, r) {
    super(x, y);
    this.r = r;
  }

  draw() {
    console.log(`画个坐标为 (${this.x}, ${this.y}),半径为 ${this.r} 的圆`);
  }
}

babel + babel-preset-es2015-looseСкомпилированный результат:

'use strict';

// 原型继承
function _inheritsLoose(subClass, superClass) {
  subClass.prototype = Object.create(superClass.prototype);
  subClass.prototype.constructor = subClass;
  // 让子类可以访问父类上的静态属性,其实就是定义在构造器自身上的属性
  // 例如父类有 Person.say 属性,子类 Student 通过可以通过 Student.say 访问
  subClass.__proto__ = superClass;
}

var Shape = function Shape(x, y) {
  this.x = x;
  this.y = y;
};

var Circle = (function (_Shape) {
  _inheritsLoose(Circle, _Shape);

  function Circle(x, y, r) {
    var _this;

    // 组合继承
    _this = _Shape.call(this, x, y) || this;
    _this.r = r;
    return _this;
  }

  var _proto = Circle.prototype;

  _proto.draw = function draw() {
    console.log(
      '\u753B\u4E2A\u5750\u6807\u4E3A (' +
        this.x +
        ', ' +
        this.y +
        ')\uFF0C\u534A\u5F84\u4E3A ' +
        this.r +
        ' \u7684\u5706'
    );
  };

  return Circle;
})(Shape);

Все расширения ES6 реализуют прототипное наследование + наследование композиции.

Конструктор подкласса вызывает конструктор родительского класса и указывает его экземпляру подкласса для достиженияОбъединить свойства экземпляра родительского класса с экземпляром дочернего класса:

// 组合继承
_this = _Shape.call(this, x, y) || this;

_inheritsLooseЭта функция реализует прототипное наследование, описанное в следующем разделе.

прототипное наследование

говорящий原型继承Прежде чем мы поговорим继承эта фраза. Я думаю, в общем смысле继承говорит:Если класс A наследуется от класса B, то экземпляры A наследуют свойства экземпляра B..

原型继承это继承С точки зрения простого继承Не совсем то же самое, это:Экземпляры A могут наследовать свойства прототипа B.

Давайте определим прототипное наследование:

对于类 A 和类 B,如果满足 A.prototype.__proto__ === B.prototype,那么 A 原型继承 B

prototypeExtends.png

На самом деле приведенное выше определение является слишком строгим, я думаю, пока прототип B находится в цепочке прототипов A, этого достаточно, чтобы свойства прототипа B уже были доступны для экземпляра A. Можно сказать, что приведенное выше определение наследуется напрямую, но может быть два или более уровня наследования.

Как реализовать прототипное наследование? Самый простой способ - установить его напрямуюA.prototype === new B(), пусть прототип A будет экземпляром B:

function A() {}
function B() {
  this.xxx = '污染 A 的原型';
}

A.prototype = new B();

console.log(A.prototype.__proto__ === B.prototype); // => true

Но этот способ приведет к тому, что свойства экземпляра B загрязнят прототип A. Решение состоит в том, чтобы связать его с пустой функцией.Пустая функция никогда не загрязнит цепочку прототипов атрибутами экземпляра:

function A(p) {
  this.p = p;
}

function B() {
  this.xxx = '污染原型';
}

// 空函数
function Empty() {}

Empty.prototype = B.prototype;
A.prototype = new Empty();
// 修正 constructor 指向
A.prototype.constructor = A;

// 满足原型继承的定义
console.log(A.prototype.__proto__ === B.prototype); // => true

const a = new A('p');
console.log(a instanceof A); // => true

const b = new B();
console.log(b instanceof B); // => true

// a 也是 B 的实例
console.log(a instanceof B); // => true
console.log(a.__proto__.__proto__ === B.prototype); // => true

Цепочка прототипов, нарисованная с помощью программного обеспечения для рисования, которое поставляется с Windows_〆(´Д ` ):

prototypeChain.png

использоватьObject.create, мы можем реализовать прототипное наследование более просто, что является функцией инструмента, используемой Babel выше._inheritsLoose:

function _inheritsLoose(subClass, superClass) {
  // Object.create(prototype) 返回一个以 prototype 为原型的对象
  subClass.prototype = Object.create(superClass.prototype);
  subClass.prototype.constructor = subClass;
  // 我们上面实现的原型继承没有设置这个,但是 class 的继承会设置子类的原型为父类
  subClass.__proto__ = superClass;
}

Особенности синтаксиса JavaScript и прототипов

На самом деле, многие грамматические особенности связаны с прототипами.Что касается прототипов, мы продолжим говорить о некоторых знаниях, связанных с прототипами в грамматических функциях JavaScript.

новый операторный принцип

Что происходит, когда мы используем new в функции.

Использование кода для его описания:

function isObject(value) {
  const type = typeof value;
  return value !== null && (type === 'object' || type === 'function');
}

/**
 * constructor 表示 new 的构造器
 * args 表示传给构造器的参数
 */
function New(constructor, ...args) {
  // new 的对象不是函数就抛 TypeError
  if (typeof constructor !== 'function') throw new TypeError(`${constructor} is not a constructor`);

  // 创建一个原型为构造器的 prototype 的空对象 target
  const target = Object.create(constructor.prototype);
  // 将构造器的 this 指向上一步创建的空对象,并执行,为了给 this 添加实例属性
  const result = constructor.apply(target, args);

  // 上一步的返回如果是对象就直接返回,否则返回 target
  return isObject(result) ? result : target;
}

Просто проверьте это:

function Computer(brand) {
  this.brand = brand;
}

const c = New(Computer, 'Apple');
console.log(c); // => Computer { brand: 'Apple' }

Принцип оператора instanceof

instanceof используется для определения того, является ли объект экземпляром класса, если obj instance A, мы говорим, что obj является экземпляром A.

Его принцип очень прост, в одном предложении:obj instanceof конструктора A, что эквивалентно оценке того, является ли прототип A прототипом obj (он также может быть вторичным прототипом).

Код:

function instanceOf(obj, constructor) {
  if (!isObject(constructor)) {
    throw new TypeError(`Right-hand side of 'instanceof' is not an object`);
  } else if (typeof constructor !== 'function') {
    throw new TypeError(`Right-hand side of 'instanceof' is not callable`);
  }

  // 主要就这一句
  return constructor.prototype.isPrototypeOf(obj);
}

Просто проверьте это:

function A() {}
const a = new A();

console.log(a instanceof A); // => true
console.log(instanceOf(a, A)); // => true

Загрязнение прототипа

Осенью 2019 года прошлого года, когда я еще был стажером на крупном отечественном заводе, lodash взломал серьезную уязвимость в системе безопасности:Серьезная уязвимость в библиотеке Lodash, затрагивающая более 4 миллионов проектов.. Это нарушение безопасности связано сЗагрязнение прототипавызванный.

Загрязнение прототипа относится к:

Злоумышленник каким-то образом модифицирует прототип объекта JavaScript.

Хотя можно сказать, что любой прототип загрязнен, это может вызвать проблемы, но обычно мы называем загрязнение прототипаObject.prototypeзагрязненный.

Опасности загрязнения прототипа

проблемы с производительностью

Возьмем самый простой пример:

Object.prototype.hack = '污染原型的属性';
const obj = { name: 'xiaoHong', age: 18 };
for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(obj[key]);
  }
}

/* =>
xiaoHong
18
*/

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

вызывать неожиданные логические ошибки

Посмотрите на конкретный случай уязвимости безопасности узла:

'use strict';

const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const path = require('path');

const isObject = (obj) => obj && obj.constructor && obj.constructor === Object;

function merge(a, b) {
  for (var attr in b) {
    if (isObject(a[attr]) && isObject(b[attr])) {
      merge(a[attr], b[attr]);
    } else {
      a[attr] = b[attr];
    }
  }
  return a;
}

function clone(a) {
  return merge({}, a);
}

// Constants
const PORT = 8080;
const HOST = '127.0.0.1';
const admin = {};

// App
const app = express();
app.use(bodyParser.json());
app.use(cookieParser());

app.use('/', express.static(path.join(__dirname, 'views')));
app.post('/signup', (req, res) => {
  var body = JSON.parse(JSON.stringify(req.body));
  var copybody = clone(body);
  if (copybody.name) {
    res.cookie('name', copybody.name).json({
      done: 'cookie set',
    });
  } else {
    res.json({
      error: 'cookie not set',
    });
  }
});
app.get('/getFlag', (req, res) => {
  var аdmin = JSON.parse(JSON.stringify(req.cookies));
  if (admin.аdmin == 1) {
    res.send('hackim19{}');
  } else {
    res.send('You are not authorized');
  }
});
app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);

Уязвимость этого кода заключается в функции слияния, мы можем атаковать ее так:

curl -vv --header 'Content-type: application/json' -d '{"__proto__": {"admin": 1}}' 'http://127.0.0.1:4000/signup';

curl -vv 'http://127.0.0.1/getFlag'

сначала запросить/signupинтерфейс, в сервисе NodeJS мы называем уязвимыйmergeметод и через__proto__дляObject.prototype(потому что{}.__proto__ === Object.prototype), чтобы добавить предыдущий новый атрибутadmin, а значение равно 1.

запросить сноваgetFlagинтерфейс, который обращается к прототипу объекта наadmin,Условные операторыadmin.аdmin == 1дляtrue, служба атакована.

Предотвращение загрязнения прототипа

Фактически, загрязнение прототипа в основном происходит при вызове функций, которые изменяют или расширяют свойства объекта, такие как значения по умолчанию в lodash и расширение в jquery. Самое главное для предотвращения загрязнения прототипа — иметь чувство предотвращения и выработать хорошие привычки кодирования.

Object.create(null)

Когда я смотрю на исходный код некоторых библиотек классов, я часто вижу такую ​​операцию, напримерEventEmitter3. Создайте объект без прототипа через Object.create(null), даже если вы его установили__proto__тоже не работает, потому что его прототип изначально нулевой, нет__proro__изsetter.

const obj = Object.create(null);
obj.__proto__ = { hack: '污染原型的属性' };
const obj1 = {};
console.log(obj1.__proto__); // => {}

Object.freeze(obj)

Объект obj можно заморозить с помощью Object.freeze(obj).Замороженный объект не может быть изменен и становится нерасширяемым объектом. Как упоминалось ранее, прототип нерасширяемого объекта нельзя изменить, и будет выброшено исключение TypeError:

const obj = Object.freeze({ name: 'xiaoHong' });
obj.xxx = 666;
console.log(obj); // => { name: 'xiaoHong' }
console.log(Object.isExtensible(obj)); // => false
obj.__proto__ = null; // => TypeError: #<Object> is not extensible

Прошло почти три месяца с тех пор, как я ушла из компании, где была стажером, каждый раз вспоминаюnpm installВсе показывают десятки обнаруженных уязвимостей зависимостей. Давно наверное без апгрейда накопилось столько лазеек.Во всяком случае не рискну апгрейдиться невзначай.Прошлый баг проверялся давно и результат вызван апгрейдом аксиоса. Не знаю, обновился ли он еще.

Вопрос интервью, связанный с прототипом, с которым мы столкнулись на недавнем собеседовании при приеме на работу в школу.

Недавно я столкнулся со следующим вопросом на собеседовании на крупной фабрике:

function Page() {
  return this.hosts;
}
Page.hosts = ['h1'];
Page.prototype.hosts = ['h2'];

const p1 = new Page();
const p2 = Page();

console.log(p1.hosts);
console.log(p2.hosts);

Результат операции: вывод первыйundefiend, а затем сообщить об ошибкеTypeError: Cannot read property 'hosts' of undefined.

Зачемconsole.log(p1.hosts)это выходundefiendЧто ж, когда мы упоминали new ранее, если мы возвращаем объект, мы будем напрямую использовать этот объект как результат new.p1должно бытьthis.hostsрезультат, покаnew Page(), этоPage.prototypeпрототипtargetОбъект, так что здесьthis.hostsможет получить доступ кPage.prototype.hostsто есть['h2']. такp1равно['h2'],['h2']нетhostsсвойство так возвращаетundefined.

Зачемconsole.log(p2.hosts)сообщит об ошибке,p2это прямой вызовPageРезультат конструктора, вызываемого напрямуюpageфункция, на этот разthisУказывает на глобальный объект, глобальный объект неhostsсобственность, поэтому верниundefined,ПрошлоеundefinedпосетитьhostsКонечно, это неправильно.

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

  1. Последнее: все, что вам нужно знать о JavaScript, стоящем за серьезным недостатком безопасности Lodash

Эта статья представляет собой оригинальный контент, впервые опубликованный наличный блог, Пожалуйста, укажите источник.