1. На проекте открылась регистрация только для продавцов. Для обычных пользователей будет открыта позже. Подробнее.
    P.S. Не надо скидывать ссылки на форумы, где у вас ноль сообщений. Подобные заявки будут отклонятся.
Скрыть объявление
Привет, Незнакомец! У тебя есть возможность Оставить комментарий в теме

JavaScript Основы и заблуждения насчет JavaScript

Тема в разделе "PR0GRAMM1NG", создана пользователем ddd, 30 май 2011.

  1. ddd (•̪̀●́)=o/̵͇̿̿/'̿̿ ̿ ̿̿

    ddd
    TS
    Команда форума WebOwner WebVoice
    Регистрация:
    5 мар 2007
    Сообщения:
    2.897
    Симпатии:
    191
    ICQ:
    943084
    Чет ваще нефига нету в разделе про джаваскрипты, вот решил пополнить статьями стыренными с хабрахабра, в будущем возможно от себя напишу о замечательной библиотеке jQuery :)

    Объекты, классы, конструкторы
    В JavaScript нет привычных классов, но есть функции-конструкторы, порождающие объекты по определенным алгоритмам (см. Оператор new).

    Прототипное делегирующее наследование
    Классическое наследование очень похоже на то, как люди наследуют гены своих предков. Есть какие-то базовые особенности: люди могут ходить, говорить… И есть характерные черты для для каждого человека. Люди не в состоянии изменить себя — свой класс (но могут поменять собственные свойства) и бабушки, дедушки, мамы и папы не могут динамически повлиять на гены детей и внуков. Все очень по земному.

    Теперь представим другую планету, на которой не такое как на Земле генное наследование. Там обитают мутанты с «телепатическим наследованием», которые способны изменять гены своих потомков.
    Разберем пример. Отец наследует гены от Дедушки, а Сын наследует гены от Отца, который наследует от Дедушки. Каждый мутант может свободно мутировать, и может менять гены своих потомков. Например у Дедушки был зеленый цвет кожи, Отец цвет унаследовал, Сын тоже унаследовал цвет. И вдруг Дед решил: «надоело мне ходить зеленым — хочу стать сними», смутировал (изменил прототип своего класса) и «телепатически» распространил эту мутацию Отцу и Сыну, вобщем посинели все. Тут Отец подумал: «Дед на старости лет совсем двинулся» и поменял свой цвет в генах обратно на зеленый(изменил прототип своего класса), и распространил «телепатически» свой цвет сыну. Отец и Сын зеленые, Дед синий. Теперь как бы дед ни старался Отец и сын цвет не поменяют, т.к сейчас Отец в своем прототипе прописал цвет, а Сын в первую очередь унаследует от Прототипа Отца. Теперь Сын решает: «Поменяю ка я свой цвет на черный, а моё потомство пусть наследует цвет от Отца» и прописал собственное свойство, которое не влияет на потомство. И так далее.

    Опишем все в коде:
    Код:
    var Grandfather = function () {}; // Конструктор Grandfather  Grandfather.prototype.color = 'green';  var Father = function () {}; // Конструктор Father  Father.prototype = new Grandfather(); // Это простой, но не самый лучший вариант протитипного наследования  var Son = function () {}; // Конструктор Son  Son.prototype = new Father(); // Аналогично  var u = new Grandfather(); // Экземпляр "класса" Grandfather var f = new Father(); // Экземпляр "класса" Father var s = new Son(); // Экземпляр "класса" Son  // Изначально все зеленые console.log([u.color, f.color, s.color]); // ["green", "green", "green"]  // Дед решил поменять свой цвет и цвет потомства Grandfather.prototype.color = 'blue'; console.log([u.color, f.color, s.color]); // ["blue", "blue", "blue"]  // Отец решил все вернуть для себя и своего потомства Father.prototype.color = 'green'; // Хотя мог исделать и так: // Grandfather.prototype.color = 'green'; console.log([u.color, f.color, s.color]); // ["blue", "green", "green"]  // Смысла нет Grandfather.prototype.color = 'blue'; console.log([u.color, f.color, s.color]); // ["blue", "green", "green"]  // Сын решил не брать пример с Деда и поменял только собственное свойство s.color = 'black'; // Меняем собственное свойство, которое не затрагивает цепочку прототипов console.log([u.color, f.color, s.color]); // ["blue", "green", "black"]  var SonsSon = function () {}; // Конструктор SonsSon SonsSon.prototype = new Son(); // Аналогично  var ss = new SonsSon(); // Экземпляр "класса" SonsSon // Сын сына унаследовал от Отца console.log([u.color, f.color, s.color, ss.color]); // ["blue", "green", "black", "green"]
    
    Почитать:
    ООП в Javascript: наследование
    Разбираемся с prototype, __proto__, constructor и их цепочками в картинках
    Цепочка прототипов, получение свойства с заданными именем
    В JavaScript каждый объект имеет собственные свойства (Own Properties) и ссылку на объект-прототип, в свою очередь прототип тоже имеет собственные свойства и ссылку на прототип, прототип прототипа тоже имеет собственные свойства и ссылку на прототип ну и так далее, пока ссылка на прототип не будет null — эта структура называется цепочка прототипов.
    При попытке обратиться к свойству объекта (через точку или скобки) выполняется поиск указателя по имени: сперва проверяется есть ли указатель с таком-то именем с списке собственных свойств (если есть, то возвращается), если его нет, то идет поиск в собственном прототипе (если есть, то возвращается), если его нет, то идет поиск в прототипе прототипа и так далее, пока прототип прототипа не станет null в этом случае возвращается undefined.

    Некоторые реализации JavaScript используют свойство __proto__ для представления следующего объекта в цепочке прототипов.

    Поиск свойства на чтение можно описать следующей функцией:
    Код:
    function getProperty(obj, prop) {
      if (obj.hasOwnProperty(prop))
        return obj[prop]
     
      else if (obj.__proto__ !== null)
        return getProperty(obj.__proto__, prop)
     
      else
        return undefined
    }
    
    Для примера рассмотри простой класс Point 2D, содержащий 2 свойства (x, y) и метод print. Используя, определения выше — постоим объект.

    Код:
    var Point = {
        x: 0,
        y: 0,
        print: function () { 
            console.log(this.x, this.y); 
        }
    };
     
    var p = {x: 10, __proto__: Point};
    
    // Свойство 'x' нашлось в свобственных свойствах:
    /* p.x */ getProperty(p, 'x'); // 10
    
    // Свойство 'y' нашлось по ссылке __proto__ в объекте-прототипе Point  
    /* p.y */ getProperty(p, 'y'); // 0
    
    // Метод print нашелся по ссылке __proto__ в объекте-прототипе Point  
    /* p.print() */ getProperty(p, 'print').call(p); // 10 0
    
    Почему я использовал call, а не вызвал полученную функции напрямую, описано ниже.


    На самом деле Point имеет ещё одно свйоство, да это наша ссылка на прототип родителя __proto__, которая в случае Point указывает на Object.prototype.
    Например, вот так будет выглядеть вся цепочка пртотипов в самом первом примере:
    Код:
                /* SonsSon <- Son <---- Father <- Grandfather <-- Object <-- null */ console.log(ss.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__ === null); 
    
    __proto__, prototype, оператор new
    Выше был «низкоуровневый код», теперь посмотрим как все работает в жизни:
    Код:
    function Point(x, y) { // Конструктор Point     this.x = x;     this.y = y; } Point.prototype = { // Прототип конструктора     print: function () { console.log(this.x, this.y); } };   var p = new Point(10, 20); // Создаем новый объект p.print(); // 10 20
    
    Если в предыдущем коде мы хотя бы знали, что куда ссылается, в этом все как-то запутанно.

    Вся «магия» кроется в операторе new. Brendan Eich (создатель JavaScript) захотел, чтобы JavaScript был похож на традиционные ОО языки, такие как C++, Java, поэтому был добавлен оператор new. Посмотрим как же он работает.

    Оператор new получает в свое распоряжение функцию и аргументы функции (new F(arguments...)) и выполняет следующие действия:

    1. Создает пустой объект с единственным свойством __proto__, которое ссылается на F.prototype
    2. Выполняет конструктор F в котором this — созданный ранее объект
    3. Возвращает объект
    Создадим функцию New, эмулирующую поведение оператора new:

    Код:
    function New (F, args) { /*1*/  var n = {'__proto__': F.prototype}; /*2*/  F.apply(n, args); /*3*/  return n; }
    Изменим предыдущий пример с Point:
    Код:
    function Point(x, y) { // Конструктор Point     this.x = x;     this.y = y; } Point.prototype = { // Прототип конструктора     print: function () { console.log(this.x, this.y); } };   var p1 = new Point(10, 20); p1.print(); // 10 20 console.log(p1 instanceof Point); // true  // Это аналогично: var p2 = New(Point, [10, 20]); p2.print(); // 10 20 console.log(p2 instanceof Point); // true
    
    Построение цепочки прототипов
    В самом первом примере я строил цепочку прототипов, используя вот такую конструкцию Father.prototype = new Grandfather():
    Код:
    var Grandfather = function () {}; // Конструктор Grandfather  Grandfather.prototype.color = 'green';  var Father = function () {}; // Конструктор Father  Father.prototype = new Grandfather(); // Это простой, но не самый лучший вариант протитипного наследования  var Son = function () {}; // Конструктор Son  Son.prototype = new Father(); // Аналогично
    
    Теперь мы знаем поведение оператора new и можем понять, что тут делается — развернем new Grandfather():
    Код:
    Father.prototype = {     __proto__: { // Прототип Grandfather         color: 'green',          __proto__: Object.prototype     } };
    
    Теперь при вызове new Father() мы получим вот такой объект (сразу развернем объект):
    Код:
    Son.prototype = {     __proto__: { // Прототип Father         __proto__: { // Прототип Grandfather             color: 'green',              __proto__: Object.prototype         }     } }
    
    Давайте посмотрим, что мы имеем в конце кода в объекте s (экземпляр Son)
    Код:
    {     color: 'black', // Сын поменял только собственное свойство      __proto__: { // Прототип Son         __proto__: { // Прототип Father             color: 'green', // Отец решил вернуть цвет             __proto__: { // Прототип Grandfather                 color: 'blue', // Дед решил поменять на синий                  __proto__: Object.prototype             }         }     } }
    
    Почему же Father.prototype = new Grandfather() не самый лучший вариант построения цепочки прототипов?
    Потому, что нам приходится вызывать конструктор Grandfather, который может подмешать лишние свойства и вызвать лишние методы, например alert. Для обхода этой проблемы используют подставной конструктор:
    Код:
    function inherit (object, parent) {     function F(){}; // Подставной конструктор     F.prototype = parent.prototype; // Подсовываем прототип реального конструктора     object.prototype = new F(); // Теперь реальный конструктор не будет выполнен     return object; // Можно и не возвращать };
    
    Пример использования:
    Код:
    var Grandfather = function () {}; // Конструктор Grandfather  Grandfather.prototype.color = 'green';  var Father = function () {}; // Конструктор Father inherit(Father, Grandfather); // Это лучше
    
    Конструктор Grandfather не будет выполнен. Если нам все-такие необходимо выполнить конструктор Grandfather, то вызываем его с помошью call или appy
    Код:
    var Father = function () { // Конструктор Father     Grandfather.call(this);     };
    
    Оператор instanceof
    Код:
    if (p instanceof Point) {     // ... }
    
    Оператор instanceof очень тесно связан с цепочной прототипов. Он использует именно цепочку прототипов для вынесения вердикта, а не проверяет порожден ли данный объект «p» конструктором «Point». В этом моменте часто бывает путаница.

    Оператор instanceof оперирует двумя объектами — obj и constructor: (obj instanceof constructor). Он начиная с constructor.prototype, пробегает по цепочке прототипов и проверяет следующее равенство obj.__proto__ === constructor.prototype, если оно истинное, то возвращает true.

    Опишем в коде:
    Код:
    function isInstanceOf(obj, constructor) {   if (obj.__proto__ === constructor.prototype)     return true;     else if (obj.__proto__ !== null)     return isInstanceOf(obj.__proto__, constructor)     else     return false }
    
    Рассмотрим пример выше:
    Код:
    function Point(x, y) { // Конструктор Point     this.x = x;     this.y = y; } Point.prototype = { // Прототип конструктора     print: function () { console.log(this.x, this.y); } };   var p = new Point(10, 20); // Создаем новый объект  /* {} instanceof Object */ console.log(isInstanceOf({}, Object)); // true /* p instanceof Point */   console.log(isInstanceOf(p, Point)); // true /* p instanceof Object */  console.log(isInstanceOf(p, Object)); // true потому, что Object есть в цепи прототипов (Point.__proto__ === Object.prototype) /* p instanceof Array */   console.log(isInstanceOf(p, Array)); // false потому, что Array нет в цепочке прототипов
    
    Свойство this
    this это одно большое заблуждение.

    В JavaScript значение this определяется вызывающей стороной по форме вызова. Правило по которому определяется то, что будет в this такое (объясню по-простому):
    1. Если метод вызывается напрямую (без new, call, apply, bind, with, try catch), то значением this будет тот объект, который стоит перед точкой, слева от имени метода.
    2. Если точки нет (функция вызывается напрямую), то this будет приравнен к undefined, null или window(global), в зависимости от среды и «use strict».
    3. Если выражение представляет из себя не ссылку, а значение, то применяется пункт 2

    Пример:
    Код:
    var foo = {     bar: function () {         console.log(this);     } };  var bar = foo.bar;  bar(); // this === global (2)  foo.bar();   // this === foo (1) (foo.bar)(); // this === foo скобки ничего не меняют (1)    // Все выражения слева от скобок вызова - значения (foo.bar = foo.bar)(); // this === global (3) (false || foo.bar)();  // this === global (3) (foo.bar, foo.bar)();  // this === global (3)  function foo() {    function bar() {        console.log(this);    }        bar(); // this === global (2) }
    
    Вспомним пример с getProperty(p, 'print').call(p) именно из-за этого правила я вручную указал значение this. Иначе функция print получила бы в качестве this — window.

    Вот эти операторы и методы способны управлять значением this: new, call, apply, bind, with, try catch (с ними более-менее все понятно, не буду затрагивать).

    Подробнее о this:
    Тонкости ECMA-262-3. Часть 3: This


    undefined, null, void
    null — примитивное значение, представляющее нулевую, пустую, не существующую ссылку
    undefined — примитивное значение, которое получает каждая перемененная по умолчанию (когда переменная не имеет значение)
    void — это оператор (т.е. при вызове его скобки не нужны), выполняющий выражение и всегда возвращающий undefined

    Заключение
    1. В JavaScript нет классов — есть конструкторы
    2. Цепь прототипов — база на которую опирается все наследование в JavaScript
    3. Свойство объекта получается с использованием цепи прототипов
    4. __proto__ — это ссылка на прототип конструктора(prototype)
    5. Оператор new создает пустой объект с единственным свойством __proto__, которое ссылается на F.prototype, выполняет конструктор F в котором this — созданный ранее объект и возвращает объект
    6. Оператор instanceof не проверяет порожден ли данный объект «Object» конструктором «ObjectsConstoructor», для своего вердикта он использует цепь прототипов
    7. В JavaScript значение this определяется вызывающей стороной по форме вызова
    8. void — это оператор, а не функция. undefined, null — примитивные значения

    Важно В некоторых реализациях JavaScript нельзя напрямую менять __proto__, к тому же это свойство не стандартное и уже устаревшее. Для получения ссылки на прототип следует использовать Object.getPrototypeOf. В статье я применял его (__proto__) для демонстрации «внутренностей» ECMAScript.