String
Объект String используется, чтобы представить и конструировать последовательность символов.
Синтаксис
Строковые литералы могут быть следующих форм:
'строка текста' "строка текста" "中文 español English हिन्दी العربية português বাংলা русский 日本語 ਪੰਜਾਬੀ 한국어 தமிழ்"
Кроме регулярных печатных символов можно использовать специальные символы, которые можно закодировать, используя нотацию escape-последовательностей:
Код | Вывод |
---|---|
\0 | нулевой символ (символ NUL) |
\’ | одинарная кавычка |
\» | двойная кавычка |
\\ | обратный слеш |
\n | новая строка |
\r | возврат каретки |
\v | вертикальная табуляция |
\t | табуляция |
\b | забой |
\f | подача страницы |
\uXXXX | Юникод-символ |
\xXX | символ в кодировке Latin-1 |
Либо можно использовать глобальный объект String напрямую:
String(thing) new String(thing)
Параметры
Всё, что может быть преобразовано в строку.
Описание
Строки полезны для хранения данных, которые можно представить в текстовой форме. Некоторые из наиболее частых операций со строками — это проверка их длины, построение строки с помощью операций строковой конкатенации + и +=, проверка на существование или местоположение подстрок с помощью метода indexOf() , либо извлечение подстрок с помощью метода substring() .
Доступ к символам
Существует два способа добраться до конкретного символа в строке. В первом способе используется метод charAt() :
return "кот".charAt(1); // вернёт "о"
Другим способом (введённым в ECMAScript 5) является рассмотрение строки как массивоподобного объекта, в котором символы имеют соответствующие числовые индексы:
return "кот"[1]; // вернёт "о"
При доступе к символам посредством нотации с квадратными скобками, попытка удалить символ, или присвоить значение числовому свойству закончится неудачей, поскольку эти свойства являются незаписываемыми и ненастраиваемыми. Смотрите документацию по методу Object.defineProperty() для дополнительной информации.
Сравнение строк
Разработчики на C имеют для сравнения строк функцию strcmp() . В JavaScript вы просто используете операторы меньше и больше:
var a = "a"; var b = "b"; if (a b) // true print(a + " меньше чем " + b); > else if (a > b) print(a + " больше чем " + b); > else print(a + " и " + b + " равны."); >
Подобный результат также может быть достигнут путём использования метода localeCompare() , имеющегося у всех экземпляров String .
Разница между строковыми примитивами и объектами String
Обратите внимание, что JavaScript различает объекты String и значения строкового примитива (то же самое верно и для объектов Boolean и Number ).
Строковые литералы (обозначаемые двойными или одинарными кавычками) и строки, возвращённые вызовом String в неконструкторном контексте (то есть, без использования ключевого слова new ) являются строковыми примитивами. JavaScript автоматически преобразует примитивы в объекты String , так что на строковых примитивах возможно использовать методы объекта String . В контекстах, когда на примитивной строке вызывается метод или происходит поиск свойства, JavaScript автоматически оборачивает строковый примитив объектом и вызывает на нём метод или ищет в нём свойство.
var s_prim = "foo"; var s_obj = new String(s_prim); console.log(typeof s_prim); // выведет 'string' console.log(typeof s_obj); // выведет 'object'
Строковые примитивы и объекты String также дают разные результаты при использовании глобальной функции eval() . Примитивы, передаваемые в eval() , трактуются как исходный код; объекты же String трактуются так же, как и все остальные объекты, а именно: возвращается сам объект. Например:
var s1 = "2 + 2"; // создаёт строковый примитив var s2 = new String("2 + 2"); // создаёт объект String console.log(eval(s1)); // выведет число 4 console.log(eval(s2)); // выведет строку '2 + 2'
По этим причинам код может сломаться, если он получает объекты String , а ожидает строковые примитивы, хотя в общем случае вам не нужно беспокоиться о различиях между ними.
Объект String также всегда может быть преобразован в его примитивный аналог при помощи метода valueOf() .
.log(eval(s2.valueOf())); // выведет число 4
Свойства
Хранит длину строки. Только для чтения.
Методы
Возвращает строку, созданную из указанной последовательности значений Юникода.
Возвращает строку, созданную из указанной последовательности кодовых точек Юникода.
Возвращает строку, созданную из сырой шаблонной строки.
Общие методы объекта String
Методы экземпляров String также доступны в Firefox как часть JavaScript 1.6 (который не является частью стандарта ECMAScript) на объекте String , что позволяет применять эти методы к любому объекту:
var num = 15; console.log(String.replace(num, /5/, "2"));
Общие методы также доступны для объекта Array .
Следующая прослойка позволяет использовать их во всех браузерах:
/*globals define*/ // Предполагаем, что все требуемые методы экземпляров String уже присутствуют // (для них так же можно использовать полифилы, если их нет) (function () "use strict"; var i, // Мы могли построить массив методов следующим образом, однако метод // getOwnPropertyNames() нельзя реализовать на JavaScript: // Object.getOwnPropertyNames(String).filter(function(methodName) // return typeof String[methodName] === 'function'; // >); methods = [ "quote", "substring", "toLowerCase", "toUpperCase", "charAt", "charCodeAt", "indexOf", "lastIndexOf", "startsWith", "endsWith", "trim", "trimLeft", "trimRight", "toLocaleLowerCase", "toLocaleUpperCase", "localeCompare", "match", "search", "replace", "split", "substr", "concat", "slice", ], methodCount = methods.length, assignStringGeneric = function (methodName) var method = String.prototype[methodName]; String[methodName] = function (arg1) return method.apply(arg1, Array.prototype.slice.call(arguments, 1)); >; >; for (i = 0; i methodCount; i++) assignStringGeneric(methods[i]); > >)();
Примеры
Пример: преобразование в строку
Объект String можно использовать как «безопасную» альтернативу методу toString() , так как хотя он обычно и вызывает соответствующий метод toString() , он также работает и для значений null и undefined . Например:
var outputStrings = []; for (var i = 0, n = inputValues.length; i n; ++i) outputStrings.push(String(inputValues[i])); >
Спецификации
Specification |
---|
ECMAScript Language Specification # sec-string-objects |
Совместимость с браузерами
BCD tables only load in the browser
Смотрите также
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 7 авг. 2023 г. by MDN contributors.
Your blueprint for a better internet.
MDN
Support
- Product help
- Report an issue
Our communities
Developers
- Web Technologies
- Learn Web Development
- MDN Plus
- Hacks Blog
- Website Privacy Notice
- Cookies
- Legal
- Community Participation Guidelines
Visit Mozilla Corporation’s not-for-profit parent, the Mozilla Foundation.
Portions of this content are ©1998– 2023 by individual mozilla.org contributors. Content available under a Creative Commons license.
Конструктор, оператор «new»
Обычный синтаксис <. >позволяет создать только один объект. Но зачастую нам нужно создать множество похожих, однотипных объектов, таких как пользователи, элементы меню и так далее.
Это можно сделать при помощи функции-конструктора и оператора «new» .
Функция-конструктор
Функции-конструкторы технически являются обычными функциями. Но есть два соглашения:
- Имя функции-конструктора должно начинаться с большой буквы.
- Функция-конструктор должна выполняться только с помощью оператора «new» .
function User(name) < this.name = name; this.isAdmin = false; >let user = new User("Jack"); alert(user.name); // Jack alert(user.isAdmin); // false
Когда функция вызывается как new User(. ) , происходит следующее:
- Создаётся новый пустой объект, и он присваивается this .
- Выполняется тело функции. Обычно оно модифицирует this , добавляя туда новые свойства.
- Возвращается значение this .
Другими словами, new User(. ) делает что-то вроде:
function User(name) < // this = <>; (неявно) // добавляет свойства к this this.name = name; this.isAdmin = false; // return this; (неявно) >
Таким образом, let user = new User(«Jack») возвращает тот же результат, что и:
let user = < name: "Jack", isAdmin: false >;
Теперь, если нам будет необходимо создать других пользователей, мы можем просто вызвать new User(«Ann») , new User(«Alice») и так далее. Данная конструкция гораздо удобнее и читабельнее, чем многократное создание литерала объекта.
Это и является основной целью конструкторов – реализовать код для многократного создания однотипных объектов.
Давайте ещё раз отметим – технически любая функция (кроме стрелочных функций, поскольку у них нет this ) может использоваться в качестве конструктора. Его можно запустить с помощью new , и он выполнит выше указанный алгоритм. Подобные функции должны начинаться с заглавной буквы – это общепринятое соглашение, чтобы было ясно, что функция должна вызываться с помощью «new».
new function() < … >
Если в нашем коде присутствует большое количество строк, создающих один сложный объект, то мы можем обернуть их в функцию-конструктор, которая будет немедленно вызвана, вот так:
// создаём функцию и сразу же вызываем её с помощью new let user = new function() < this.name = "John"; this.isAdmin = false; // . другой код для создания пользователя // возможна любая сложная логика и инструкции // локальные переменные и так далее >;
Такой конструктор не может быть вызван снова, так как он нигде не сохраняется, просто создаётся и тут же вызывается. Таким образом, этот трюк направлен на инкапсуляцию кода, который создаёт отдельный объект, без возможности повторного использования в будущем.
Проверка на вызов в режиме конструктора: new.target
Продвинутая возможность
Синтаксис из этого раздела используется крайне редко. Вы можете пропустить его, если не хотите углубляться в детали языка.
Используя специальное свойство new.target внутри функции, мы можем проверить, вызвана ли функция при помощи оператора new или без него.
В случае обычного вызова функции new.target будет undefined . Если же она была вызвана при помощи new , new.target будет равен самой функции.
function User() < alert(new.target); >// без "new": User(); // undefined // с "new": new User(); // function User
Это можно использовать внутри функции, чтобы узнать, была ли она вызвана при помощи new , «в режиме конструктора», или без него, «в обычном режиме».
Также мы можем сделать, чтобы вызовы с new и без него делали одно и то же:
function User(name) < if (!new.target) < // в случае, если вы вызвали меня без оператора new return new User(name); // . я добавлю new за вас >this.name = name; > let john = User("John"); // переадресовывает вызов на new User alert(john.name); // John
Такой подход иногда используется в библиотеках, чтобы сделать синтаксис более гибким. Чтобы люди могли вызывать функцию с new и без него, и она все ещё могла работать.
Впрочем, вероятно, это не очень хорошая практика использовать этот трюк везде, так как отсутствие new может ввести разработчика в заблуждение. С new мы точно знаем, что создаётся новый объект.
Возврат значения из конструктора, return
Обычно конструкторы не имеют оператора return . Их задача – записать все необходимое в this , и это автоматически становится результатом.
Но если return всё же есть, то применяется простое правило:
- При вызове return с объектом, вместо this вернётся объект.
- При вызове return с примитивным значением, оно проигнорируется.
Другими словами, return с объектом возвращает этот объект, во всех остальных случаях возвращается this .
К примеру, здесь return замещает this , возвращая объект:
function BigUser() < this.name = "John"; return < name: "Godzilla" >; // alert( new BigUser().name ); // Godzilla, получили этот объект
А вот пример с пустым return (или мы могли бы поставить примитив после return , неважно):
function SmallUser() < this.name = "John"; return; // alert( new SmallUser().name ); // John
Обычно у конструкторов отсутствует return . Здесь мы упомянули особое поведение с возвращаемыми объектами в основном для полноты картины.
Пропуск скобок
Кстати, мы можем не ставить круглые скобки после new :
let user = new User; //
Пропуск скобок считается плохой практикой, но просто чтобы вы знали, такой синтаксис разрешён спецификацией.
Создание методов в конструкторе
Использование конструкторов для создания объектов даёт большую гибкость. Функции-конструкторы могут иметь параметры, определяющие, как создавать объект и что в него записывать.
Конечно, мы можем добавить к this не только свойства, но и методы.
Например, new User(name) ниже создаёт объект с заданным name и методом sayHi :
function User(name) < this.name = name; this.sayHi = function() < alert( "Меня зовут: " + this.name ); >; > let john = new User("John"); john.sayHi(); // Меня зовут: John /* john = < name: "John", sayHi: function() < . >> */
Для создания сложных объектов есть и более продвинутый синтаксис – классы, который мы рассмотрим позже.
Итого
- Функции-конструкторы или просто конструкторы, являются обычными функциями, но существует общепринятое соглашение именовать их с заглавной буквы.
- Функции-конструкторы следует вызывать только с помощью new . Такой вызов подразумевает создание пустого this в начале и возврат заполненного в конце.
Мы можем использовать конструкторы для создания множества похожих объектов.
JavaScript предоставляет функции-конструкторы для множества встроенных объектов языка: таких как Date , Set , и других, которые нам ещё предстоит изучить.
Мы ещё вернёмся к объектам!
В этой главе мы рассмотрели только основы объектов и конструкторов. Данная информация необходима нам для дальнейшего изучения типов данных и функций в последующих главах.
Как только мы с ними разберёмся, мы вернёмся к объектам для более детального изучения в главах Прототипы, наследование и Классы.
«Сложно о простом». Функции-конструкторы — как объекты,(prototype). Объекты,(__proto__). constructor, =<>, как функция-конструктор new Object()
В прошлый раз мы попытались разобраться со следующими вещами:
- 1. Не смотря на расхожее мнение «всё в JS является объектами» — это не так, мы выяснили, что из 6 доступных программисту типов данных аж 5 является примитивами и лишь один представляет тип объектов.
- 2. Про объекты мы узнали, что это такая структура данных, которая содержит в себе пары «ключ-значение». Значением может быть любой из типов данных (и это будет свойство объекта) или функция (и это будет метод объекта).
- 3. А вот примитивы – это не объекты. Хотя с ними и можно работать как с объектом (и это вызывает заблуждение что примитив – это объект), но…
- 4. Переменные можно объявить как по простому (литерально) (var a = ‘str’), так и через функцию-конструктор (обёртка)(var a = new String(‘str’)). Во втором случае мы получим уже не примитив, а объект созданный конструктором String(). (что за магический оператор new и что такое функция-конструктор мы узнаем дальше).
- 5. Узнали, что именно за счёт создания обёртки над примитивом (new String(‘str’)) c ним можно работать как с объектом. Именно эту обёртку создаёт интерпретатор вокруг примитива, когда мы пытаемся работать с ним как с объектом, но после выполнения операции она разрушается (поэтому примитив никогда не сможет запомнить свойство, которое мы ему присвоим a.test = ‘test’- свойство test исчезнет с обёрткой).
- 6. Узнали, что у объектов есть метод toString() который возвращает строковое представление объекта (для типа number valueOf() – вернёт числовое значение).
- 7. Поняли, что при выполнении операций конкатенации или математических операциях примитивы могут переопределить свой тип в нужный. Для этого они используют функции-обёртки своих типов, но без оператора new (str = String(str)).(в чём разница и как это работает, поговорим дальше)
- 8. И наконец, узнали, что typeof берёт значения из жёстко зафиксированной таблицы (вот откуда ещё одно заблуждение, основанное на typeof null //object).
function A() <> A.prototype.x = 10; a = new A(); console.log(a.x); //10 console.log(a.y); //undefined A.prototype.y = 20; console.log(a.y); //20 /*То есть при таком подходе A.prototype. добавления свойств в прототип все эти свойства стразу появляются у экземпляра*/ Пример2: function B() <> B.prototype.x = 10; b = new B(); console.log(b.x); //10 console.log(b.y); //undefined B.prototype = ; console.log(b.x); //10 console.log(b.y); //undefined /*А при таком подходе B.prototype = ; добавления свойств в прототип с экземпляром ничего не происходит*/ b1 = new B(); console.log(b1.x); //10 console.log(b1.y); //20 /*Зато у последующих потомков появляются все новые свойства*/ b instanceof B; //false b1 instanceof B //true /*Более того оказывается что придедущие потомки уже и не потомки вовсе*/
Функции-конструкторы, как объекты, prototype.
Начнём по порядку. Рассмотрим простую на первый взгляд строчку кода.
function A() <>
Самое первое что можно сказать: «Мы объявили функцию с именем A». Совершенно верно. Но здесь есть нюансы.
1. Не забываем что в JS — практически всё есть Объект. Функция, как оказалось не исключение(это даже два объекта связанные ссылкой).
2. Её можно использовать как функцию-конструктор.
В JavaScript нет того, что принято называть классами. Работу классов в JavaScript выполняют функции-конструкторы, которые создают объекты с определенными заданными свойствами.
В общем-то говоря любая объект-функция в JS может быть конструктором( я говорю о пользовательских функциях). Их условно можно поделить на три (DF(Функция декларация), FE(Функция выражение), функции созданные конструктором Function()). У всех этих функций есть свои особенности(по этому они разделены на разные группы), но о них я здесь рассказывать не буду, если кому интересно я отвечу лично или напишу отдельно про них в другой раз. Однако у них есть и одна общая черта, которая позволяет им быть конструкторами — это наличие внутренних свойств [[Construct]] и [[Call]], а также явного свойства prototype(о нем ниже).
Именно внутренний метод [[Construct]] отвечает за выделения памяти под новый объект и его инициализацию. Однако — это не значит что вызов функции приведёт к созданию объекта, конечно нет. Для этого перед вызовом функции нужно поставить оператор new. Именно new запускает метод [[Construct]] и связанные с ним процессы.
function A()<> A(); //просто вызов функции var a = new A(); //вызов функции-конструктора. Экземпляр (a) созданный функцией-конструктором (A)
3. Так же можно сказать что это функция декларация(DF) и прочее, но остальное пока не важно.
Итак Функция «A» (из первой строчки первого примера) — это функция-конструктор и по совместительству объект. Раз это объект — она может иметь свойства. Так оно и есть. А раз это функций-конструктор, то она имеет свойство prototype. Свойство prototype — это ссылка на объект, который хранит свойства и методы которые перейдут к экземплярам созданным этой функцией-конструктором. Давайте попробуем всё это отобразить графически.
По умолчанию объект prototype «пустой» (ну почти пустой, но об это ниже). Выше я сказал что всё что лежит в этом объекте перейдёт в экземпляр, а так же будет доступно потомкам. То есть по умолчанию(если ничего в prototype не дописывать), то в экземпляр «ничего» не перейдёт от функции-конструктора «A». То есть при выполнении кода:
function A()<> var a = new A();
мы получим «обычный»( насколько это можно в JS ) объект «а».
В JS уже встроено много функций-конструкторов. Это например Number(), String() и т. д. Давайте отвлечёмся ненадолго от примера и поговорим о встроенных функциях-конструкторах и об Объектах в целом.
Объекты(__proto__).
Из прошлой статьи, мы знаем, что при создании (явно или не явно) объектов одним из встроенных конструкторов Number(), String() или Boolean(), экземпляр получает доступ к некоторым методам характерным данному типу. Например для Number() есть метод toPrecision(). Если посмотреть в консоли на объект созданный конструктором new Number(2), то Вы не обнаружите там этого метода(Вы вообще не обнаружете там методов). Откуда же он берётся? Как раз он и подобные ему методы(к которым должен иметь доступ потомок) и содержатся в prototype-объекте родителя. Но как экземпляр получает к ним доступ? У экземпляра есть свойство __proto__ — это ссылка на prototype-объект родителя. Если при вызове метода, метод не находится в самом экземпляре, происходит переход по ссылке __proto__ в prototype-объект родителя и поиск продолжается там. На самом деле так продолжается и дальше пока не будет встречен null.
Попробуем всё это нарисовать:
Подведя итог можно сказать, что пока всё не сложно: Есть родитель(функция-конструктор), у которой есть ссылка в свойстве prototype на некий объект где хранятся все методы и свойства к которым потомок должен иметь доступ. И есть, собственно, потомок которому при создании через вызов new от родителя передаётся ссылка в свойство __proto__ на тот самый объект с общими свойствами и методами.
Для закрепления попробуем рассмотреть пример:
function A() <> //Мы создаём функцию-конструктор (пока с «пустым» prototype) A.prototype.x = 10;//Теперь мы добавляем в prototype(через ссылку в сам объект) свойство (x) равное 10 a = new A(); //Создаём экземпляр у которого свойство __proto__ станет ссылкой на объект prototype свойством (x==10) console.log(a.x); //Свойство (x) не будет обнаружено в самом экземпляре (a), но пройдя по ссылке __proto__ интерпретатор найдёт его в объекте prototype. console.log(a.y); // А вот (y) нет ни там ни там. A.prototype.y = 20; //Однако добавив свойство (y) в prototype(через ссылку родителя в сам объект) console.log(a.y); //20 //Интерпретатор сможет найти и это свойство через ссылку __proto__ потомка
constructor.
Я всегда брал слово (пустой) в кавычки когда говорил («пустой» prototype). Мол когда мы создаём функцию-конструктор function A()<>, то создаётся свойство prototype с ссылкой на «пустой» prototype-объект. На самом деле нет. В prototype всё же кое-что лежит. Во-первых поскольку как я уже говорил prototype — это «простой» Объект, то там лежит свойство __proto__ с ссылкой на prototype функции-конструктора Object() (именно она создаёт всё «простые», самые элементарные объекты), а во-вторых там лежит свойство constructor. Свойство constructor туда добавляет интерпретатор, когда понимает что создаётся функция-конструктор а не просто объект Для начала давайте дополним наш первый рисунок с учётом этих двух фактов.
Всё что нарисовано серым, нам сейчас особо не нужно — это для более полной картины. Сосредоточимся на свойстве constructor. Как видно из рисунка constructor указывает на саму функцию-конструктор для которой изначально было создано это «хранилище», этот объект. То есть между свойством prototype функции-конструктора и свойством constructor объекта-prototype появляется цикличность — они указывают на объекты друг-друга.
Через свойство constructor (если оно всё ещё указывает на конструктор, а свойство prototype конструктора, в свою очередь, всё ещё указывает на первоначальный прототип) косвенно можно получить ссылку на прототип объекта: a.constructor.prototype.x. А можно полуть ссылку к самой функции-конструктору и её свойствам которые были присвоены не в prototype-объект, а конкретно к ней. Например:
function A()<> A.own = 'I am A!'; //А как я говорил функция — тоже объект и мы можем добавлять свойства a = new A(); a.own //undefined a.constructor.own // 'I am A!';
=<> — как функция-конструктор (new Object()).
Отлично, вроде как всё встало на свои места. Есть «общее хранилище», у родителя и потомка есть ссылки на это хранилище, если свойства нет в самом экземпляре, то интерпретатор перейдя по ссылке поищет его в «общем хранилище». В чём загвоздка?? Посмотрим Пример2:
function B() <> B.prototype.x = 10; b = new B(); console.log(b.x); //10 console.log(b.y); //undefined B.prototype = ; console.log(b.x); //10 console.log(b.y); //undefined
Вроде как всё должно работать. Мы создали функцию-конструктор, задали «общему хранилищу» (prototype(через ссылку)) свойство (x), создали экземпляр, свойство (x) у него есть — всё нормально. Потом мы вообще переопределили свойство родителя prototype, добавив свойства (x) и (y) указали верный constructor. Всё должно работать в «общем хранилище лежит» оба этих свойства, но нет, (y) интерпретатор не находит. WTF.
Что же здесь за магия происходит? Почему мы не видим этих изменений из потомка этого конструктора? Почему потомок не видит y? Ну во-первых мы переопределяем свойство prototype функции-конструктора(B) и оно начинает ссылаться на новый объект (связь с первоначальным объектом prototype разорвана). Во-вторых обычное присвоение переменной объекта, типа: var a = <>, интерпретатором на самом деле выполняется как var a = new Object(). А это значит, что свойство prototype функции-конструктора теперь содержит совершенно новый объект у которого ссылка constructor отсутствует и чтоб не потерять родителя мы самостоятельно дописываем туда свойство constructor и присваиваем ему самого родителя.
А экземпляр сделанный ранее содержит ссылку __proto__ на старый объект prototype где свойства (y) нет. То есть в отличии от Примера1 здесь мы не «добавили в хранилище свойство» и даже не «переписали хранилище заново», мы просто создали новое, разорвав связь с старым, а экземпляр об этом ничего не знает, он всё ещё пользуется старым по своей старой ссылке __proto__. Выглядит это вот так:
Чёрным цветом — это то что не изменилось и после B.prototype = ;
Красным — то что удалилось
Зелёным — то что добавилось
Так же можно добавить немного об instanceof. Как ни странно, но в данном примере b1 будет принадлежать функции-конструктору B, а b — нет. Всё очень просто. Дело в том что instanceof ищет выполнения следующего условия — что бы объект указанный по ссылке __proto__(на любом уровне цепочки)(кружочек с цифрой 1) был равен объекту на который ссылается свойство prototype искомого родителя(кружочек с цифрой 2)(сравните на рисунке чёрный цвет и зелёный). В чёрном цвете это условие уже не выполняется, а в зелёном — выполняется.
В нашем случае у экземпляра (b) эта связь разорвана, так как новое свойство prototype искомого родителя(B) ссылается уже на новый объект, а не как раньше. Зато у экземпляра (b1) с этим как видим всё в порядке.
Вдогонку
По поводу this в теле функции-конструктора и вообще углубляться не буду — об этом в следующей статье. Единственное что скажу, это то, что this при вызове функции как конструктора(через new) будет указывать на создаваемый экземпляр, а при вызове как функции — на глобальный объект.
Давайте разберём на примере:
function A(str) < this.val = str; >a = new A('test'); //Сдесь свойство val добавится в экземпляр, так как при вызове функции как конструктора this всегда будет указывать на создаваемый экземпляр A('test'); //А тут this — это глобальный объект. И теперь у него добавится свойство val console.log(val) //'test'
Как же узнать как вызвали функцию? Через new или нет? Это делается очень просто:
function A(str) < if( this instanceof A) //Если this является экземпляром A(то есть вызов через new) this.val = str; //добавить экземпляру свойство val else retur str; //Иначе(при простом вызове функции без new) вернуть строку >
Примерно таким образом реализован механизм приведения типов. При выполнении например 1+'1' интерпретатор воспринимает + как конкатенацию строк и пытается привести число 1 в строку. Это происходит с помощью неявного вызова String(1)(без new). А в конструкторе String написана примерно та же конструкция что у нас выше. То есть если вызов произошел без new просто вернуть строку(неявный вызов метода toString()). Таким образом без создания каких либо объектов происходит преобразование типов.
Так же хочу добавить следующее, что бы добавить свойство к функции(именно к функции а не к prototype) нужно обратится к ней как к объекту. Например
function A()<> A.val = 'str';
Это свойство будет недоступно потомку, так как оно не лежит в prototype, а потомок имеет доступ только туда. Но как говорится «если сильно хочется то можно». Тут то нам и пригодится свойсто объекта prototype — constructor. Оно как мы помним ссылается на саму функцию(если конечно этого специально не меняли). Тогда чтоб получить переменную val нужно обратится к ней так:
function A()<> A.val = 'str'; a = new A(); a.constructor.val; //'str'
Обработка строк в Java. Часть I: String, StringBuffer, StringBuilder
Что вы знаете о обработке строк в Java? Как много этих знаний и насколько они углублены и актуальны? Давайте попробуем вместе со мной разобрать все вопросы, связанные с этой важной, фундаментальной и часто используемой частью языка. Наш маленький гайд будет разбит на две публикации:
- String, StringBuffer, StringBuilder (реализация строк)
- Pattern, Matcher (регулярные выражения)
String
Строка — объект, что представляет последовательность символов. Для создания и манипулирования строками Java платформа предоставляет общедоступный финальный (не может иметь подклассов) класс java.lang.String. Данный класс является неизменяемым (immutable) — созданный объект класса String не может быть изменен. Можно подумать что методы имеют право изменять этот объект, но это неверно. Методы могут только создавать и возвращать новые строки, в которых хранится результат операции. Неизменяемость строк предоставляет ряд возможностей:
- использование строк в многопоточных средах (String является потокобезопасным (thread-safe) )
- использование String Pool (это коллекция ссылок на String объекты, используется для оптимизации памяти)
- использование строк в качестве ключей в HashMap (ключ рекомендуется делать неизменяемым)
Создание
Мы можем создать объект класса String несколькими способами:
1. Используя строковые литералы:
String habr = "habrahabr";
Строковый литерал — последовательность символов заключенных в двойные кавычки. Важно понимать, что всегда когда вы используете строковой литерал компилятор создает объект со значением этого литерала:
System.out.print("habrahabr"); // создали объект и вывели его значение
2. С помощью конструкторов:
String habr = "habrahabr"; char[] habrAsArrayOfChars = ; byte[] habrAsArrayOfBytes = ; String first = new String(); String second = new String(habr);
Если копия строки не требуется явно, использование этих конструкторов нежелательно и в них нет необходимости, так как строки являются неизменными. Постоянное строительство новых объектов таким способом может привести к снижению производительности. Их лучше заменить на аналогичные инициализации с помощью строковых литералов.
String third = new String(habrAsArrayOfChars); // "habrahabr" String fourth = new String(habrAsArrayOfChars, 0, 4); // "habr"
Конструкторы могут формировать объект строки с помощью массива символов. Происходит копирование массива, для этого используются статические методы copyOf и copyOfRange (копирование всего массива и его части (если указаны 2-й и 3-й параметр конструктора) соответственно) класса Arrays, которые в свою очередь используют платформо-зависимую реализацию System.arraycopy.
String fifth = new String(habrAsArrayOfBytes, Charset.forName("UTF-16BE")); // кодировка нам явно не подходит "桡扲慨慢�"
Можно также создать объект строки с помощью массива байтов. Дополнительно можно передать параметр класса Charset, что будет отвечать за кодировку. Происходит декодирование массива с помощью указанной кодировки (если не указано — используется Charset.defaultCharset(), который зависит от кодировки операционной системы) и, далее, полученный массив символов копируется в значение объекта.
String sixth = new String(new StringBuffer(habr)); String seventh = new String(new StringBuilder(habr));
Ну и наконец-то конструкторы использующие объекты StringBuffer и StringBuilder, их значения (getValue()) и длину (length()) для создания объекта строки. С этими классами мы познакомимся чуть позже.
Приведены примеры наиболее часто используемых конструкторов класса String, на самом деле их пятнадцать (два из которых помечены как deprecated).
Длина
Важной частью каждой строки есть ее длина. Узнать ее можно обратившись к объекту String с помощью метода доступа (accessor method) length(), который возвращает количество символов в строке, например:
public static void main(String[] args) < String habr = "habrahabr"; // получить длину строки int length = habr.length(); // теперь можно узнать есть ли символ символ 'h' в "habrahabr" char searchChar = 'h'; boolean isFound = false; for (int i = 0; i < length; ++i) < if (habr.charAt(i) == searchChar) < isFound = true; break; // первое вхождение >> System.out.println(message(isFound)); // Your char had been found! // ой, забыл, есть же метод indexOf System.out.println(message(habr.indexOf(searchChar) != -1)); // Your char had been found! > private static String message(boolean b)
Конкатенация
Конкатенация — операция объединения строк, что возвращает новую строку, что есть результатом объединения второй строки с окончанием первой. Операция для объекта String может быть выполнена двумя способами:
1. Метод concat
String javaHub = "habrhabr".concat(".ru").concat("/hub").concat("/java"); System.out.println(javaHub); // получим "habrhabr.ru/hub/java" // перепишем наш метод используя concat private static String message(boolean b)
Важно понимать, что метод concat не изменяет строку, а лишь создает новую как результат слияния текущей и переданной в качестве параметра. Да, метод возвращает новый объект String, поэтому возможны такие длинные «цепочки».
2. Перегруженные операторы "+" и "+="
String habr = "habra" + "habr"; // "habrahabr" habr += ".ru"; // "habrahabr.ru"
Это одни с немногих перегруженных операторов в Java — язык не позволяет перегружать операции для объектов пользовательских классов. Оператор "+" не использует метод concat, тут используется следующий механизм:
String habra = "habra"; String habr = "habr"; // все просто и красиво String habrahabr = habra + habr; // а на самом деле String habrahabr = new StringBuilder()).append(habra).append(habr).toString(); // может быть использован StringBuffer
Используйте метод concat, если слияние нужно провести только один раз, для остальных случаев рекомендовано использовать или оператор "+" или StringBuffer / StringBuilder. Также стоит отметить, что получить NPE (NullPointerException), если один с операндов равен null, невозможно с помощью оператора "+" или "+=", чего не скажешь о методе concat, например:
String string = null; string += " habrahabr"; // null преобразуется в "null", в результате "null habrahabr" string = null; string.concat("s"); // логично что NullPointerException
Форматирование
Класс String предоставляет возможность создания форматированных строк. За это отвечает статический метод format, например:
String formatString = "We are printing double variable (%f), string ('%s') and integer variable (%d)."; System.out.println(String.format(formatString, 2.3, "habr", 10)); // We are printing double variable (2.300000), string ('habr') and integer variable (10).
Методы
Благодаря множеству методов предоставляется возможность манипулирования строкой и ее символами. Описывать их здесь нет смысла, потому что Oracle имеет хорошие статьи о манипулировании и сравнении строк. Также у вас под рукой всегда есть их документация. Хотелось отметить новый статический метод join, который появился в Java 8. Теперь мы можем удобно объединять несколько строк в одну используя разделитель (был добавлен класс java.lang.StringJoiner, что за него отвечает), например:
String hello = "Hello"; String habr = "habrahabr"; String delimiter = ", "; System.out.println(String.join(delimiter, hello, habr)); // или так System.out.println(String.join(delimiter, new ArrayList(Arrays.asList(hello, habr)))); // в обоих случаях "Hello, habrahabr"
Это не единственное изменение класса в Java 8. Oracle сообщает о улучшении производительности в конструкторе String(byte[], *) и методе getBytes().
Преобразование
1. Число в строку
int integerVariable = 10; String first = integerVariable + ""; // конкатенация с пустой строкой String second = String.valueOf(integerVariable); // вызов статического метода valueOf класса String String third = Integer.toString(integerVariable); // вызов метода toString класса-обертки
2. Строку в число
String string = "10"; int first = Integer.parseInt(string); /* получаем примитивный тип (primitive type) используя метод parseXхх нужного класса-обертки, где Xxx - имя примитива с заглавной буквы (например parseInt) */ int second = Integer.valueOf(string); // получаем объект wrapper класса и автоматически распаковываем
StringBuffer
Строки являются неизменными, поэтому частая их модификация приводит к созданию новых объектов, что в свою очередь расходует драгоценную память. Для решения этой проблемы был создан класс java.lang.StringBuffer, который позволяет более эффективно работать над модификацией строки. Класс является mutable, то есть изменяемым — используйте его, если Вы хотите изменять содержимое строки. StringBuffer может быть использован в многопоточных средах, так как все необходимые методы являются синхронизированными.
Создание
Существует четыре способа создания объекта класса StringBuffer. Каждый объект имеет свою вместимость (capacity), что отвечает за длину внутреннего буфера. Если длина строки, что хранится в внутреннем буфере, не превышает размер этого буфера (capacity), то нет необходимости выделять новый массив буфера. Если же буфер переполняется — он автоматически становиться больше.
StringBuffer firstBuffer = new StringBuffer(); // capacity = 16 StringBuffer secondBuffer = new StringBuffer("habrahabr"); // capacity = str.length() + 16 StringBuffer thirdBuffer = new StringBuffer(secondBuffer); // параметр - любой класс, что реализует CharSequence StringBuffer fourthBuffer = new StringBuffer(50); // передаем capacity
Модификация
В большинстве случаев мы используем StringBuffer для многократного выполнения операций добавления (append), вставки (insert) и удаления (delete) подстрок. Тут все очень просто, например:
String domain = ".ru"; // создадим буфер с помощью String объекта StringBuffer buffer = new StringBuffer("habrahabr"); // "habrahabr" // вставим домен в конец buffer.append(domain); // "habrahabr.ru" // удалим домен buffer.delete(buffer.length() - domain.length(), buffer.length()); // "habrahabr" // вставим домен в конец на этот раз используя insert buffer.insert(buffer.length(), domain); // "habrahabr.ru"
Все остальные методы для работы с StringBuffer можно посмотреть в документации.
StringBuilder
StringBuilder — класс, что представляет изменяемую последовательность символов. Класс был введен в Java 5 и имеет полностью идентичный API с StringBuffer. Единственное отличие — StringBuilder не синхронизирован. Это означает, что его использование в многопоточных средах есть нежелательным. Следовательно, если вы работаете с многопоточностью, Вам идеально подходит StringBuffer, иначе используйте StringBuilder, который работает намного быстрее в большинстве реализаций. Напишем небольшой тест для сравнения скорости работы этих двух классов:
public class Test < public static void main(String[] args) < try < test(new StringBuffer("")); // StringBuffer: 35117ms. test(new StringBuilder("")); // StringBuilder: 3358ms. >catch (java.io.IOException e) < System.err.println(e.getMessage()); >> private static void test(Appendable obj) throws java.io.IOException < // узнаем текущее время до теста long before = System.currentTimeMillis(); for (int i = 0; i++ < 1e9; ) < obj.append(""); >// узнаем текущее время после теста long after = System.currentTimeMillis(); // выводим результат System.out.println(obj.getClass().getSimpleName() + ": " + (after - before) + "ms."); > >
Спасибо за внимание. Надеюсь статья поможет узнать что-то новое и натолкнет на удаление всех пробелов в этих вопросах. Все дополнения, уточнения и критика приветствуются.