ดูเหมือนว่าชื่อแทนฟังก์ชัน JavaScript จะไม่ทำงาน


85

ฉันเพิ่งอ่านคำถามนี้และต้องการลองใช้วิธีนามแฝงมากกว่าวิธีการห่อหุ้มฟังก์ชัน แต่ดูเหมือนว่าฉันจะไม่สามารถใช้งานได้ทั้งใน Firefox 3 หรือ 3.5beta4 หรือ Google Chrome ทั้งในหน้าต่างแก้ไขข้อบกพร่องและ ในหน้าเว็บทดสอบ

Firebug:

>>> window.myAlias = document.getElementById
function()
>>> myAlias('item1')
>>> window.myAlias('item1')
>>> document.getElementById('item1')
<div id="item1">

หากฉันวางไว้ในหน้าเว็บการเรียกใช้ myAlias ​​ทำให้ฉันเกิดข้อผิดพลาดนี้:

uncaught exception: [Exception... "Illegal operation on WrappedNative prototype object" nsresult: "0x8057000c (NS_ERROR_XPC_BAD_OP_ON_WN_PROTO)" location: "JS frame :: file:///[...snip...]/test.html :: <TOP_LEVEL> :: line 7" data: no]

Chrome (ที่แทรก >>> เพื่อความชัดเจน):

>>> window.myAlias = document.getElementById
function getElementById() { [native code] }
>>> window.myAlias('item1')
TypeError: Illegal invocation
>>> document.getElementById('item1')
<div id=?"item1">?

และในหน้าทดสอบฉันได้รับข้อความ "การร้องขอที่ผิดกฎหมาย" เหมือนกัน

ฉันทำอะไรผิดหรือเปล่า? ใครสามารถทำซ้ำสิ่งนี้ได้หรือไม่?

นอกจากนี้ฉันเพิ่งลองและใช้งานได้ใน IE8


1
ใน IE8 - อาจเป็นฟังก์ชันเวทย์มนตร์หรืออะไรบางอย่าง
Maciej Łebkowski

ฉันได้เพิ่มความคิดเห็นให้กับคำตอบที่ไม่ถูกต้องที่stackoverflow.com/questions/954417และนำไปที่นี่
Grant Wagner

1
สำหรับเบราว์เซอร์ที่รองรับเมธอด Function.prototype.bind: window.myAlias ​​= document.getElementById.bind (document); ค้นหาเอกสาร MDN จะมีการผูก shim
Ben Fleming

คำตอบ:


39

คุณต้องผูกวิธีนั้นกับวัตถุเอกสาร ดู:

>>> $ = document.getElementById
getElementById()
>>> $('bn_home')
[Exception... "Cannot modify properties of a WrappedNative" ... anonymous :: line 72 data: no]
>>> $.call(document, 'bn_home')
<body id="bn_home" onload="init();">

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

function makeAlias(object, name) {
    var fn = object ? object[name] : null;
    if (typeof fn == 'undefined') return function () {}
    return function () {
        return fn.apply(object, arguments)
    }
}
$ = makeAlias(document, 'getElementById');

>>> $('bn_home')
<body id="bn_home" onload="init();">

ด้วยวิธีนี้คุณจะไม่สูญเสียการอ้างอิงไปยังวัตถุดั้งเดิม

ในปี 2012 มีbindวิธีการใหม่จาก ES5 ที่ช่วยให้เราทำสิ่งนี้ได้ดีขึ้น:

>>> $ = document.getElementById.bind(document)
>>> $('bn_home')
<body id="bn_home" onload="init();">

4
ซึ่งเป็น wrapper จริงๆเพราะส่งคืนฟังก์ชันใหม่ ฉันคิดว่าคำตอบสำหรับคำถามของ Kev คือเป็นไปไม่ได้ด้วยเหตุผลที่คุณอธิบายไว้ข้างต้น วิธีที่คุณอธิบายที่นี่น่าจะเป็นตัวเลือกที่ดีที่สุด
jiggy

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

188

ฉันขุดลึกเพื่อทำความเข้าใจพฤติกรรมเฉพาะนี้และฉันคิดว่าฉันได้พบคำอธิบายที่ดี

ก่อนที่ฉันจะอธิบายถึงสาเหตุที่คุณไม่สามารถใช้นามแฝงdocument.getElementByIdได้ฉันจะพยายามอธิบายว่าฟังก์ชัน / วัตถุ JavaScript ทำงานอย่างไร

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

พิจารณาฟังก์ชันต่อไปนี้:

function sum(a, b)
{
    return a + b;
}

sum(10, 20); // returns 30;

ฟังก์ชันนี้ถูกประกาศในขอบเขตของหน้าต่างและเมื่อคุณเรียกใช้ค่าthisภายในฟังก์ชัน sum จะเป็นโกลบอลWindowอ็อบเจ็กต์

สำหรับฟังก์ชัน 'sum' ไม่สำคัญว่าค่าของ 'this' จะเป็นเท่าใดเนื่องจากไม่ได้ใช้งาน


พิจารณาฟังก์ชันต่อไปนี้:

function Person(birthDate)
{
    this.birthDate = birthDate;    
    this.getAge = function() { return new Date().getFullYear() - this.birthDate.getFullYear(); };
}

var dave = new Person(new Date(1909, 1, 1)); 
dave.getAge(); //returns 100.

เมื่อคุณเรียกใช้ฟังก์ชัน dave.getAge, ล่าม JavaScript เห็นว่าคุณกำลังเรียกใช้ฟังก์ชัน getAge บนdaveวัตถุจึงกำหนดthisไปdaveและเรียกgetAgeฟังก์ชั่น ได้อย่างถูกต้องจะกลับมาgetAge()100


คุณอาจทราบว่าใน JavaScript คุณสามารถระบุขอบเขตโดยใช้applyวิธีการ ลองดูสิ

var dave = new Person(new Date(1909, 1, 1)); //Age 100 in 2009
var bob = new Person(new Date(1809, 1, 1)); //Age 200 in 2009

dave.getAge.apply(bob); //returns 200.

ในบรรทัดด้านบนแทนที่จะปล่อยให้ JavaScript ตัดสินใจขอบเขตคุณกำลังส่งผ่านขอบเขตด้วยตนเองเป็นbobออบเจ็กต์ getAgeตอนนี้จะกลับมา200แม้ว่าคุณจะ 'คิดว่า' เรียกร้องgetAgeสิ่งdaveนั้นก็ตาม


ประเด็นทั้งหมดข้างต้นคืออะไร? ฟังก์ชั่นถูกแนบ 'อย่างหลวม ๆ ' กับวัตถุ JavaScript ของคุณ เช่นคุณสามารถทำได้

var dave = new Person(new Date(1909, 1, 1));
var bob = new Person(new Date(1809, 1, 1));

bob.getAge = function() { return -1; };

bob.getAge(); //returns -1
dave.getAge(); //returns 100

มาทำขั้นตอนต่อไป

var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;

dave.getAge(); //returns 100;
ageMethod(); //returns ?????

ageMethodการดำเนินการเกิดข้อผิดพลาด! เกิดอะไรขึ้น?

หากคุณอ่านประเด็นข้างต้นของฉันอย่างละเอียดคุณจะทราบว่าdave.getAgeเมธอดถูกเรียกด้วยdaveas thisobject ในขณะที่ JavaScript ไม่สามารถกำหนด 'ขอบเขต' สำหรับageMethodการดำเนินการได้ ดังนั้นมันจึงผ่าน 'Window' ทั่วโลกเป็น 'this' ขณะนี้windowไม่มีbirthDateคุณสมบัติageMethodการดำเนินการจะล้มเหลว

จะแก้ไขได้อย่างไร? เรียบง่าย

ageMethod.apply(dave); //returns 100.

ทั้งหมดข้างต้นสมเหตุสมผลหรือไม่? หากเป็นเช่นนั้นคุณจะสามารถอธิบายได้ว่าเหตุใดคุณจึงไม่สามารถใช้นามแฝงได้document.getElementById:

var $ = document.getElementById;

$('someElement'); 

$ถูกเรียกด้วยwindowas thisและหากgetElementByIdคาดว่าthisจะใช้documentงานได้ก็จะล้มเหลว

อีกครั้งเพื่อแก้ไขปัญหานี้คุณสามารถทำได้

$.apply(document, ['someElement']);

เหตุใดจึงใช้งานได้ใน Internet Explorer

ผมไม่ทราบว่าการดำเนินการภายในของgetElementByIdใน IE แต่ความคิดเห็นในแหล่ง jQuery ( inArrayวิธีการดำเนินการ) window == documentกล่าวว่าใน หากเป็นเช่นนั้นนามแฝงdocument.getElementByIdควรใช้งานได้ใน IE

เพื่ออธิบายเพิ่มเติมฉันได้สร้างตัวอย่างที่ซับซ้อน ดูPersonฟังก์ชั่นด้านล่าง

function Person(birthDate)
{
    var self = this;

    this.birthDate = birthDate;

    this.getAge = function()
    {
        //Let's make sure that getAge method was invoked 
        //with an object which was constructed from our Person function.
        if(this.constructor == Person)
            return new Date().getFullYear() - this.birthDate.getFullYear();
        else
            return -1;
    };

    //Smarter version of getAge function, it will always refer to the object
    //it was created with.
    this.getAgeSmarter = function()
    {
        return self.getAge();
    };

    //Smartest version of getAge function.
    //It will try to use the most appropriate scope.
    this.getAgeSmartest = function()
    {
        var scope = this.constructor == Person ? this : self;
        return scope.getAge();
    };

}

สำหรับPersonฟังก์ชันข้างต้นนี่คือวิธีการgetAgeทำงานของเมธอดต่างๆ

มาสร้างวัตถุสองชิ้นโดยใช้Personฟังก์ชัน

var yogi = new Person(new Date(1909, 1,1)); //Age is 100
var anotherYogi = new Person(new Date(1809, 1, 1)); //Age is 200

console.log(yogi.getAge()); //Output: 100.

ตรงไปตรงมา, วิธีการ getAge ได้รับyogiวัตถุและเอาท์พุทthis100


var ageAlias = yogi.getAge;
console.log(ageAlias()); //Output: -1

JavaScript ชุด interepreter windowวัตถุเป็นthisของเราและวิธีการที่จะกลับมาgetAge-1


console.log(ageAlias.apply(yogi)); //Output: 100

หากเรากำหนดขอบเขตที่ถูกต้องคุณสามารถใช้ageAliasวิธีการ


console.log(ageAlias.apply(anotherYogi)); //Output: 200

ถ้าเราผ่านวัตถุของคนอื่นมันจะยังคำนวณอายุได้อย่างถูกต้อง

var ageSmarterAlias = yogi.getAgeSmarter;    
console.log(ageSmarterAlias()); //Output: 100

ageSmarterฟังก์ชั่นบันทึกเดิมthisวัตถุดังนั้นตอนนี้คุณไม่ต้องกังวลเกี่ยวกับการจัดหาขอบเขตที่ถูกต้อง


console.log(ageSmarterAlias.apply(anotherYogi)); //Output: 100 !!!

ปัญหาageSmarterคือคุณไม่สามารถกำหนดขอบเขตให้กับวัตถุอื่นได้


var ageSmartestAlias = yogi.getAgeSmartest;
console.log(ageSmartestAlias()); //Output: 100
console.log(ageSmartestAlias.apply(document)); //Output: 100

ageSmartestฟังก์ชั่นจะใช้ขอบเขตเดิมถ้าขอบเขตที่ไม่ถูกต้องเป็นที่จัดทำ


console.log(ageSmartestAlias.apply(anotherYogi)); //Output: 200

คุณจะยังส่งPersonวัตถุอื่นไปยังgetAgeSmartestได้ :)


7
+1 สาเหตุที่ IE ใช้งานได้ ส่วนที่เหลือเป็นเรื่องเล็กน้อย แต่ยังคงเป็นประโยชน์โดยทั่วไป :)
เคฟ

44
คำตอบที่ยอดเยี่ยม ฉันไม่เห็นว่ามีอะไรนอกหัวข้อเลย
Justin Johnson

คำตอบที่ดีมากแน่นอน รุ่นที่ค่อนข้างสั้นที่เขียนนี่
Marcel Korpel

8
หนึ่งในคำตอบที่ดีที่สุดที่ฉันเคยเห็นใน stackoverflow
warbaker

ทำไมมันถึงทำงานใน IE? เพราะว่า: stackoverflow.com/questions/6994139/… - ดังนั้นในความเป็นจริงการใช้งานวิธีนี้อาจเป็น {return window [id]} ดังนั้นจึงใช้ได้ในทุกบริบท
Maciej Łebkowski

3

นี่คือคำตอบสั้น ๆ

ต่อไปนี้ทำสำเนา (การอ้างอิงถึง) ฟังก์ชัน ปัญหาคือตอนนี้ฟังก์ชั่นอยู่บนwindowวัตถุเมื่อมันถูกออกแบบมาให้อยู่บนdocumentวัตถุ

window.myAlias = document.getElementById

ทางเลือกคือ

  • ใช้กระดาษห่อหุ้ม (กล่าวถึงโดย Fabien Ménager)
  • หรือคุณสามารถใช้สองนามแฝง

    window.d = document // A renamed reference to the object
    window.d.myAlias = window.d.getElementById
    

2

อีกคำตอบสั้น ๆ สำหรับการห่อ / นามแฝงconsole.logและวิธีการบันทึกที่คล้ายกัน พวกเขาทั้งหมดคาดหวังว่าจะอยู่ในconsoleบริบท

นี้สามารถใช้ได้เมื่อตัดconsole.logกับ fallbacks บางส่วนในกรณีที่คุณหรือผู้ใช้ของคุณจะประสบปัญหาเมื่อใช้เบราว์เซอร์ที่ไม่ได้เป็น (เสมอ) สนับสนุนมัน นี่ไม่ใช่วิธีแก้ปัญหาอย่างสมบูรณ์เนื่องจากจำเป็นต้องได้รับการขยายการตรวจสอบและการสำรอง - ระยะทางของคุณอาจแตกต่างกันไป

ตัวอย่างการใช้คำเตือน

var warn = function(){ console.warn.apply(console, arguments); }

จากนั้นใช้งานได้ตามปกติ

warn("I need to debug a number and an object", 9999, { "user" : "Joel" });

หากคุณต้องการเห็นอาร์กิวเมนต์การบันทึกของคุณรวมอยู่ในอาร์เรย์ (ฉันทำโดยส่วนใหญ่แล้ว) ให้แทนที่.apply(...)ด้วย.call(...).

ควรทำงานร่วมกับconsole.log(), console.debug(), console.info(), ,console.warn() console.error()ดูเพิ่มเติมconsoleได้ที่ MDN


1

นอกจากนี้คำตอบที่ดีอื่น ๆ มีวิธีง่ายๆ jQuery $ .proxy

คุณสามารถใช้นามแฝงดังนี้:

myAlias = $.proxy(document, 'getElementById');

หรือ

myAlias = $.proxy(document.getElementById, document);

+1 เนื่องจากเป็นวิธีแก้ปัญหาที่เป็นไปได้ แต่พูดอย่างเคร่งครัดเบื้องหลัง$.proxyยังเป็นกระดาษห่อหุ้มจริงๆ
pimvdb

-6

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

>>> document.s = document.getElementById;
>>> document.s('myid');
<div id="myid">

5
สิ่งนี้ไม่ถูกต้องเล็กน้อย คุณสามารถ 'นามแฝงแท้' ซึ่งเป็นฟังก์ชันได้หากการใช้งานฟังก์ชันใช้ 'this' ดังนั้นมันขึ้นอยู่กับ
SolutionYogi

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