Многоразовые объекты javascript, прототипы и область действия

MyGlobalObject;

function TheFunctionICanUseRightAwaySingleForAllInstansesAndWithoutInstanse() {
    function() {
        alert('NO CONSTRUCTOR WAS CALLED');
    }
};

Функция с длинными именами должна быть вызвана из MyGlobalObject, которая в свою очередь должна быть доступна как глобальная (до window) переменная во все времена после загрузки script. Он должен поддерживать расширяемость в соответствии с последними стандартами.

Я нахожусь в архитектурной дилемме о том, как создать базу JS для приложения (почти 100% JS).

Нам нужен объект i.e. window.MyObject (например, модуль, например jQuery), поэтому

Он может быть создан с помощью

VAR1

 var MyGlobalObjConstructor = function(){
     this.GlobalFunctionInObject = function(){
        alert('called with MyGlobalObj.GlobalFunctionInObject()');
        }        
};
window.MyGlobalObj = new MyGlobalObjConstructor();    

Является ли MyGlobalObj расширяемым? Могу ли я создать дочерние объекты, которые наследуют текущее состояние MyGlobalObj (расширенные функции/свойства MyGlobalObj.NewFunc например.)? В чем основное отличие использования прототипа (VAR3)?

Под GlobaldFunction Я имею в виду один экземпляр для всех инициализированных/инстанцированных (возможно, экземпляров) экземпляров..

Или с помощью

VAR2

var MyGlobalObj = {
    GlobalFunctionInObject: function...
    GlobalFunctionInObject2: function...
};
MyGlobalObj.GlobalFunctionInObject();
// here I lose all hierarchy elements, no prototype, 
// can I use GlobalFunctionInObject2 in GlobalFunctionInObject?

Или с помощью

VAR3

var MyGlobalConstuctor = function(){} // already 'well-formed' object
MyGlobalConstuctor.prototype.GlobalFunctionInObject = function...
};
var MyGlobalObj = new MyGlobalConstuctor();

// so I'm sceptical to NEW, because I have ALREADY wrote my functions 
// which I expect to be in memory, single instance of each of them, 
// so creating MyObject2,3,4 with NEW MyGC() makes no sense to me.
// DO I REALLY HAVE TO USE "MyGlobalConstuctor.prototype." FOR EACH FUNCTION?!!!!

Какая разница определяет MyGlobalObj как функцию и как объект (результат func или VAR2)?

ИЛИ VAR4?

Я вижу в Отладчике Chrome как прототип, так и __proto__ специальные поля. Я читал, что это нормально, но почему они не сохраняются в одном прототипе?

Итак, каков правильный/оптимальный способ реализации window.MyObject, поэтому можно было бы MyObject.MyFunction(); В чем отличия (pro/contra) вариантов 1 2 и 3?

Ответ 1

Вариант 1 - Mixin

function SomeType() {
    var priv = "I'm private";
    this.publ = "I'm public";
    this.action = function() {
        return priv + this.publ;
    };
}

var obj = new SomeType();

С помощью этого метода вы создаете новый объект каждый раз, когда вы вызываете new SomeType(), создавая все его методы и добавляя весь этот метод к новому объекту. Каждый раз, когда вы создаете объект.

Pros

  • Это похоже на классическое наследование, поэтому легко понять Java-С# -С++ - и т.д. люди.
  • Он может иметь частные переменные на один экземпляр, поскольку у вас есть одна функция closure для каждого создаваемого вами объекта
  • Он допускает множественное наследование, также известное как Twitter-mixins или функциональные микшины
  • obj instanceof SomeType вернет true

Против

  • Он потребляет больше памяти в качестве большего количества объектов, созданных вами, потому что с каждым объектом вы создаете новое замыкание и снова создаете каждый из его методов.
  • Частные свойства private, а не protected, подтипы не могут получить к ним доступ.
  • Нет простого способа узнать, имеет ли объект некоторый тип в качестве суперкласса.

Наследование

function SubType() {
    SomeType.call(this);
    this.newMethod = function() {
        // can't access priv
        return this.publ;
    };
}

var child = new SubType();

child instanceof SomeType вернет false, нет другого способа узнать, есть ли у ребенка методы SomeType, чем посмотреть, есть ли он один за другим.

Вариант 2 - Объектный литерал с прототипом

var obj = {
    publ: "I'm public",
    _convention: "I'm public too, but please don't touch me!",
    someMethod: function() {
        return this.publ + this._convention;
    }
};

В этом случае вы создаете один объект. Если вам понадобится только один экземпляр этого типа, это может быть лучшим решением.

Pros

  • Это быстро и легко понять.
  • производительным

Против

  • Отсутствие конфиденциальности, каждое свойство является общедоступным.

Наследование

Вы можете наследовать объект, прототипирующий его.

var child = Object.create(obj);
child.otherMethod = function() {
    return this._convention + this.publ;
};

Если вы находитесь в старом браузере, вам нужно будет гарантировать Object.create works:

if (!Object.create) {
    Object.create = function(obj) {
        function tmp() { }
        tmp.prototype = obj;
        return new tmp;
    };
}

Чтобы узнать, является ли объект прототипом другого, вы можете использовать

obj.isPrototypeOf(child); // true

Вариант 3 - Конструкторский шаблон

ОБНОВЛЕНИЕ: Это шаблон ES6 - это синтаксис сахара. Если вы используете классы ES6, вы следуете этому шаблону под капотом.

class SomeType {
    constructor() {
        // REALLY important to declare every non-function property here
        this.publ = "I'm public";
        this._convention = "I'm public too, but please don't touch me!";
    }
    someMethod() {
        return this.publ + this._convention;
    }
}

class SubType extends SomeType {
    constructor() {
        super(/* parent constructor parameters here */);
        this.otherValue = 'Hi';
    }
    otherMethod() {
        return this._convention + this.publ + this.otherValue;
    }
}

function SomeType() {
    // REALLY important to declare every non-function property here
    this.publ = "I'm public";
    this._convention = "I'm public too, but please don't touch me!";
}

SomeType.prototype.someMethod = function() {
    return this.publ + this._convention;
};

var obj = new SomeType();

Вы можете повторно назначить прототип вместо добавления каждого метода, если вы не наследуете, и не забудьте повторно назначить свойство конструктора:

SomeType.prototype = {
    constructor: SomeType,
    someMethod = function() {
        return this.publ + this._convention;
    }
};

Или используйте _.extend или $.extend, если на вашей странице есть символ подчеркивания или jquery

_.extend(SomeType.prototype, {
    someMethod = function() {
        return this.publ + this._convention;
    }
};

Ключевое слово new под капотом просто делает это:

function doNew(Constructor) {
    var instance = Object.create(Constructor.prototype);
    instance.constructor();
    return instance;
}

var obj = doNew(SomeType);

У вас есть функция, которая не имеет методов; он просто имеет свойство prototype со списком функций, оператор new означает создание нового объекта и использование этого свойства прототипа функции (Object.create) и constructor как инициализатор.

Pros

  • производительным
  • Цепочка прототипа позволит вам узнать, наследует ли объект от какого-либо типа

Против

  • Двушаговое наследование

Наследование

function SubType() {
    // Step 1, exactly as Variation 1
    // This inherits the non-function properties
    SomeType.call(this);
    this.otherValue = 'Hi';
}

// Step 2, this inherits the methods
SubType.prototype = Object.create(SomeType.prototype);
SubType.prototype.otherMethod = function() {
    return this._convention + this.publ + this.otherValue;
};

var child = new SubType();

Вы можете подумать, что это похоже на супер-набор вариации 2... и вы будете правы. Это похоже на вариант 2, но с функцией инициализации (конструктор);

child instanceof SubType и child instanceof SomeType вернут оба true

Любопытство: под капотом instanceof оператор имеет

function isInstanceOf(obj, Type) {
    return Type.prototype.isPrototypeOf(obj);
}

Вариант 4 - Перезаписать __proto__

Когда вы делаете Object.create(obj) под капотом, он делает

function fakeCreate(obj) {
    var child = {};
    child.__proto__ = obj;
    return child;
}

var child = fakeCreate(obj);

Свойство __proto__ изменяет непосредственно свойство скрытого объекта [Prototype]. Поскольку это может нарушить поведение JavaScript, оно не является стандартным. И стандартный способ является предпочтительным (Object.create).

Pros

  • Быстрый и эффективный

Против

  • Нестандартные
  • Опасные; вы не можете иметь hashmap, поскольку ключ __proto__ может изменить прототип объекта

Наследование

var child = { __proto__: obj };
obj.isPrototypeOf(child); // true

Комментировать вопросы

1. var1: что происходит в SomeType.call(это)? Специальная функция "вызов"?

О, да, функции - это объекты, поэтому у них есть методы, я упомянул три: . call(), . apply() и . bind()

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

var obj = {
    test: function(arg1, arg2) {
        console.log(this);
        console.log(arg1);
        console.log(arg2);
    }
};

// These two ways to invoke the function are equivalent

obj.test('hi', 'lol');

// If we call fn('hi', 'lol') it will receive "window" as "this" so we have to use call.
var fn = obj.test;
fn.call(obj, 'hi', 'lol');

Итак, когда мы делаем SomeType.call(this), мы передаем объект this в функцию SomeCall, поскольку вы помните, что эта функция будет добавлять методы к объекту this.

2. var3: С вашими "ДЕЙСТВИТЕЛЬНО определите свойства" вы имеете в виду, если я использую их в функциях? Это конвенция? Поскольку получение this.newProperty без его определения на одном уровне с другими функциями-членами не является проблемой.

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

3. Var3: что произойдет, если я не назначу конструктор?

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

function A() { }

// When you create a function automatically, JS does this:
// A.prototype = { constructor: A };

A.prototype.someMethod = function() {
    console.log(this.constructor === A); // true
    this.constructor.staticMethod();
    return new this.constructor();  
};

A.staticMethod = function() { };

Это не лучшая практика, потому что не все об этом знают, но иногда это помогает. Но если вы переназначите прототип...

A.prototype = {
    someMethod = function() {
        console.log(this.constructor === A); // false
        console.log(this.constructor === Object); // true
        this.constructor.staticMethod();
        return new this.constructor();  
    }
};

A.prototype - это новый объект, экземпляр Object, чем прототипы Object.prototype и Object.prototype.constructor - Object. Смущает, не так ли?: P

Итак, если вы перезапишите прототип и не reset свойство "конструктор", оно будет ссылаться на Object вместо A, и если вы попытаетесь использовать свойство "конструктор" для доступа к некоторым статическим вы можете сходить с ума.

Ответ 2

Я обычно соглашаюсь с возвратом объекта с функциями как свойствами:

var newCat = function (name) {
return {name: name, purr: function () {alert(name + ' purrs')}};
};

var myCat = newCat('Felix');
myCat.name; // 'Felix'
myCat.purr(); // alert fires

У вас есть наследование, вызывая функцию newCat и расширяя объект, который вы получаете:

var newLion = function (name) {
    var lion = newCat(name);
    lion.roar = function () {
        alert(name + ' roar loudly');
    }
    return lion;
}

Если вы хотите глобальный объект кошек:

var cats = (function () {

var newCat = function (name) {
    return {
        name: name,
        purr: function () {
            alert(name + ' is purring')
        }
    };
};

return {
    newCat: newCat
};
}());

Теперь вы можете позвонить:

var mySecondCat = cats.newCat('Alice');