Как стать автором
Обновить

Комментарии 85

НЛО прилетело и опубликовало эту надпись здесь
Надо же, кто-то ещё не спит :)

Рад, что кому-то статья пригодится. А алерт — это для наглядности. Большинство примеров рассчитаны на то, что их даже не придётся вбивать в браузер — их поведение и так понятно. Да и для начинающих, имхо, легче понять с алертом, чем с логом в консоли.
НЛО прилетело и опубликовало эту надпись здесь
Особенно удобно дебажить console.log'ом в IE6-7.
НЛО прилетело и опубликовало эту надпись здесь
Так ведь сарказм, нет? ))
А алерт — это для наглядности.

Извините, но у меня в JavaScript алерта нет — интерпретатор ругается:

$ node
> alert('Hi')
ReferenceError: alert is not defined
    at repl:1:2
    at REPLServer.self.eval (repl.js:110:21)
    at Interface.<anonymous> (repl.js:239:12)
    at Interface.EventEmitter.emit (events.js:95:17)
    at Interface._onLine (readline.js:202:10)
    at Interface._line (readline.js:531:8)
    at Interface._ttyWrite (readline.js:754:14)
    at ReadStream.onkeypress (readline.js:99:10)
    at ReadStream.EventEmitter.emit (events.js:98:17)
    at emitKey (readline.js:1089:12)
> 
Из-за слабой типизации, почти все данные в JavaScript-е являются объектами.
Вот это сурово!
Например, в Haskell нет объектов (в ООП-понимании этого слова), у него какая типизация?
В Java почти все, за исключением int, byte и boolean является объектом (могу быть не точен, поправьте) — потомком, если не ошибаюсь, object — какая в ней типизация?
А вообще статья неплохая, полезная.
В данном случае я имею в виду, что в отличие от других языков программировния, в JavaScript объектами являются не только сами объекты, но и строки, массивы и функции (насколько я знаком с другими языками — в них не так). Само понятие типизации — определение типа данных, коих может быть довольно много.

JavaScript же со своей стороны, может осуществлять приведение типов для сравнения или использования, что делает его слаботипизированным. В выражении var t = 5; переменная t в зависимости от способа использования может быть трактована как булев тип, как строка, как число, а при необходимости, выступить и как объект. Вот это я имел в виду, говоря о слабой типизации. Если не прав, поправьте.
В js 5 типов примитивов, объектами они не являются. Null и undefined к объектам не приводятся. При обращении к свойствам строк, чисел и булевых, они оборачивается во временный объект, который, после операций над ним, удаляется. Не вводите людей в заблуждение, а то потом путают, что передается по ссылке, а что по значению.
а какой пятый?
Number, string, boolean, null, undefined.
Ах да, string. Передается по ссылке, но является immutable, и посему причисляется к примитивам. Спасибо!
Можете подробнее на примере пояснить про «оборачивание в объект»? Или дайте, пожалуйста, ссылку.
// строка-примитив  
var str1='string';
console.log(typeof str1); // string
// строка-объект
var str2=new String('string');
console.log(typeof str2); // object
// создаем временный объект, аналогичный str2, его свойству q присваиваем 1
str1.q=1;
// тут продолжаем работать с примитивом - у него нет этого свойства
console.log(str1.q); // undefined
// str2 - объект, соответственно никакая обертка не создается и работаем с его свойствами
str2.q=1;
console.log(str2.q); // 1

String.prototype.met=function(){console.log(typeof(this))}
console.log(typeof 'string'); // string
// внутри методов контекстом будет этот временный объект
'string'.met(); // object
Спасибо
Не могу так сходу вспомнить ни одного ООП-языка, в котором функции, массивы и строки не являлись бы объектами (ну, кроме PHP (и отчасти Ruby — методы не являются там объектами, только блоки)). То, что некоторые сущности автоматически боксятся в объекты говорит не о слабой типизации, а о… не знаю, автоматическом боксинге/анбоксинге?)

Моя мысль была в том, что степень того, насколько язык пронизан идеями ООП (являются ли все встроенные типы объектами, например) и степень «силы» его типизации не связаны никак. Да, JS слабо-типизированный язык; и да, в нем функции являются объектами.
В контекста вашей статьи вторая часть высказывания — важная и полезная мысль, но слабая типизация тут не при чем.
В Ruby методы являются объектами. Просто obj.f — вызов метода, а obj.method(:f) — объект.
А, вот как. Спасибо.
В C++ функции — это не объекты. По факту, там даже методы (в том числе виртуальные) — обычные функции, но со скрытым параметром this.
Не силен в этом семействе, к своему стыду… но мне казалось, что в C или С++ можно передать ссылку на функцию, нет?
В C и C++ можно было взять указатель на функцию, это не делает функцию объектом, с одинаковым успехом можно написать:

int f() {
    return 123;
}

/* будет работать */

int (*ptr)() = f;
(*ptr)();

/* допустимо, но никто не гарантирует, что это будет работать на конкретной машине */

int (*ptr)() = (int (*)()) 0xfe017c24h;
(*ptr)();


В C++11 появились лямбды.

bool c = true;
std::function<bool(int, int)> f = [&] (int a, int b) -> bool {
    return c ? a < b : a > b;
}

/* ... */

c = true;
f(5, 3); // false
c = false;
f(5, 3); // true


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

struct __my_functor{
    bool& c;
    __my_functor(bool &c): c(c) {}
    bool operator() (int a, int b) { return c ? a < b : a > b; }
};

/* ... */

bool c = true;
__my_functor f(c);

/* ... */
c = true;
f(5, 3); // false
c = false;
f(5, 3); // true


То есть в C++, благодаря перегрузке операторов, объект может прикинуться функцией, но функция не является объектом.
Спасибо, узнал новое.

Я к тому, что в java нельзя сделать даже близкого, максимум, что можно сделать — создавать анонимный инстанс анонимного класса.
На самом деле, после C++ во многих языках не хватает переопределения операторов — может пригодиться как в простых случаях вроде операций с матрицами, так и вот в подобной магии с функторами (одна из вещей, что раздражают меня в java — нельзя создать простой callback, приходится городить анонимные классы с именованными методами).
Кстати, с массивами в C++ не все так просто. С одной стороны, это просто последовательно расположенные в памяти данные, как это было в C, с другой, если точно известен размер массива в данном контексте, то доступны итераторы по этому массиву (по крайней мере в C++11).

void f(int [] array, size_t size);
/* ... */
int array[5];

/* тут доступны begin() и end() */
for (int a : array) {
    std::cout << array << std::endl;
}

f(array, 5);

/* ... */
void f(int [] array, size_t size) {
/* а тут уже нет */
    for (int i = 0; i < size; ++i) {
        std::cout << array[i] << std::endl;
    }
}
Вру. в C++11 появился шаблонный класс array, который не хранит размер массива, но использует значение аргумента шаблона.
Код с явно записанным функтором функтором эквивалентен
auto f = [&] (int a, int b) -> bool { return c ? a < b : a > b;
, а не
std::function<bool(int, int)> f = [&] (int a, int b) -> bool
Если появляется std::function, возникает дополнительный полиморфизм, который стоит производительности (лишний malloc+косвенность для захвата замыкания вместо простой структуры на стеке с auto/вручную записанным функтором)
Функтор это функциональный объект или же прямой аналог функции в js. С ними тоже можно что угодно делать. даже складывать, если оператор опишешь.
У функтора несколько разных пониманий. Функторы в C++, Prolog и Haskell — совершенно разные вещи.

Отличие функциональных объектов в JS от C++ в том, что в C++ объект может вести себя как функция, если определен operator(), а в JS любая определенная пользователем функция является объектом.
Требования производительности и совместимости с Си, функциональные объекты не так то просто заинлайнить.
А чем обьект, прикинувшийся функцией, отличается от функции в понимании js? Правильнее называть это дело не функторами, а функциональными объектами.
Да, про C++ я не подумал. Исключительный случай — низкоуровневый ООП-язык. Конечно, объектами в нем являются только инстансы классов.
Строки, массивы и т.д. являются объектами далеко не только в JS, как верно заметили выше.
может осуществлять приведение типов для сравнения или использования

как мне кажется немного не точная формулировка: JS делает слаботипизированным то, что он может производить приведение типов неявно. например мы можем сделать так:
var a = 5;
if (a) {
// do something
}

тут при проверке условия происходит неявное приведение переменной a к булеву (если a == 0 или undefined или null то вернётся false). В строготипизированных языках такой фокус не прокатит: оператору if должно бередаваться только булево значание

Вот ещё пример слабой типизации:
var a = 5;
a = 'String'

мы не задаём явного типа переменной a, и как следствие можем в любой момент присвоить ей значение люого типа.

а ваше утверждение, что если всё являтся объектами, то язык слаботипизирован, является в корне неверным
Мне не нравится, что мы тут все трактуем сильную и слабую типизацию (ключевое слово «трактуем»), потому что есть вполне определенные термины.

Но последняя фраза вашего комента — это тютелька в тютельку то, что я хотел выразить изначально:)
Неявное приведение типов, кстати говоря, есть и в Haskell — можно сложить вещественное и целое, например (с точки зрения Haskell это разные типы). Но это строго типизированный язык, просто операция (функция, вообще говоря) сложения определена и для пары R+N и для N+R и т.д. Тем не менее, не получится просто так передать в функцию, которая принимает строку, целое число (нужно определить функцию с таким же именем, но с другими аргумантами).

В данном контексте слабая типизация JS — это возможность в рантайме поменять тип значения переменной.
я не являюсь знатоком большого числа языков программирования, к сожалению, и про такой нюанс в Haskel`е не знал(хотя чего реха таить, я с ним и не знаком. ну разве что название знаю) Если честно, вообще как-то не задумывался что в строготипизированном языке могут быть неявные приведения, так что спасибо, что развеяли моё заблуждение
Я тоже не особо знаток, просто полистал «Learn you a Haskell for a great good». Интересно же, на чем люди пишут и почему:)
Стоп, в Haskel же нет перегрузки функций! Потому и значения разных типов складывать нельзя, никак.

Возможно, вас обмануло наличие полиморфных констант в Haskell — число «5» может быть, в зависимости от контекста, целым, вещественным, дробным или даже комплексным. Но стоит зафиксировать его тип — и сложение не сработает.

2 + 2.0  -- Сложение двух вещественных чисел, результат = 4.0
(2 :: Integer) + 2.0 -- Ошибка компиляции, функция (+) требует одинакового типа операндов

let a = 2 -- Именованная константа a имеет тип Integer
a + 2.0 -- И снова ошибка компиляции

let b :: Num x => x; b = 2 -- Именованная константа b является полиморфной
b + 2.0 -- Работает, результат = 4.0

let c = 2 in c + 2.0 -- Механизм вывода типа справился, с имеет тип Double, результат 4.0
Стоп, в Haskel же нет перегрузки функций! Потому и значения разных типов складывать нельзя, никак.

Можно, никто не запрещает спрятать прелюдию и определить свой плюс на multiparam тайпклассе.
Но это будет уже другой плюс, тут же обсуждалась встроенная функция.

PS вы меня покритиковали только затем, чтобы написать ниже ровным счетом то же самое?
а дальше вывод типов поработает и приведет все к одному типу, на котором определен + (который всегда принимает 2 одинаковых типа, он не может быть определен для разных типов никак)
Плюс не является встроенным, это часть стандартной библиотеки, точнее — тайпкласса Num в модуле Prelude. Язык не запрещает не подключать Prelude к модулю.
Встроенными примитивами являются функции, которые используются в инстансах Num для Int, Integer и прочих (ctrl-f instance Num Int в www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/src/GHC-Num.html)

Я не критиковал, а уточнил. У себя не стал это писать, чтобы не дублировать коммент.
Честно считал, что перегрузка функций там есть. С чем-то перепутал.
Спасибо.
Не определено там это.
В хаскеле литерал «5» является синтаксическим сахаром для «fromInteger 5», а «5.0» — «fromRational 5.0». Отсюда они имеют тип:
fromInteger 5 :: (Num a) => a
fromRational 5.0 :: (Fractional a) => a

То есть «5 + 5.0» будет на самом деле «fromInteger 5 + fromRational 5.0», а дальше вывод типов поработает и приведет все к одному типу, на котором определен + (который всегда принимает 2 одинаковых типа, он не может быть определен для разных типов никак)
Ясно, спасибо.
а дальше вывод типов поработает и приведет все к одному типу, на котором определен + (который всегда принимает 2 одинаковых типа, он не может быть определен для разных типов никак)
Почему никак? Никто не запрещает спрятать прелюдию и определить свой плюс на multiparam тайпклассе.
Пожалуй, оставлю это здесь.
Да, сей прекрасный, хоть и донельзя избитый ролик отлично иллюстрирует.
Классный ролик, спасибо за ссылку.
Я долго осознавал, что такое замыкание в JS. Мне долго пытались это вдолбить разные статьи, книги, советы. Пока один хороший человек не ткнул меня в такое понятие как Scope. И только осознав, что такое scope, до меня дошло, что же такое замыкание. Почему здесь про это нет ни слова?
Цель данной статьи не показать внутренние механизмы работы, а попытаться «на пальцах» объяснить как это работает. Тем более, что про Scope отлично описывается в статьях, чьи ссылки находится внизу топика; если быть точнее — в статье по первой же ссылке.
Аналогично. Когда я узнал, что scope — это всего-навсего объект, а переменные «внутри» него — это его свойства, оказалось, что в прототипах и в скопах используется по сути один и тот же механизм, делегация.

object → [[proto]] → [[proto]] → [[proto]] → null
[[scope]] → [[scope]] → [[scope]] → window → null

А «магическая» конструкция with() всего-то навсего добавляет в эту цепь произвольный объект. И тогда js стал для меня из непредсказуемой штуки красивым и изящным языком.
Ну, у прототипов и вложенных областей видимости есть принципиальное различие — операция присваивания значения свойству никогда не изменит значение свойства, найденного в прототипе. В то же время операция присваивания значения переменной всегда меняет ту переменную, где она была найдена.

И вторая неточность: вместо window лучше бы написать [[global]], поскольку глобальный объект не обязан быть окном. Особенно заметен данный факт, если делать расширения для браузеров.
Подразумевалось, что при разрешении имен идентификаторов в обоих случаях используется делегация (или проще — обход цепочек [[scope]] или [[proto]]). А про различие правильно подмечено.
Уточнения хорошие. Да, согласен, разница между scope chain и proto chain есть. Но не такая уж она прям и принципиальная.

А насчет [[global]] — незачем новичков пугать лишний раз. Я надеюсь, что разработчики расширений для браузеров или скриптов на node уже знают js достаточно хорошо, и им мой коммент вообще без надобности.

Главное, имхо, уточнениями не запутать все окончательно, а то ещё выйдет, что и «операция присваивания значения свойству никогда не изменит значение свойства, найденного в прототипе» — не совем-то и правда.
Скрытый текст
var o = Object.create(function(){
	var stashedProtoval;
	return Object.create({}, {protoval:{
		get: function(){ return stashedProtoval; },
		set: function(v){ stashedProtoval = v; },
		configurable: true,
		enumerable: true
	}});
}());

o.protoval; // undefined
o.protoval = 42;
o.protoval; // 42
o.hasOwnProperty('protoval'); // false
Я так же считаю, что только благодаря пониманию Scope можно понять замыкания. По сути, замыкания и есть сама функция и это свойство Scope. Очень прекрасно все это описано на сайте Дмитрия Сошникова. Добавьте эту ссылку в топик.
А статья, в целом, хорошая.
почти все данные в JavaScript-е являются объектами, или могут быть использованы как объекты
лучше было бы так: «почти все данные в JavaScript-е являются голыми бабами, или могут быть использованы как голые бабы.» Эх, не было фантазии у разработчиков ECMA 262((
Это скорее претензия ко мне, как к автору, в том, что не могу правильно сформулировать мысль — пытался сформулировать по разному много раз; то, что получилось, и есть результат.

А про голых баб занятно. Только, думаю, что эту статью будут читать не только парни — думаю, они бы проголосовали не за «баб», а за «мужиков». И чтобы не выбирать между двух зол, пусть останутся объекты.
ну давай тогда будут объекты-мужики(объекты, массивы) и объекты-бабы(функции). А голые они потому что у них все свойства паблик!
Тому, кто считает, что почти все данные в джаваскриптах могут быть использованы как голые бабы, наверняка необходима помощь квалифицированного сексопатолога — да поскорее, пока не случилось непоправимое.
В данном примере переменная title была объявлена дважды – первый раз глобально, а второй раз – внутри функции. Благодара тому, что внутри функции example переменная title была объявлена с помощью ключевого слова var, она становится локальной и никак не связана с переменной title, объявленной до функции. В результате выполнения кода вначале выведется «internal» (внутренняя переменная), а затем «external» (глобальная переменная).


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

Пример:
var o = { x: 1 };

function addY(obj) {
    obj.y = 2;
}

function addZ(obj) {
   //повторно объявим obj
    var obj;
    obj.z = 3;
}

addY(o);
console.log(o); // => { x: 1, y: 2 }

addZ(o);
console.log(o); // => { x: 1, y: 2, z: 3 }

Полезное уточнение. Добавил как скрытый текст.
Хорошая статья для начинающих, но печалит, что ничего не сказано про Strict Mode и let.
Например:
> Второе — динамическое объявление функций, использующих замыкания.
можно переписать так (работает в FF и Chrome):

void function() {"use strict";

for(let counter=1; counter <=10; counter++){
    let _counter = counter;
    $('<div>').css({
        //...
    }).html('<h1>'+ counter +'</h1>')
    .appendTo('body')
    .click(function(){
        alert(_counter);
    });
}

}.call(null)


строку let _counter = counter; можно было бы опустить, если бы браузеры релизовали let по спецификации
в моем хроме let не работает, да и это только будущее языка.
Во первых, let можно использовать уже сейчас
Во вторых, javascript это уже давно не только язык интернет страниц. Если вы пишете код под Node.js или FireFox OS, то можете использовать конструкцию let без препроцессоров и это существенно упростит/ускорит ваш код.
И вы уверены, что это должны знать те, кто еще не знает что такое замыкание или прототип?) А по поводу this в strict mode — соглашусь.
У меня товарищ-робототехник начал изучение javascript именно с Node.js, для написания сервера управления Arduino.

И моё личное мнение: изучать сейчас javascript без (хотя бы поверхностного) ознакомления с Strict Mode и let, такая же ошибка, как начать изучать javascript с jQuery.
Вы и ваш товарищ молодцы, и что дальше?:) И я javascriptECMAScript учил на ноде и по спецификации. И в текущей принятой версии спецификации ни слова о let и const. Да, это нужно знать, это нужно учить на будущее — но уже после того, как выучил js на базовом уровне — уже явно после того, как разобрался с тем, что написано в статье. А вот strict mode в текущей версии спецификации присутствует, но к теме статьи относится только одно — при вызове функции не в контексте объекта, контекст не переопределяется на глобальный объект.
Вы абсолютно правильно поняли — статья ориентирована на начинающих. Поэтому я старался не выходить за пределы тем, упомянутых в начале топика. Да и это описание занимет более 10 страниц ворда, что довольно много для одной статьи.
Мне кажется, что правильное решение будет таким:
for(var counter=1; counter <=10; counter ++){
    $('<div>').css({
        "border": "solid 1px blue",
        "height": "50px",
        "margin":  "10px",
        "text-align": "center",
        "width": "100px"
    }).html('<h1>'+ counter +'</h1>')
    .appendTo('body')
    .click(window.alert.bind(window, counter));
}
для того, чтобы просто вывести номер — пойдёт, т.к. суть та же — замыкание номера. Однако цель примера — не вывести алерт, а показать, что внутри функции можно получить текущий номер с помощью замыкания.

Ещё следует быть осторожным с bind-ом, т.к. он не не совсем кроссбраузерный.
Проблема кроссбраузерности bind'а (да и других функций из ES5) решается очень просто
Самая печаль, это то, что обычное замыкание работает намного быстрее чем .bind()
Минусую топик только за
При попытке присвоить переменной значение, переменная будет создана в глобальной области видимости, и ей присвоится значение.


В конце статьи есть ссылки на статьи Дмитрия Сошникова. Жаль что вы не читали (читали невнимательно) вот эту. В ней наглядно объясняется разница между глобальной переменной и свойством глобального объекта. Эта разница на первый взгляд совершенно несущественна, но на самом деле весьма значительна.
Спасибо за объяснение причины минусования топика.
Не за что. Вы бы статейку прочитали хотя бы в части «объявления переменных без var» и поправили свою, что бы людей в заблуждение не вводить.
Прочитал до того, как ответить Вам на комментарий. Ваше замечание полезно, но выходит за рамки уровня целевой аудитории, для которого написана эта статья. К сожалению, многих вещей пришлось коснуться лишь поверхностно и не в подробностях, чтобы не слишком усложнять статью. Приводя ваше же высказывание:

Я думаю этот вопрос все же лучше рассмотреть отдельно, потому что если рассматривать их в одном топике, топик будет слишком сложным.
Тогда хотя бы исправьте фразу, которая может ввести читателей в заблуждение.
Добавил ниже спойлер с сылками, где объясняется разница в объявлении глобальной переменной от присвоения значения несуществующей. Саму фразу исправлять не стал, т.к. имхо, разница не столь уж значительная.
Мне кажется, статьи, в которых рассказывается про прототипирование, контекст и замыкания в JS, появляются на хабре примерно раз в два месяца. И там обязательно одни и те же комментарии. Например, подобные моему. Сансара.
Я не понял. Вы пишите:

Если убрать ключевое слово var из строки var title = 'internal', то запустив код, в результате дважды получим сообщение «internal». Это происходит из-за того, что при вызове функции мы объявляли не локальную переменную title, а перезаписывали значение глобальной переменной!


Потом далее приводите пример:

var title = «external title»;
function example(){
title = «changing external title»;
alert(title);

example();

Если закомментировать строку title = «changing external title»;, то при первым сгенерированным сообщением станет «undefined» — локальная переменная title уже была инициализирована (существует), но значение (на момент вызова alert-а) не было присвоено.


Вопрос. Почему в этом примере переменная title толи не вида внутри функции, то ли еще не проинициализирована, хотя это делается в первой же строке программы?
В этом фрагменте автор имел ввиду что функция example принимает аргумент title (то есть функция должна быть объявлена как function example(title)). Это становится очевидно, если посмотреть предыдущий фрагмент кода. Если аргументы вернуть то все становится на свои места — функция была вызвана без аргументов, поэтому и

первым сгенерированным сообщением станет «undefined»
Во втором примере объясняется момент с инициализацией переменных внутри функции. Надеюсь, Вы уже уловили разницу между локальными и глобальными переменными.

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

Существует разница между «переменная не существует» и «переменная с пустым значением (undefined)»
Возможно более понятным будет показать на примере:

function example(){
	alert(title);
	var title = "internal title";
}


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

function example(){
	var title;
	alert(title);
	title = "internal title";
}


отсюда и сообщение «undefined» — переменная есть, а вот значения — нет.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации