Почему в JavaScript функция считается и конструктором, и объектом? - PullRequest
5 голосов
/ 16 декабря 2008

В последнее время я проводил много исследований по этому вопросу, но до сих пор не получил действительно хороший твердый ответ. Я где-то читал, что новый объект Function () создается, когда движок JavaScript сталкивается с оператором функции, что приводит меня к мысли, что это может быть потомок объекта (таким образом становясь единым целым). Итак, я написал Дугласу Крокфорду по электронной почте, и он ответил:

Не совсем, потому что функция оператор не вызывает компилятор.

Но это дает аналогичный результат.

Кроме того, насколько мне известно, вы не можете вызывать члены конструктора функции, если он не был создан как новый объект. Так что это не сработает:

function myFunction(){
    this.myProperty = "Am I an object!";
}
myFunction.myProperty; // myFunction is not a function
myFunction().myProperty; // myFunction has no properties

Однако это будет работать:

function myFunction(){
    this.myProperty = "Am I an object!";
}
var myFunctionVar = new myFunction();
myFunctionVar.myProperty;

Является ли это просто вопросом семантики ... во всем мире программирования, когда объект действительно становится объектом и как это отображается в JavaScript?

Ответы [ 8 ]

13 голосов
/ 17 декабря 2008

В функциях и конструкторах нет ничего волшебного. Все объекты в JavaScript являются & hellip; хорошо, объекты. Но некоторые объекты более специфичны, чем другие, а именно: встроенные объекты. Разница заключается в основном в следующих аспектах:

  1. Общая обработка объектов. Примеры:
    • Числа и строки неизменны (& rArr; константы). Не определены методы для их внутреннего изменения & mdash; новые объекты всегда создаются в результате. Хотя у них есть некоторые врожденные методы, вы не можете их изменить или добавить новые методы. Любые попытки сделать это будут игнорироваться.
    • null и undefined - специальные объекты. Любая попытка использовать метод для этих объектов или определить новые методы вызывает исключение.
  2. Применимые операторы. JavaScript не позволяет (пере) определять операторы, поэтому мы остановились на том, что доступно.
    • Числа имеют особый способ с арифметическими операторами: +, -, *, /.
    • В строках есть специальный способ обработки оператора конкатенации: +.
    • Функции имеют специальный способ обработки оператора вызова: () и оператора new. Последний обладает врожденными знаниями о том, как использовать свойство prototype конструктора, создать объект с соответствующими внутренними ссылками на прототип и вызвать для него функцию конструктора, правильно установив this.

Если вы загляните в стандарт ECMAScript ( PDF ), то увидите, что все эти «дополнительные» функции определены как методы и свойства, но многие из них напрямую не доступны программистам. Некоторые из них будут представлены в новой редакции стандарта ES3.1 (проект от 15 декабря 2008 г .: PDF ). Одно свойство (__proto__) уже выставлено в Firefox .

Теперь мы можем ответить на ваш вопрос напрямую. Да, у объекта функции есть свойства, и мы можем добавлять / удалять их по желанию:

var fun = function(){/* ... */};
fun.foo = 2;
console.log(fun.foo);  // 2
fun.bar = "Ha!";
console.log(fun.bar);  // Ha!

Не имеет значения, что на самом деле делает функция & mdash; это никогда не приходит в игру, потому что мы не называем это! Теперь давайте определим это:

fun = function(){ this.life = 42; };

Сам по себе это не конструктор, это функция, которая работает в своем контексте. И мы можем легко предоставить это:

var context = {ford: "perfect"};

// now let's call our function on our context
fun.call(context);

// it didn't create new object, it modified the context:
console.log(context.ford);           // perfect
console.log(context.life);           // 42
console.log(context instanceof fun); // false

Как видите, добавлено еще одно свойство к уже существующему объекту.

Чтобы использовать нашу функцию в качестве конструктора, мы должны использовать оператор new:

var baz = new fun();

// new empty object was created, and fun() was executed on it:
console.log(baz.life);           // 42
console.log(baz instanceof fun); // true

Как видите, new сделал нашу функцию конструктором. Следующие действия были выполнены new:

  1. Создан новый пустой объект ({}).
  2. Его внутреннее свойство прототипа было установлено на fun.prototype. В нашем случае это будет пустой объект ({}), потому что мы его не модифицировали.
  3. fun() был вызван с этим новым объектом в качестве контекста.

В нашу функцию входит изменение нового объекта. Обычно он устанавливает свойства объекта, но может делать все, что ему захочется.

Веселые мелочи:

  • Поскольку конструктор - это просто объект, мы можем его вычислить:

    var A = function(val){ this.a = val; };
    var B = function(val){ this.b = val; };
    var C = function(flag){ return flag ? A : B; };
    
    // now let's create an object:
    var x = new (C(true))(42);
    
    // what kind of object is that?
    console.log(x instanceof C); // false
    console.log(x instanceof B); // false
    console.log(x instanceof A); // true
    // it is of A
    
    // let's inspect it
    console.log(x.a); // 42
    console.log(x.b); // undefined
    
    // now let's create another object:
    var y = new (C(false))(33);
    
    // what kind of object is that?
    console.log(y instanceof C); // false
    console.log(y instanceof B); // true
    console.log(y instanceof A); // false
    // it is of B
    
    // let's inspect it
    console.log(y.a); // undefined
    console.log(y.b); // 33
    
    // cool, heh?
    
  • Конструктор может вернуть значение, переопределяющее вновь созданный объект:

    var A = function(flag){
      if(flag){
        // let's return something completely different
        return {ford: "perfect"};
      }
      // let's modify the object
      this.life = 42;
    };
    
    // now let's create two objects:
    var x = new A(false);
    var y = new A(true);
    
    // let's inspect x
    console.log(x instanceof A); // true
    console.log(x.ford);         // undefined
    console.log(x.life);         // 42
    
    // let's inspect y
    console.log(y instanceof A); // false
    console.log(y.ford);         // perfect
    console.log(y.life);         // undefined
    

    Как видите, x имеет A с прототипом и всем, тогда как y - это наш "голый" объект, который мы вернули из конструктора.

12 голосов
/ 16 декабря 2008

Ваше понимание неверно:

myFunction().myProperty; // myFunction has no properties

Причина, по которой это не работает, заключается в том, что «.myProperty» применяется к возвращаемому значению «myFunction ()», а не к объекту «myFunction». Для остроумия:

$ js
js> function a() { this.b=1;return {b: 2};}
js> a().b
2
js> 

Помните, что "()" является оператором. «myFunction» - это не то же самое, что «myFunction ()». Вам не нужно «возвращаться», когда instanciang с новым:

js> function a() { this.b=1;}
js> d = new a();
[object Object]
js> d.b;
1
5 голосов
/ 16 декабря 2008

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

Например, вы всегда можете сделать это:

function foo(){
  return 0;
}
foo.bar = 1;
alert(foo.bar); // shows "1"

Функции Javascript ведут себя примерно как классы в других языках ООП, когда они используют указатель this. Они могут быть созданы как объекты с новым ключевым словом:

function Foo(){
  this.bar = 1;
}
var foo = new Foo();
alert(foo.bar); // shows "1"

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

если вы собираетесь заниматься каким-либо значительным программированием на Javascript, я настоятельно рекомендую Javascript: The Good Parts Крокфорда, того парня, которого вы отправили по электронной почте.

4 голосов
/ 16 декабря 2008

Действительно, функции являются «первоклассными гражданами»: они являются Объектом.

Каждый объект имеет прототип, но непосредственно на прототип функции можно ссылаться. Когда new вызывается с функциональным объектом в качестве аргумента, новый объект создается с использованием прототипа функционального объекта в качестве прототипа, и this устанавливается для нового объекта перед вводом функции.

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

Есть очень хорошие учебники по конструкторам, прототипам и т. Д. ... Лично я многому научился у Объектно-ориентированного программирования на JavaScript . Он показывает эквивалентность функции, которая «наследует» свой прототип, но использует this для заполнения свойств нового объекта, и объект функции, который использует конкретный прототип:

function newA() { this.prop1 = "one"; } // constructs a function object called newA
function newA_Too() {} // constructs a function object called newA_Too
newA_Too.prototype.prop1 = "one";

var A1 = new newA();
var A2 = new newA_Too();
// here A1 equals A2.
4 голосов
/ 16 декабря 2008

«Глобальная» область действия Javascript (по крайней мере, в браузере) - это объект window.

Это означает, что когда вы делаете this.myProperty = "foo" и вызываете функцию как обычный myFunction(), вы фактически устанавливаете window.myProperty = "foo"

Второй момент с myFunction().myProperty заключается в том, что здесь вы смотрите на возвращаемое значение из myFunction(), поэтому, естественно, у него не будет никаких свойств, поскольку он возвращает ноль.

То, о чем вы думаете, это:

function myFunction()
{
    myFunction.myProperty = "foo";
}

myFunction();
alert(myFunction.myProperty); // Alerts foo as expected

Это (почти) то же самое, что и

var myFunction = new Function('myFunction.myProperty = "foo";');
myFunction();

Когда вы используете его в контексте new, «возвращаемое значение» - это ваш новый объект, а указатель «this» меняется на ваш новый объект, так что это работает так, как вы ожидаете.

1 голос
/ 16 декабря 2008

Во-первых, JavaScript не ведет себя так же по отношению к объектам, как C ++ / Java, поэтому вам нужно выбросить подобные идеи из окна, чтобы понять, как работает JavaScript.

Когда эта строка выполняется:

var myFunctionVar = new myFunction();

тогда this внутри myFunction() относится к этому новому объекту, который вы создаете - myFunctionVar. Таким образом, эта строка кода:

 this.myProperty = "Am I an object!";

по существу имеет результат

 myFunctionVar.myProperty = "Am I an object!";

Это может помочь вам взглянуть на некоторую документацию по оператору new. В JS оператор new, по сути, позволяет вам создать объект из функции - любой простой старой функции. Нет ничего особенного в функции, которую вы используете с оператором new, который помечает ее как конструктор, как это было бы в C ++ или Java. Как сказано в документации:

Создание пользовательского типа объекта требует двух шагов:

  1. Определите тип объекта, написав функцию.
  2. Создать экземпляр объекта с новым.

Итак, что вы сделали с кодом

function myFunction(){
    this.myProperty = "Am I an object!";
}

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

0 голосов
/ 16 декабря 2008

Только когда вы создаете экземпляр с новым ключевым словом, функция действует как конструктор.

Результатом является объект, который может использовать ключевое слово "this" для доступа к свойствам элемента. Ключевое слово this в методе не имеет никакого смысла, когда функция используется другим способом.

0 голосов
/ 16 декабря 2008

JavaScript основан на скрипте ECMA. Его спецификация использует модель прототипа для того, чтобы она была ООП. Однако скрипт ECMA не обеспечивает строгие типы данных. Объект должен быть создан по той же причине, по которой сценарий ECMA требует «новый» вызов, который выделит память для свойства. В противном случае он останется функцией, и вы можете вызвать ее, если хотите, в этом случае свойство будет инициализировано и затем будет уничтожен, когда функция завершится.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...