ฟังก์ชั่นไม่ระบุชื่อตนเองที่ดำเนินการเทียบกับต้นแบบ


26

ใน Javascript มีเทคนิคที่เด่นชัดบางประการสำหรับการสร้างและจัดการคลาส / เนมสเปซในจาวาสคริปต์

ฉันอยากรู้ว่าสถานการณ์ใดที่รับประกันโดยใช้เทคนิคหนึ่งกับอีกเทคนิคหนึ่ง ฉันต้องการที่จะเลือกและติดกับมันก้าวไปข้างหน้า

ฉันเขียนรหัสองค์กรที่ได้รับการบำรุงรักษาและใช้งานร่วมกันในหลาย ๆ ทีมและฉันต้องการที่จะรู้ว่าวิธีที่ดีที่สุดในการเขียนจาวาสคริปต์คืออะไร?

ฉันมักจะชอบฟังก์ชั่นนิรนามที่ดำเนินการเอง แต่ฉันอยากรู้ว่าชุมชนโหวตให้กับเทคนิคเหล่านี้อย่างไร

ต้นแบบ:

function obj()
{
}

obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

ฟังก์ชั่นไม่เปิดเผยตัวตนปิดตัวเอง:

//Self-Executing Anonymous Function 
(function( skillet, $, undefined ) {
    //Private Property
    var isHot = true;

    //Public Property
    skillet.ingredient = "Bacon Strips";

    //Public Method
    skillet.fry = function() {
        var oliveOil;

        addItem( "\t\n Butter \n\t" );
        addItem( oliveOil );
        console.log( "Frying " + skillet.ingredient );
    };

    //Private Method
    function addItem( item ) {
        if ( item !== undefined ) {
            console.log( "Adding " + $.trim(item) );
        }
    }     
}( window.skillet = window.skillet || {}, jQuery ));   
//Public Properties      
console.log( skillet.ingredient ); //Bacon Strips  

//Public Methods 
skillet.fry(); //Adding Butter & Fraying Bacon Strips 

//Adding a Public Property 
skillet.quantity = "12"; console.log( skillet.quantity ); //12   

//Adding New Functionality to the Skillet 
(function( skillet, $, undefined ) {
    //Private Property
    var amountOfGrease = "1 Cup";

    //Public Method
    skillet.toString = function() {
        console.log( skillet.quantity + " " + 
                     skillet.ingredient + " & " + 
                     amountOfGrease + " of Grease" );
        console.log( isHot ? "Hot" : "Cold" );
     };     

}( window.skillet = window.skillet || {}, jQuery ));
//end of skillet definition


try {
    //12 Bacon Strips & 1 Cup of Grease
    skillet.toString(); //Throws Exception 
} catch( e ) {
    console.log( e.message ); //isHot is not defined
}

ฉันรู้สึกว่าฉันควรพูดถึงว่าฟังก์ชั่นไม่เปิดเผยตัวตนเป็นรูปแบบที่ทีม jQuery ใช้

อัปเดต เมื่อฉันถามคำถามนี้ฉันไม่ได้เห็นความสำคัญของสิ่งที่ฉันพยายามเข้าใจ ปัญหาที่แท้จริงอยู่ที่ว่าจะใช้ใหม่หรือไม่เพื่อสร้างอินสแตนซ์ของวัตถุของคุณหรือใช้รูปแบบที่ไม่ต้องใช้ตัวสร้าง / การใช้newคำสำคัญ

ฉันเพิ่มคำตอบของตัวเองเพราะในความคิดของฉันเราควรใช้รูปแบบที่ไม่ใช้newคำหลัก

สำหรับข้อมูลเพิ่มเติมโปรดดูคำตอบของฉัน


1
คุณสามารถยกตัวอย่างสั้น ๆ ของเทคนิคทั้งสองที่คุณกำลังอธิบายได้หรือไม่
เฮ้

อย่าดูถูกดูแคลนต้นแบบเพราะตัวอย่างง่ายๆของฉัน
Robotsushi

1
มันไม่ได้ดำเนินการเอง = /
เฮ้

2
ฉันไม่เห็นวงเล็บใด ๆ ที่จะปิดการแสดงออกหรือเรียกว่า ...
เฮ้

1
(+1) ผู้ใช้หลายคนมองข้ามเนมสเปซ
umlcat

คำตอบ:


22

ฟังก์ชั่นที่ไม่ระบุชื่อที่ดำเนินการเองจะใช้ในการดำเนินการสคริปต์โดยอัตโนมัติโดยไม่ต้องเชื่อมต่อกับกิจกรรมภายนอก (เช่น window.onload)

ในตัวอย่างนี้ใช้เพื่อสร้างรูปแบบโมดูลคลาสสิกจุดประสงค์หลักที่จะแนะนำเนมสเปซเข้ากับสภาพแวดล้อมโลกและจัดให้มีการห่อหุ้มสำหรับคุณสมบัติภายในใด ๆ ที่ไม่ใช่ "ส่งออก" หรือแนบกับเนมสเปซ

ในทางกลับกันการดัดแปลงวัตถุต้นแบบนั้นถูกใช้เพื่อสร้างการสืบทอด (หรือขยายแบบดั้งเดิม) รูปแบบนี้ใช้เพื่อสร้างวัตถุ 1: n ด้วยวิธีการหรือคุณสมบัติทั่วไป

คุณไม่ควรเลือกรูปแบบหนึ่งในการตั้งค่าไปที่อื่น ๆ เนื่องจากพวกเขาดำเนินการที่แตกต่างกันงาน ในแง่ของการกำหนดเนมฟังก์ชั่นการดำเนินการด้วยตนเองเป็นตัวเลือกที่เหมาะสม


7
โปรดทราบว่า "ตัวเองการดำเนินงานฟังก์ชั่นที่ไม่ระบุชื่อ" เป็นที่รู้จักกันทั่วไปว่าเป็นทันที-เรียกฟังก์ชั่นการแสดงออก (IIFE)
voithos

ฉันหลีกเลี่ยงพวกเขาและจะซื่อสัตย์ฉันไม่ได้รับความรักกับ IIFE พวกมันยุ่งเหยิงในการดีบักและทำลายรหัสร่างใน Eclipse หากคุณต้องการเนมสเปซติดไว้ที่วัตถุถ้าคุณต้องการเรียกใช้งานมันก็แค่เรียกมันว่าและการห่อหุ้มก็ไม่ได้อะไรเลย
Daniel Sokolowski

4

นี่คือรูปแบบที่ฉันเพิ่งเริ่มใช้ (ใช้รูปแบบของมันจนถึงเมื่อวาน):

function MyClass() {
    // attributes
    var privateVar = null;

    // function implementations
    function myPublicFunction() {
    }

    function myPrivateFunction() {
    }

    // public declarations
    this.myPublicFunction = myPublicFunction;
}

MyClass.prototype = new ParentClass(); // if required

ความคิดบางอย่างเกี่ยวกับเรื่องนี้:

  1. คุณไม่ควรได้รับการ(anonymous)ติดตามใด ๆในการติดตามสแต็คของ debugger เป็นชื่อทุกอย่าง (ไม่มีฟังก์ชั่นที่ไม่ระบุชื่อ)
  2. มันเป็นลวดลายที่สะอาดที่สุดที่ฉันเคยเห็น
  3. คุณสามารถจัดกลุ่ม API ที่เปิดเผยของคุณได้อย่างง่ายดายโดยไม่ต้องมีการติดตั้งใช้งานควบคู่กับการประกาศ

ครั้งเดียวที่ฉันจะใช้prototypeอีกต่อไปจริงๆคือการกำหนดมรดก


5
มีปัญหาเล็กน้อยกับสิ่งนี้ ฟังก์ชั่นวัตถุใหม่จะถูกสร้างขึ้นสำหรับแต่ละ "วิธี" กับแต่ละการเรียกร้องของคอนสตรัค นอกจากนี้การเรียกตัวสร้างเพื่อรับสำเนาของวัตถุต้นแบบสำหรับการสืบทอดเป็น frowned ใช้ Object.create (ParentClass.prototype) หรือ shim สำหรับ Object.create likefunction clone(obj){return this typeof 'clone' ? this : new clone(clone.prototype=obj)}
Hey

@GGG: ใช่คุณถูกต้องกับจุดแรกของคุณ ฉันพูด (และควรได้กล่าวถึงในโพสต์ของฉัน) ว่าแต่ละกรณีการใช้งานเฉพาะของการดำเนินการควรจะคิดผ่าน ปัญหาของฉันเกี่ยวกับprototypeวิธีการตามที่คุณแนะนำคือ (เว้นแต่จะมีวิธีที่ฉันไม่คุ้นเคยซึ่งอาจเป็นกรณีนี้) ที่คุณสูญเสียความสามารถในการห่อหุ้มคุณลักษณะซึ่งหมายถึงทุกสิ่งที่เปิดเป็นสาธารณะ (ซึ่งไม่ใช่จุดจบของโลก เป็นเพียงความชอบส่วนตัว)
Demian Brecht

นอกจากนี้หลังจากดูงานต่อไปนี้ที่ทำบน jsperf ( jsperf.com/object-create-vs-constructor-vs-object-literal/12 ) ฉันจะเพิ่มประสิทธิภาพมากกว่าค่าใช้จ่ายหน่วยความจำสำเนาเพิ่มเติมเกือบทุกวัน (อีกครั้งอัตนัยมากกับกรณีการใช้งานเฉพาะ)
Demian Brecht

ตอนนี้เมื่อฉันพูดทั้งหมดฉันแค่ผ่าน ECMA-262 ดังนั้นอาจมีหลายสิ่งที่ฉันไม่เห็น .. นอกจากนี้ฉันไม่ได้ใช้คำของคร็อคฟอร์ดเป็นพระกิตติคุณ .. ใช่เขาเป็นคนหนึ่ง ของผู้เชี่ยวชาญในสาขานี้ (แน่นอนที่สุด) แต่ก็ไม่ได้ทำให้เขาถูกต้อง 100% ตลอดเวลา มีผู้เชี่ยวชาญคนอื่น ๆ อยู่ที่นั่น (ฉันไม่ได้เป็นหนึ่งในนั้น) ผู้ที่มีความคิดเห็นที่ขัดแย้งกับข้อโต้แย้งที่น่าสนใจ
Demian Brecht

2
คุณอาจจะสนใจในสิ่งนี้ฉันกำลังทำงานอยู่
เฮ้

3

ฉันใช้ต้นแบบเพราะสะอาดกว่าและทำตามรูปแบบการสืบทอดมาตรฐาน ฟังก์ชั่นการเรียกใช้ตัวเองนั้นยอดเยี่ยมสำหรับการพัฒนาเบราว์เซอร์หรือสถานการณ์ที่คุณไม่รู้ว่าโค้ดนั้นกำลังถูกประมวลผลที่ไหน

ตัวอย่าง:

var me;

function MyObject () {
    this.name = "Something";
}

MyObject.prototype.speak = function speak () {
    return "Hello, my name is " + this.name;
};

me = new MyObject();
me.name = "Joshua";
alert(me.speak());

1
ฟังก์ชั่นที่ไม่ระบุชื่อดำเนินการด้วยตนเองมีประโยชน์มากสำหรับการอนุญาตให้คุณมีฟังก์ชั่นส่วนตัวที่เข้าถึงได้ในชั้นเรียน
Zee

1

ฉันจะไปกับฟังก์ชั่นการทำงานด้วยตัวเอง แต่มีความแตกต่างเล็กน้อย

MyClass = (function() {
     var methodOne = function () {};
     var methodTwo = function () {};
     var privateProperty = "private";
     var publicProperty = "public";

     return function MyClass() {
         this.methodOne = methodOne;
         this.methodTwo = methodTwo;
         this.publicProperty = publicProperty;
     };
})();

หากพบว่าวิธีนี้สะอาดกว่ามากฉันแยกตัวแปรโกลบอลที่ส่งคืนจากพารามิเตอร์อินพุตใด ๆ (เช่น jQuery) (วิธีที่คุณเขียนจะเท่ากับโมฆะที่ส่งคืนและใช้พารามิเตอร์ ref ใน C # ซึ่งฉันหาบิตหรือ ผ่านในตัวชี้ไปยังตัวชี้และกำหนดใหม่ใน C ++) ถ้าฉันจะแนบวิธีการหรือคุณสมบัติเพิ่มเติมกับคลาสฉันจะใช้การสืบทอดต้นแบบ (ตัวอย่างด้วยวิธี $ .extend ของ jQuery แต่ง่ายพอที่จะม้วนส่วนขยายของคุณเอง):

var additionalClassMethods = (function () {
    var additionalMethod = function () { alert('Test Method'); };
    return { additionalMethod: additionalMethod };
})();

$.extend(MyClass.prototype, additionalClassMethods);

var m = new MyClass();
m.additionalMethod(); // Pops out "Test Method"

วิธีนี้ทำให้คุณมีความแตกต่างที่ชัดเจนระหว่างวิธีการเพิ่มเติมและวิธีดั้งเดิม


1
ฉันเป็นคนเดียวที่คิดว่าใช้ NFE เช่นนี้เป็นความคิดที่ไม่ดีหรือไม่?
เฮ้

1

ตัวอย่างสด

(function _anonymouswrapper(undefined) {

    var Skillet = {
        constructor: function (options) {
            options && extend(this, options);
            return this; 
        },
        ingredient: "Bacon Strips",
        _isHot: true,
        fry: function fry(oliveOil) {
            this._addItem("\t\n Butter \n\t");
            this._addItem(oliveOil);
            this._addItem(this.ingredient);
            console.log("Frying " + this.ingredient);
        },
        _addItem: function addItem(item) {
            console.log("Adding " + item.toString().trim());
        }
    };

    var skillet = Object.create(Skillet).constructor();

    console.log(skillet.ingredient);
    skillet.fry("olive oil");

    var PrintableSkillet = extend(Object.create(Skillet), {
        constructor: function constructor(options) {
            options && extend(this, options);
            return this;
        },
        _amountOfGrease: "1 Cup",
        quantity: 12,
        toString: function toString() {
            console.log(this.quantity + " " +
                        this.ingredient + " & " +
                        this._amountOfGrease + " of Grease");
            console.log(this._isHot ? "Hot" : "Cold");
        }
    });

    var skillet = Object.create(PrintableSkillet).constructor();

    skillet.toString();

    function extend(target, source) {
        Object.getOwnPropertyNames(source).forEach(function (name) {
            var pd = Object.getOwnPropertyDescriptor(source, name);
            Object.defineProperty(target, name, pd);
        });
        return target;
    }
}());

คุณสามารถใช้ IIFE เพื่อเลียนแบบ "ขอบเขตโมดูล" รอบรหัสของคุณ จากนั้นคุณสามารถใช้วัตถุได้ตามปกติ

อย่า "เลียนแบบ" สถานะส่วนตัวโดยใช้การปิดที่มีการลงโทษหน่วยความจำขนาดใหญ่

หากคุณเขียนแอปพลิเคชันระดับองค์กรและต้องการให้การใช้งานหน่วยความจำของคุณต่ำกว่า 1GB หลีกเลี่ยงการใช้การปิดเพื่อเก็บสถานะโดยไม่จำเป็น


คุณมีคู่ที่พิมพ์ผิดในคู่รหัส ฉันไม่เห็นด้วยกับการที่คุณยืนยันว่าการปิดโดยอัตโนมัติทำให้เกิดการใช้หน่วยความจำมากเกินไปฉันคิดว่ามันขึ้นอยู่กับว่าคุณใช้งานหนักแค่ไหนและคุณจัดการกับปัญหาการกำหนดขอบเขตได้ดีเพียงใด ตัวอย่างเช่นไม่ดี (การใช้งานโกลบอลคอนโซลเป็นตัวอย่างที่ดีของโค้ดข้างต้นจะมีประสิทธิภาพมากขึ้นถ้าคุณส่งผ่านคอนโซลเป็นตัวแปร)
Ed James

@EdWoodcock ฉันคิดว่ารหัสเป็นรถม้าเพียง refactored และแก้ไขมัน
Raynos

@EdWoodcock ความแตกต่างของประสิทธิภาพระหว่างท้องถิ่นconsoleและทั่วโลกconsoleเป็นการเพิ่มประสิทธิภาพขนาดเล็ก มันคุ้มค่าที่จะทำเพื่อให้คุณสามารถย่อให้เล็กลงconsoleแต่นั่นเป็นเรื่องที่แตกต่าง
Raynos

@EdWoodcock closures ช้าหากวัตถุที่คุณทำซ้ำอยู่ สิ่งที่ช้าคือการสร้างฟังก์ชั่นภายในฟังก์ชั่นเมื่อไม่ต้องการ ปิดยังมีค่าใช้จ่ายในหน่วยความจำขนาดเล็กสำหรับการจัดเก็บของรัฐในการเปรียบเทียบกับการจัดเก็บของรัฐเกี่ยวกับวัตถุโดยตรง
Raynos

ใช่แค่ต้องการชี้ให้เห็นเพราะคำตอบของคุณเป็นวิธีที่สมเหตุสมผลในการทำสิ่งต่าง ๆ (แม้ว่าไม่ใช่วิธีที่ฉันเลือกใช้) (ฉันได้ทำการแก้ไขแล้ว แต่ฉันยังไม่แน่ใจเกี่ยวกับมารยาทของสิ่งเหล่านั้นในไซต์ SE นี้โดยเฉพาะ)
Ed James

0

อัปเดต ตอนนี้ฉันเข้าใจ JavaScript ได้ดีขึ้นและรู้สึกว่าฉันสามารถตอบคำถามได้อย่างถูกต้อง ฉันคิดว่ามันเป็นหัวข้อที่พูดไม่ดี แต่สำคัญมากสำหรับการจัดการกับจาวาสคริปต์

รูปแบบฟังก์ชั่นไม่ระบุชื่อที่ดำเนินการด้วยตนเองไม่ใช่สิ่งที่ต้องใช้คำหลักใหม่หากคุณหลีกเลี่ยงการใช้ฟังก์ชันนี้นอก ฉันเห็นด้วยกับความคิดที่ว่าการใช้ใหม่เป็นเทคนิคแบบเก่าและเราควรพยายามใช้รูปแบบที่หลีกเลี่ยงการใช้สิ่งใหม่

ฟังก์ชั่นไม่ระบุชื่อดำเนินการด้วยตนเองตรงตามเกณฑ์นี้

คำตอบสำหรับคำถามนี้เป็นอัตนัยเพราะมีรูปแบบการเข้ารหัสจำนวนมากในจาวาสคริปต์ อย่างไรก็ตามจากการวิจัยและประสบการณ์ของฉันฉันขอแนะนำให้เลือกใช้ Self Executing Function เพื่อกำหนด API ของคุณและหลีกเลี่ยงการใช้งานใหม่ทุกครั้งที่ทำได้


1
คำหลักใหม่มีอะไรไม่ดี ฉันอยากรู้.
พันธมิตร

0

นี่คือวิธีที่ฉันจะไปเกี่ยวกับเรื่องนี้IIFE SelfExecutingFunctionเพื่อขยายMyclassด้วยMyclass.anotherFunction();

MyClass = (function() {
     var methodOne = function () {};
     var methodTwo = function () {};
     var privateProperty = "private";
     var publicProperty = "public";

    //Returning the anonymous object {} all the methods and properties are reflected in Myclass constructor that you want public those no included became hidden through closure; 
     return {
         methodOne = methodOne;
         methodTwo = methodTwo;
         publicProperty = publicProperty;
     };
})();

@@@@@@@@@@@@@@@@@@@@@@@@
//then define another IIFE SEF to add var function=anothermethod(){};
(function(obj){
return obj.anotherfunction=function(){console.log("Added to Myclass.anotherfunction");};
})(Myclass);

//the last bit : (function(obj){})(Myclass); obj === Myclass obj is an alias to pass the Myclass to IIFE

var myclass = new Myclass();
myclass.anotherfunction(); //"Added to Myclass.anotherfunction"

1
โปรแกรมเมอร์คือคำถามคำตอบเกี่ยวกับทัวร์แนวคิดที่คาดว่าจะอธิบายสิ่งต่าง ๆ การขว้างปาโค้ดทิ้งแทนที่จะอธิบายเป็นเหมือนการคัดลอกโค้ดจาก IDE ไปยังไวท์บอร์ด: มันอาจดูคุ้นหูและบางครั้งก็เข้าใจได้ แต่มันให้ความรู้สึกแปลก ๆ ... มันแปลก ๆ กระดานไวท์บอร์ดไม่มีคอมไพเลอร์
ริ้น
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.