ฉันจะกำหนดตัวแปรทั่วโลกใน CoffeeScript ได้อย่างไร


317

บน Coffeescript.org:

bawbag = (x, y) ->
    z = (x * y)

bawbag(5, 10) 

จะรวบรวมเพื่อ:

var bawbag;
bawbag = function(x, y) {
  var z;
  return (z = (x * y));
};
bawbag(5, 10);

รวบรวมผ่าน coffee-script ภายใต้ node.js ล้อมรอบด้วย:

(function() {
  var bawbag;
  bawbag = function(x, y) {
    var z;
    return (z = (x * y));
  };
  bawbag(5, 10);
}).call(this);

เอกสารพูดว่า:

หากคุณต้องการสร้างตัวแปรระดับบนสุดเพื่อให้สคริปต์อื่นใช้ให้แนบไฟล์เหล่านั้นเป็นคุณสมบัติในหน้าต่างหรือบนวัตถุเอ็กซ์ปอร์ตใน CommonJS ตัวดำเนินการที่มีอยู่ (ด้านล่าง) ให้วิธีที่เชื่อถือได้ในการกำหนดตำแหน่งที่จะเพิ่มหากคุณกำหนดเป้าหมายทั้ง CommonJS และเบราว์เซอร์: root = export? นี้

ฉันจะกำหนดตัวแปรส่วนกลางได้อย่างไรใน CoffeeScript 'แนบไฟล์เหล่านี้เป็นคุณสมบัติบนหน้าต่าง' หมายถึงอะไร


4
ทำทราบว่าการใช้ตัวแปรทั่วโลกไม่ดีc2.com/cgi/wiki?GlobalVariablesAreBadและพิจารณาถึงอันตรายc2.com/cgi/wiki?GotoConsideredHarmful และไม่มีเหตุผลที่จะใช้มันใน JavaScript เลยเพราะคุณมีคุณสมบัติที่ยอดเยี่ยมเช่นการปิดที่สามารถแก้ปัญหาส่วนใหญ่ที่คุณกำลังใช้ตัวแปรทั่วโลกเพื่อแก้ปัญหาได้
Evgeny

9
@Evgeny ในขณะที่ฉันเห็นด้วยกับคุณที่นี่ในบางกรณีจำเป็นต้องสร้างออบเจ็กต์ 'แอป' กลางและมีโมดูลแนบอยู่
jackyalcine

1
วัตถุกลางสามารถบันทึกลงในวัตถุสถานะส่วนกลางที่มีอยู่เช่นwindowวัตถุหรือexportsวัตถุ ไม่จำเป็นต้องสร้างตัวแปรทั่วโลก
Evgeny

9
@Evgeny ตัวแปรทั่วโลกจะถูกบันทึกเป็นคุณสมบัติของwindow(หรือglobalใน nodejs) วัตถุ
shesek

21
ใช่มันก็ไม่ได้เลวร้ายที่จะมีความเป็นสากล การฝึกฝนที่ไม่ดีเพียงแค่ยึดแอปของคุณลงกับพวกเขาโดยไม่ตั้งใจ การประกาศอย่างใดอย่างหนึ่งและการใช้สิ่งนั้นในฐานะที่เป็นโรงงานอะแดปเตอร์เช่น jQuery หรือเนมสเปซบางชนิดเป็นวิธีปฏิบัติทั่วไป
Erik Reppen

คำตอบ:


419

ตั้งแต่สคริปต์กาแฟไม่มีvarคำสั่งโดยอัตโนมัติแทรกสำหรับตัวแปรทั้งหมดในกาแฟสคริปต์ว่าวิธีที่จะป้องกันไม่ให้รุ่น JavaScript รวบรวมจากการรั่วไหลทุกอย่างลงในnamespace โลก

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

แนบเป็นคุณสมบัติในหน้าต่าง

ซึ่งหมายความว่าคุณจะต้องทำสิ่งที่ชอบwindow.foo = 'baz';ซึ่งจัดการกรณีที่เบราว์เซอร์เนื่องจากมีวัตถุทั่วโลกwindowเป็น

Node.js

ใน Node.js ไม่มีwindowวัตถุ แต่มีexportsวัตถุที่ถูกส่งผ่านเข้าไปใน wrapper ที่ล้อมโมดูล Node.js (ดูที่: https://github.com/ry/node/blob/master/src/node.js# L321 ) ดังนั้นใน Node.js exports.foo = 'baz';สิ่งที่คุณจะต้องทำคือ

ตอนนี้ให้เราดูสิ่งที่ระบุไว้ในใบเสนอราคาของคุณจากเอกสาร:

... กำหนดเป้าหมายทั้ง CommonJS และเบราว์เซอร์: root = export หรือไม่ นี้

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

var root;
root = (typeof exports !== "undefined" && exports !== null) ? exports : this;

ก่อนอื่นจะตรวจสอบว่าexportsมีการกำหนดหรือไม่เนื่องจากพยายามอ้างอิงตัวแปรที่ไม่มีอยู่ใน JavaScript มิฉะนั้นจะส่งผลให้เกิด SyntaxError (ยกเว้นเมื่อใช้กับtypeof)

ดังนั้นถ้าexportsมีอยู่แล้วซึ่งในกรณี Node.js (หรือในเว็บไซต์ที่เขียนไม่ดี ... ) รากจะชี้ไปมิฉะนั้นจะexports thisแล้วอะไรthisล่ะ

(function() {...}).call(this);

การใช้.callฟังก์ชั่นจะผูกthisไว้ข้างในฟังก์ชั่นกับพารามิเตอร์แรกที่ผ่านไปในกรณีที่เบราว์เซอร์thisจะเป็นwindowวัตถุในกรณีของ Node.js มันจะเป็นบริบททั่วโลกซึ่งยังเป็นglobalวัตถุ

แต่เนื่องจากคุณมีrequireฟังก์ชั่นใน Node.js จึงไม่จำเป็นต้องกำหนดบางสิ่งให้กับglobalวัตถุใน Node.js แทนคุณจึงกำหนดให้กับexportsวัตถุที่ได้รับคืนโดยrequireฟังก์ชัน

กาแฟสคริปต์

หลังจากคำอธิบายทั้งหมดนี่คือสิ่งที่คุณต้องทำ:

root = exports ? this
root.foo = -> 'Hello World'

สิ่งนี้จะประกาศฟังก์ชันของเราfooในเนมสเปซส่วนกลาง (ไม่ว่าจะเกิดอะไรขึ้น)
นั่นคือทั้งหมดที่ :)


1
@IvoWetzel - อะไรคือความแตกต่างระหว่างglobal, GLOBALและrootวัตถุใน Node.js?
Aadit M Shah

1
พยายามอ้างอิงตัวแปรที่มีอยู่ไม่ใช่ใน JavaScript มิฉะนั้นจะให้ผลผลิต SyntaxErrorคุณไม่ได้หมายความว่าReferenceError?
alex

12
หรือสั้นกว่านี้:(exports ? this).foo = -> 'Hello World'
Dane O'Connor

3
this.foo มักจะ! = window.foo แม้ว่าหากคุณ 'บริบท' นี้เป็นวัตถุแล้ว นี่เป็นไวยากรณ์ที่สับสน
เควิน

1
global = exports ? thisในขณะที่ผมเห็นด้วยกับการใช้ การอ้างสิทธิ์ว่า "ในกรณีของ Node.js จะเป็นบริบทส่วนกลาง ... " ผิดเนื่องจากthisตัวแปรเมื่อจำเป็นหรือดำเนินการโดย node.js จะถูกประเมินเป็นขอบเขตโมดูล ดังนั้นหากคุณคาดหวังว่าการตั้งค่าอุปกรณ์ประกอบฉากจะทำให้สามารถเข้าถึงได้ทั่วโลกคุณจะผิดหวัง หากคุณไม่ต้องการสิ่งที่ชุดทั่วโลกในบริบท Node.js คุณจำเป็นต้องใช้ตัวแปรมากกว่าglobal this
KFL

58

สำหรับฉันดูเหมือนว่า @atomicules มีคำตอบที่ง่ายที่สุด แต่ฉันคิดว่ามันง่ายขึ้นอีกเล็กน้อย คุณต้องใส่@อะไรก่อนที่คุณต้องการเป็นโลกเพื่อที่จะรวบรวมthis.anythingและthisอ้างอิงถึงวัตถุทั่วโลก

ดังนั้น...

@bawbag = (x, y) ->
    z = (x * y)

bawbag(5, 10)

รวบรวมเพื่อ ...

this.bawbag = function(x, y) {
  var z;
  return z = x * y;
};
bawbag(5, 10);

และทำงานได้ทั้งภายในและภายนอกของ wrapper ที่กำหนดโดย node.js

(function() {
    this.bawbag = function(x, y) {
      var z;
      return z = x * y;
    };
    console.log(bawbag(5,13)) // works here
}).call(this);

console.log(bawbag(5,11)) // works here

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

1
ถูกต้องดังนั้นคุณสามารถกำหนดตัวแปรของคุณในขอบเขตที่เหมาะสม (และใช้ในตัวแปรอื่น) หรือกำหนดว่าwindow.myVariableจะทำงานที่ไหน
Billy Moon

2
คุณไม่จำเป็นต้องกำหนดตัวแปรอื่นให้ใช้=>แทนการ->สั่งให้ coffeescript สร้างฟังก์ชันภายใต้ namespace นี้ / ส่วนกลาง
Ricardo Villamil

2
สิ่งนี้มีประโยชน์มากตอนนี้ฉันสามารถสร้างวัตถุและฟังก์ชั่นระดับโลกในสคริปต์กาแฟแยกต่างหาก
Diego Fernando Murillo Valenci

มันดีกว่ามาก การถ่ายโอน JS ไปยัง CS ต้องการให้ฉันเปลี่ยนการเรียกใช้ฟังก์ชันจำนวนมากเพื่อใช้วัตถุหน้าต่างตอนนี้ฉันสามารถเปลี่ยนกลับได้
casraf

33

Ivo จับมัน แต่ฉันจะพูดถึงว่ามีเคล็ดลับสกปรกอย่างหนึ่งที่คุณสามารถใช้ได้แม้ว่าฉันจะไม่แนะนำถ้าคุณกำลังมองหาจุดสไตล์: คุณสามารถฝังรหัส JavaScript โดยตรงใน CoffeeScript ของคุณด้วยการหลบหนีด้วย backticks

อย่างไรก็ตามนี่คือเหตุผลว่าทำไมนี่จึงเป็นความคิดที่ไม่ดี: คอมไพเลอร์ CoffeeScript ไม่รู้จักตัวแปรเหล่านั้นซึ่งหมายความว่าพวกเขาจะไม่ปฏิบัติตามกฎการกำหนดขอบเขต CoffeeScript ปกติ ดังนั้น,

`foo = 'bar'`
foo = 'something else'

รวบรวมเพื่อ

foo = 'bar';
var foo = 'something else';

และตอนนี้คุณมีสองตัวfooในขอบเขตที่แตกต่างกัน ไม่มีวิธีใดที่จะแก้ไขโกลบอล fooจากโค้ด CoffeeScript โดยไม่ต้องอ้างอิงอ็อบเจกต์โกลบอลตามที่ Ivy อธิบาย

แน่นอนว่านี่เป็นปัญหาถ้าคุณทำการมอบหมายให้fooใน CoffeeScript - ถ้าfooกลายเป็นแบบอ่านอย่างเดียวหลังจากได้รับค่าเริ่มต้น (นั่นคือค่าคงที่ทั่วโลก) ดังนั้นวิธีการแก้ปัญหา JavaScript แบบฝังตัวอาจยอมรับได้ ไม่แนะนำ).


1
นี่เป็นทางออกที่มีประโยชน์สำหรับฉันตั้งแต่ฉันใช้ Titanium กับ CoffeeScript การส่งออกและวัตถุหน้าต่างจะไม่มีที่นั่น
Pier-Olivier Thibault

อันที่จริงนี่เป็นเพียงหนึ่งในท้องถิ่นfooตัวแปรเพราะvarรอก (JS สแกนล่วงหน้าสำหรับทุกvarการประกาศและตีความพวกเขาราวกับว่าพวกเขาอยู่ที่ด้านบนของฟังก์ชั่น)
คอร์เนล

@porneL คุณถูกต้อง; ฉันเลือกตัวอย่างที่ไม่ดี ประเด็นก็คือคอมไพเลอร์ CoffeeScript ไม่ทำการวิเคราะห์ JavaScript ที่หลบหนีย้อนหลังดังนั้นคุณจึงสามารถรับเอาต์พุตคี่ได้
เทรเวอร์เบิร์นแฮม

2
@ Pier-OlivierThibault หากคุณต้องการใช้ Globals ใน Titanium คุณสามารถใช้ Ti.App.myGlobalVar = "ImAGlobalVar" และไม่ต้องการ backticks
Jakob Lnr

นี่คือคำตอบที่ถูกต้องสำหรับ Node.js เป็นอย่างน้อย การexpect = require('chai').expect;ทำให้expectตัวแปรพร้อมใช้งานในไฟล์ทดสอบทั้งหมดของฉัน!
pocesar

11

คุณสามารถผ่านตัวเลือก -b เมื่อคุณรวบรวมรหัสผ่าน coffee-script ภายใต้ node.js รหัสที่คอมไพล์แล้วจะเหมือนกับ coffeescript.org


อย่างไร? ฉันจะใส่ตัวเลือก -b ที่ไหน?
Harry

1
@Harry - -b/ --bareไปโดยตรงหลังจากcoffeeคำสั่ง
ocodo

9

เพื่อเพิ่มคำตอบของ Ivo Wetzel

ดูเหมือนว่าจะมีรูปแบบการจดชวเลขexports ? thisซึ่งฉันสามารถค้นหาเอกสาร / กล่าวถึงในการโพสต์ของกลุ่ม Googleเท่านั้น

เช่นในหน้าเว็บเพื่อให้ฟังก์ชั่นใช้งานได้ทั่วโลกคุณประกาศฟังก์ชั่นอีกครั้งด้วย@คำนำหน้า:

<script type="text/coffeescript">
    @aglobalfunction = aglobalfunction = () ->
         alert "Hello!"
</script>

<a href="javascript:aglobalfunction()" >Click me!</a>

9
'@' ใน @aglobalfunction จะถูกแทนที่ด้วย 'สิ่งนี้' ดังนั้นให้คอมไพล์เป็น 'this.aglobalfunction' สิ่งนี้ได้ผลเพราะขอบเขตของฟังก์ชั่น coffeescript wrapper (ถ้าใช้) คือขอบเขตทั่วโลก
Chris

9

ฉันคิดว่าสิ่งที่คุณพยายามจะทำให้สำเร็จสามารถทำได้ดังนี้:

ขณะที่คุณกำลังรวบรวม coffeescript ให้ใช้พารามิเตอร์ "-b"

-b/ --bare คอมไพล์ JavaScript โดยไม่มี wrapper ความปลอดภัยของฟังก์ชั่นระดับสูงสุด

ดังนั้นสิ่งนี้: coffee -b --compile somefile.coffee whatever.js

สิ่งนี้จะเอาท์พุทรหัสของคุณเหมือนกับในเว็บไซต์ CoffeeScript.org


7

หากคุณเป็นคนไม่ดี (ฉันเป็นคนไม่ดี) คุณสามารถทำได้ง่ายเช่นนี้: (->@)()

ในขณะที่

(->@)().im_a_terrible_programmer = yes
console.log im_a_terrible_programmer

งานนี้เพราะเมื่อตื่นตาตื่นใจReferenceกับFunction'เปลือย' (นั่นคือfunc()แทนที่จะnew func()หรือobj.func()) สิ่งที่เรียกกันทั่วไปว่าเป็น 'ฟังก์ชั่นการโทรรูปแบบการภาวนา' มักจะผูกthisกับวัตถุโลกสำหรับที่บริบทการดำเนินการ

CoffeeScript ข้างต้นรวบรวมเพียงเพื่อ(function(){ return this })(); ดังนั้นเราจึงใช้พฤติกรรมนั้นเพื่อเข้าถึงวัตถุระดับโลกได้อย่างน่าเชื่อถือ


มันยอดเยี่ยมมาก!
metalim

สิ่งเดียวที่เหมาะกับฉัน เกลียด CoffeeScript
pcv

รัก CoffeeScript จนถึงตอนนี้มันเป็นภาษาการเขียนโปรแกรมที่ดีที่สุด น่าเสียดายที่มันถูกสร้างและดูแลรักษาเป็นโครงการงานอดิเรกซึ่งนำไปสู่ความสับสนวุ่นวายและความโง่เขลาในรูปแบบการใช้งาน
metalim

3

เนื่องจาก coffeescript นั้นไม่ค่อยได้ใช้ด้วยตัวเองคุณสามารถใช้globalตัวแปรที่จัดทำโดย node.js หรือ browserify (และลูกหลานอื่น ๆ เช่น coffeeify, gulp build script ฯลฯ )

ใน node.js globalเป็นโกลบอลเนมสเปซ

ใน browserify เท่ากับglobalwindow

ดังนั้นเพียงแค่:

somefunc = ->
  global.variable = 123
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.