2013-06-15 3 views
3

Признавая, что JavaScript не имеет понятия класса как такового и что «тип» всех объектов является «объектом», я пытаюсь понять, что такое «объект», прототип "состоит из, и в частности, как его« имя »связано с ним. Например, в следующем:Уточняющая номенклатура и механизм прототипа javascript

function Foo(){}; 
console.log(Foo.prototype);    // => "Foo {}" 

Как console.log знать для вывода Foo перед фигурными скобками и что это имя в виду?

(Примечание: я знаю, что в вышеизложенном я имею в виду свойство прототипа функций, а не прототип как таковой (т. Е. Не то, что доступно __proto__), но тот же вопрос относится к фактическому prototype objects. Я просто использовал свойство prototype, чтобы упростить мой пример.)

Обновление: на основе темы комментариев этот вопрос действительно сосредоточен на том, что делает Chrome, и, в частности, рационализирует его поведение в следующем:

function Foo(){}; 
Foo.prototype.constructor = function Bar(){}; 
f = new Foo(); 
console.log(f);    // => Foo{} (remembering that f created by Foo, ignoring constructor) 
console.log(Foo.prototype) // => Bar{} (reporting constructor value) 

См. https://gist.github.com/getify/5793213 для более подробного обсуждения.

+0

Я предполагаю, что свойство 'prototype' функций и объект' __proto__' - это одно и то же. Вот почему вы видите результат, который видите. – millimoose

+0

В принципе, всякий раз, когда вы делаете 'new Foo()', результирующий объект '__proto__' устанавливается в' prototype' функции конструктора. Я сомневаюсь, что есть более глубокое объяснение, кроме «как работает Javascript OO». (Независимо от ссылок на спецификацию ECMAScript.) Альтернативно: всякий раз, когда вы определяете функцию, Javascript также внутренне определяет объект-прототип для объектов, созданных с использованием этой функции в качестве конструктора. – millimoose

+1

Вы прочитали суть в конце вопроса? Вопрос об официальном/унифицированном объяснении поведения Chrome остается нерешенным. Основной ответ ниже: «Не беспокойтесь об этом», «не паникуйте» и т. Д. Я не делаю ни того, ни другого. Я поддержал ваш ответ на том основании, что он дает хороший обзор прототипов, но на самом деле он не затрагивает вопрос о поведении Chrome. –

ответ

1

Свойство constructor (который относится к функции первоначально используется в качестве генератора соответствующих объектов) используется, чтобы дать имя к prototype объекта в журнале консоли. Рассмотрим следующий пример:

function Foo() { 
    this.x = 1; 
} 
console.log(Foo.prototype); // Foo {} 
Foo.prototype.constructor = function Bar() { 
    this.y = 2 
} 
console.log(Foo.prototype); // Bar {}   
var f = new Foo(); 
console.log(f.constructor); // function Bar() { this.y = 2} 

console.log(f.x);   // 1 
console.log(f.y);   // undefined 
console.log(f);    // Foo {x:1} 

Здесь мы перешли constructor на другую функцию, давая новое имя prototype объекта. Обратите внимание, что эта же функция возвращается, когда свойство constructor запрашивается непосредственно с объекта , созданного с помощью функции Foo() (по мере продвижения по цепочке наследования).

Тем не менее, это не означает, что для создания соответствующих объектов фактически использовалась другая функция (Bar()); он по-прежнему Foo(), и вы можете увидеть его как путем запроса свойств - и f напрямую. В основном, объекты помнят функцию, которая использовалась для их создания, даже если свойство constructorprototype было «перенаправлено».

+0

Спасибо, @ raina77ow. Что касается вашего последнего предложения: «В принципе, объекты помнят функцию, которая использовалась для их создания, даже если свойство' constructor' 'prototype' было« перенаправлено », мои последующие вопросы: 1) Существует ли принятое название концепции «функция, которая была использована для их создания» и 2) Где эта информация хранится для целей «запоминания»? –

0
function Foo(){}; 

Работа по цепи:

console.log(Foo.prototype); 
console.log(Foo.prototype.constructor); 
console.log(Foo.prototype.constructor.name); 
+0

Ответ raina77ow предполагает, что это неверно. Если свойство конструктора 'prototype.constructor' переопределено , объекты, созданные этим конструктором, по-прежнему отображаются с именем конструктора. Вы видите это по-другому? –

+0

Извините, я понимаю, что мой вопрос был посвящен тому, какое имя было отображено для свойства прототипа функции, и похоже, что оно отслеживает «прототип». конструктор'. Однако объекты, созданные конструктором, сохраняют имя конструктора, даже если свойство prototype.constructor конструктора переопределено, поэтому мне все еще интересно об этом. существует ли какое-то внутреннее, не доступное свойство 'constructor', которое сохраняет конструктор, используемый для создания объекта? –

17

JavaScript имеет очень скрученную форму прототипного наследования. Мне нравится называть это constructor pattern of prototypal inheritance. Существует еще одна модель прототипного наследования - the prototypal pattern of prototypal inheritance. Сначала я объясню это последним.

В объектах JavaScript наследуется от объектов. Нет необходимости в занятиях. Это хорошая вещь. Это облегчает жизнь. Например, у нас есть класс для линий:

class Line { 
    int x1, y1, x2, y2; 

    public: 

    Line(int x1, int y1, int x2, int y2) { 
     this.x1 = x1; 
     this.y1 = y1; 
     this.x2 = x2; 
     this.y2 = y2; 
    } 

    int length() { 
     int dx = x2 - x1; 
     int dy = y2 - y1; 
     return sqrt(dx * dx + dy * dy); 
    } 
} 

Да, это C++. Теперь, когда мы создали класс, мы теперь можем создавать объекты:

Line line1(0, 0, 0, 100); 
Line line2(0, 100, 100, 100); 
Line line3(100, 100, 100, 0); 
Line line4(100, 0, 0, 0); 

Эти четыре строки образуют квадрат.

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

var line = { 
    create: function (x1, y1, x2, y2) { 
     var line = Object.create(this); 
     line.x1 = x1; 
     line.y1 = y1; 
     line.x2 = x2; 
     line.y2 = y2; 
     return line; 
    }, 
    length: function() { 
     var dx = this.x2 - this.x1; 
     var dy = this.y2 - this.y1; 
     return Math.sqrt(dx * dx + dy * dy); 
    } 
}; 

Затем вы создаете экземпляры объекта line следующим образом:

var line1 = line.create(0, 0, 0, 100); 
var line2 = line.create(0, 100, 100, 100); 
var line3 = line.create(100, 100, 100, 0); 
var line4 = line.create(100, 0, 0, 0); 

Это все есть на него. Никаких запутанных конструкторских функций с объектами prototype. Единственной функцией, необходимой для наследования, является Object.create. Эта функция принимает объект (прототип) и возвращает другой объект, который наследуется от прототипа.

К сожалению, в отличие от Lua, JavaScript поддерживает шаблон конструктора прототипального наследования, что затрудняет понимание прототипного наследования. Шаблон конструктора является обратным шаблону прототипа.

  1. В прототипном шаблоне объекты имеют наибольшее значение. Следовательно, легко видеть, что объекты наследуются от других объектов.
  2. В конструкторе шаблоны функции имеют наибольшее значение. Следовательно, люди склонны думать, что конструкторы наследуются от других конструкторов. Это не верно.

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

function Line(x1, y1, x2, y2) { 
    this.x1 = x1; 
    this.y1 = y1; 
    this.x2 = x2; 
    this.y2 = y2; 
} 

Line.prototype.length = function() { 
    var dx = this.x2 - this.x1; 
    var dy = this.y2 - this.y1; 
    return Math.sqrt(dx * dx + dy * dy); 
}; 

Теперь вы можете создавать экземпляры Line.prototype следующим образом:

var line1 = new Line(0, 0, 0, 100); 
var line2 = new Line(0, 100, 100, 100); 
var line3 = new Line(100, 100, 100, 0); 
var line4 = new Line(100, 0, 0, 0); 

Обратите внимание на сходство между конструктором шаблона и прототипа?

  1. В прототипном шаблоне мы просто создаем объект, который имеет метод create. В шаблоне конструктора мы создаем функцию, и JavaScript автоматически создает для нас объект prototype.
  2. В прототипе у нас есть два метода - create и length. В шаблоне конструктора тоже есть два метода: constructor и length.

Шаблон конструктора является обратным шаблону прототипа, потому что при создании функции JavaScript автоматически создает объект prototype для функции. Объект prototype имеет свойство, называемое constructor который points back to the function itself:

Как сказал Эрик, причина console.log знает выход Foo происходит потому, что, когда вы проходите Foo.prototype к console.log:

  1. Он находит Foo.prototype.constructor, который Foo.
  2. Каждая именованная функция в JavaScript имеет свойство, называемое name.
  3. Следовательно Foo.name является "Foo".Поэтому он находит строку "Foo" на Foo.prototype.constructor.name.

Edit: Хорошо, я понимаю, что у вас есть проблемы с переопределение prototype.constructor собственности в JavaScript. Чтобы понять проблему, давайте сначала поймем, как работает оператор new.

  1. Во-первых, я хочу, чтобы вы хорошо рассмотрели диаграмму, которую я показал вам выше.
  2. На приведенной выше диаграмме у нас есть функция-конструктор, объект-прототип и экземпляр.
  3. Когда мы создаем экземпляр, используя ключевое слово new, перед конструктором JS создается новый объект.
  4. Внутренняя собственность [[proto]] этого нового объекта равна нулю constructor.prototype указывает на на момент создания объекта.

Что это подразумевает? Рассмотрим следующую программу:

function Foo() {} 
function Bar() {} 

var foo = new Foo; 

Foo.prototype = Bar.prototype; 

var bar = new Foo; 

alert(foo.constructor.name); // Foo 
alert(bar.constructor.name); // Bar 

Смотрите вывод здесь: http://jsfiddle.net/z6b8w/

  1. Экземпляр foo наследует от Foo.prototype.
  2. Следовательно foo.constructor.name отображает "Foo".
  3. Затем мы установили Foo.prototype в Bar.prototype.
  4. Следовательно bar наследует от Bar.prototype, хотя он был создан new Foo.
  5. Таким образом, bar.constructor.name является "Bar".

В JS fiddle вы условии, что вы создали функцию Foo, а затем установить Foo.prototype.constructor в function Bar() {}:

function Foo() {} 
Foo.prototype.constructor = function Bar() {}; 
var f = new Foo; 
console.log(f.hasOwnProperty("constructor")); 
console.log(f.constructor); 
console.log(f); 

Поскольку вы изменили свойство Foo.prototype каждый экземпляр Foo.prototype будет отражать эти изменения. Следовательно, f.constructor - function Bar() {}. Таким образом, f.constructor.name является "Bar", а не "Foo".

Смотрите сами - f.constructor.name это "Bar".


Хром, как известно, делает такие странные вещи. Важно понимать, что Chrome - это утилита для отладки, а console.log в основном используется для целей отладки.

Следовательно, при создании нового экземпляра Chrome, вероятно, записывает исходный конструктор во внутреннее свойство, к которому обращается console.log. Таким образом, он отображает Foo, а не Bar.

Это не фактическое поведение JavaScript. Согласно спецификации при перезаписывании свойства prototype.constructor нет никакой связи между экземпляром и исходным конструктором.

Другие реализации JavaScript (например, консоль Opera, node.js и RingoJS) делают правильные вещи и отображают Bar. Следовательно, поведение Chrome нестандартно и зависит от браузера, поэтому не паникуйте.

Что важно понимать, что хотя Chrome отображает Foo вместо Barconstructor свойство объекта еще function Bar() {} как и с другими реализациями:

+0

+1. Слишком много слов, но хороший обзор. Я расстраиваюсь, когда люди не понимают ни прототипного ООП, ни его вариаций, но я использовал «Я», поэтому я предвзятый. –

+1

@DaveNewton: Согласен. Прототипный ООП прост. Однако большинство людей путают из-за того, как прототипный ООП реализуется в JavaScript (т. Е. Шаблон конструктора). Поскольку JS является наиболее известным языком, который поддерживает прототипный ООП, это заставляет людей думать, что прототипный ООП является сложным или некачественным. JavaScript был моим языком выбора почти 8 лет, но теперь я взял блеск в Lua. Мне нравится оператор толстой кишки и концепция metatables в Lua, а Lua - прототипный OOP. Вы можете реализовать прототипное наследование любым способом, используя метод '__index'. –

+0

@AaditMShah, спасибо, что ответили.В моем ответе на @Eric ниже вы можете прокомментировать, как известно имя объекта-экземпляра в том случае, если свойство 'prototype.constructor' конструктора было переопределено, по адресу http://jsfiddle.net/ at4zQ/2/ –

0

Взял немного рытье онлайн, но я нашел эта статья, которая действительно иллюстрирует, как работают прототипы и другие ключевые функции ядра javascript:

http://dmitrysoshnikov.com/ecmascript/javascript-the-core/

Мне особенно нравится диаграмма о том, как выглядит цепочка прототипов.

Смежные вопросы