Небольшой вопрос о наследовании прототипа
Недавно я пытался создать пользовательский метод, например, JS .toUpperCase() и другие методы ..., который использует this ссылку из префиксного объекта.

(глупый бесполезный пример)

Object.prototype.customMethod = function(){
    return this.toLowerCase() ; 
};

И может быть использован как:

// access some object and get a key value
member.name; // JOHN
// use custom Method
member.name.customMethod(); // john

Проблема в том, что метод .customMethod() наследуется глобально каждый Object.
Как сделать его менее навязчивым и ссылаться только на префиксный объект? или вообще?

Вот пример: http://jsbin.com/evaneg/2/edit

// CREATE OBJECT
var obj = { "text" : "Hi" };

// TEST OBJECT KEY VALUE
alert( "OBJECT TEXT: "+ obj.text );     // Hi


// CREATE CUSTOM METHOD ( just like e.g. JS's .toUpperCase() method )
Object.prototype.addText = function(){ 
  return this+' there!';  
};

// USE CUSTOM .addText() METHOD
alert( "OBJECT TEXT+method: "+obj.text.addText() ); // Hi there! // wow, the method works!


for(var key in obj){
  alert( 'obj KEYS: '+ key ); // text // addText
}

// hmm... addText method as obj Key ???
// ok let's try with a new one...

var foobee = { "foo" : "bee" };

for(var key in foobee){
  alert( 'foobee KEYS: '+ key ); // foo // addText
}

// addText  ...again... but why?

Я также прочитал этот http://javascript.crockford.com/prototypal.html и множество других подобные вещи здесь, в SOverflow, но большинство из них сосредоточены на использовании создания new F( arguments ), использующего конкретные аргументы, что не в моем случае. Спасибо за любое объяснение

3
Ginnani 28 Янв 2013 в 02:10

2 ответа

Лучший ответ

Вам не нужно добавлять метод к каждому объекту - только тот тип объекта, с которым вы работаете. Если это строковый метод, вы можете добавить его в String.prototype и определить его для всех строк.

var obj = { "text" : "Hi" };

// We only want to add this method to strings.
String.prototype.addText = function(){ 
  return this+' there!';  
};

alert("OBJECT TEXT+method: " + obj.text.addText());

Обратите внимание, что это делает методы enumerable , что означает, что они отображаются в цикле for..in.


Object.defineProperty

Если вы можете заботиться только о поддержке браузеров с 2010 года (без IE8), тогда я настоятельно рекомендую использовать Object.defineProperty чтобы определить свойства, чтобы они не были перечисляемыми:

var obj = { foo: 'bar' };

// Extending all objects this way is likely to break other scripts...
Object.prototype.methodA = function() { return 'A'; };

// Instead you can do extensions this way...
Object.defineProperty(Object.prototype, 'methodB', {
    value: function() { return 'B'; },
    // Prevent the method from showing up in for..in
    enumerable: false,
    // Allow overwriting this method.
    writable: true,
    // Allow reconfiguring this method.
    configurable: true
});

for (var propertyName in obj) {
    console.log(propertyName);
    // Logs: "foo" and "methodA" but not "methodB"
}

Приведенный выше код регистрирует свойство «foo» и свойство «methodA», но не регистрирует свойство «methodB», потому что мы определили его как не перечисляемый. Вы также заметите, что встроенные методы, такие как "toString", "valueOf", "hasOwnProperty" и т. Д., Также не отображаются. Это потому, что они также определены как не перечисляемые.

Таким образом, другие сценарии смогут свободно использовать for..in, и все должно работать как положено.


Возвращаясь к конкретным встроенным

Мы также можем определять не перечисляемые методы для определенных типов объектов с помощью Object.defineProperty. Например, следующее добавляет метод contains ко всем массивам, который возвращает true, если массив содержит значение, и false, если его нет:

Object.defineProperty(Array.prototype, 'contains', {
    value: (function() {
        // We want to store the `indexOf` method so that we can call
        // it as a function. This is called uncurrying `this`.
        // It's useful for ensuring integrity, but I'm mainly using
        // it here so that we can also call it on objects which aren't
        // true Arrays.
        var indexOf = Function.prototype.call.bind(Array.prototype.indexOf);
        return function(value) {
            if (this == null)
                throw new TypeError('Cannot be called on null or undefined.');
            return !!~indexOf(this, value);
        }
    })(),
    enumerable: false,
    writable: true,
    configurable: true
});

var colors = [ 'red', 'green', 'blue', 'orange' ];

console.log(colors.contains('green'));  // => true
console.log(colors.contains('purple')); // => false

Обратите внимание, что поскольку мы определили этот метод для Array.prototype, он доступен только для массивов. Он недоступен для других объектов. Однако, в духе других методов массива, он написан достаточно обобщенно, чтобы его можно было вызывать для объектов, подобных массиву:

function foo() {
    Array.prototype.contains.call(arguments, 5);
}

console.log(foo(1, 2, 3, 4, 5));  // => true
console.log(foo(6, 7, 8, 9, 10)); // => false

Вызов contains для arguments работает выше, хотя событие arguments не является истинным массивом.


Упрощение Boilerplate

Использование Object.defineProperty дает много возможностей, но также требует большого количества дополнительного кода, который большинство людей предпочитают не вводить постоянно. Это стало понятно, когда комитет ECMAScript определил эту функцию, но они предполагали, что люди могут писать вспомогательные функции, чтобы сделать код чище. Имейте это в виду. Например, вы всегда можете сделать что-то вроде следующего:

var define = (function() {
    // Let's inherit from null so that we can protect against weird situations
    // like Object.prototype.get = function() { };
    // See: https://mail.mozilla.org/pipermail/es-discuss/2012-November/026705.html
    var desc = Object.create(null);
    desc.enumerable = false;
    desc.writable = true;
    desc.configurable = true;
    return function define(constructor, name, value) {
        if (typeof constructor != 'function'
            || !('prototype' in constructor))
            throw new TypeError('Constructor expected.');
        desc.value = value;
        Object.defineProperty(constructor.prototype, name, desc);
    }
})();

Тогда вы можете сделать:

define(String, 'addText', function() {
    return this + ' there!';
});

console.log('Hi'.addText()); // => "Hi there!"

Есть даже несколько небольших библиотек, которые были разработаны, чтобы помочь с некоторыми из этих вещей. Посмотрите redefine.js Андреа Гиаммарки в качестве примера.


Предосторожность

Единственное реальное предостережение: если вы добавите свои собственные методы во встроенные модули, возможно столкновение имен с (a) будущими версиями JavaScript или (b) другими сценариями. (Проблема с конфликтом имен решается в следующей версии JavaScript с помощью символов .) Многие утверждают, что это достаточно большая проблема, поэтому вам никогда не следует изменять встроенные модули, но следует придерживаться изменяя только то, что вам «принадлежит» - то, что вы создали с помощью своих собственных конструкторов. Я склонен согласиться в некоторых (или многих) ситуациях, но я думаю, что для вашего личного использования и обучения игра со встроенными прототипами может быть очень полезной и забавной.

Ознакомьтесь с этой статьей о том, как взвесить некоторые затраты на изменение встроенных модулей, чтобы получить более подробное описание возможных недостатков: http://perfectionkills.com/extending-built-in-native-objects-evil-or-not/ Обратите внимание, что эта статья немного устарела (1 1 / 2 года), и одна из его главных жалоб - это перечислимость, которую можно преодолеть с помощью Object.defineProperty во всех современных браузерах. Как я уже сказал, другая важная проблема (столкновения имен) будет решена в следующей версии JavaScript. Все становится лучше!

3
Nathan Wall 28 Янв 2013 в 02:23

Вы должны также использовать новый F (). вам просто нужно определить F. Давайте назовем F 'Name', хотя его легче идентифицировать.

var Name = function(){
    var newName = {
        text: 'Hello',
        customMethod: function(){
            this.text = this.text.toUpperCase();
        }
    }
    return newName;
}

var Member = function(){
    var newMember = {
        name = new Name(),
        customMethod: function(){
        this.name.customMethod(); // make a member invoke upperCase on name (or any number of vars)
        }
    }
    return newMember;
}

var member = new Member();  //now when you run new Member, it runs the function above. and automatically makes your object with it's properties and methods.

console.log(member.name.text); //Hello!
member.customMethod();
// or
member.name.customMethod();
console.log(member.name.text); //HELLO!
0
Patrick Gunderson 27 Янв 2013 в 23:01