mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-11-26 09:21:00 +03:00
Compare commits
5 Commits
9024fdc4a9
...
47ac101eb6
Author | SHA1 | Date | |
---|---|---|---|
|
47ac101eb6 | ||
|
774ae2c45a | ||
|
7678771275 | ||
|
990878a11f | ||
|
ddde3166a8 |
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: ruby/setup-ruby@ec02537da5712d66d4d50a0f33b7eb52773b5ed1
|
- uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: '3.2'
|
ruby-version: '3.2'
|
||||||
- run: gem install mdl
|
- run: gem install mdl
|
||||||
|
775
fennel.html.markdown
Normal file
775
fennel.html.markdown
Normal file
@ -0,0 +1,775 @@
|
|||||||
|
---
|
||||||
|
language: fennel
|
||||||
|
filename: learnfennel.fnl
|
||||||
|
contributors:
|
||||||
|
- ["Jesse Wattenbarger", "https://github.com/jjwatt"]
|
||||||
|
---
|
||||||
|
|
||||||
|
Fennel is a programming language that brings together the simplicity,
|
||||||
|
speed, and reach of Lua with the flexibility of a lisp syntax and
|
||||||
|
macro system.
|
||||||
|
|
||||||
|
|
||||||
|
```fennel
|
||||||
|
;; Comments start with semicolons.
|
||||||
|
|
||||||
|
;; Fennel is written in lists of things inside parentheses, separated
|
||||||
|
;; by whitespace.
|
||||||
|
|
||||||
|
;; The first thing in parentheses is a function or macro to call, and
|
||||||
|
;; the rest are the arguments.
|
||||||
|
|
||||||
|
;; ------------------------- ;;
|
||||||
|
;; 1. Primitives & Operators ;;
|
||||||
|
;; ------------------------- ;;
|
||||||
|
|
||||||
|
;; (local ...) defines a var inside the whole file's scope.
|
||||||
|
(local s "walternate") ; Immutable strings like Python.
|
||||||
|
|
||||||
|
;; local supports destructuring and multiple value binding.
|
||||||
|
;; (covered later).
|
||||||
|
|
||||||
|
;; Strings are utf8 byte arrays
|
||||||
|
"λx:(μα.α→α).xx" ; can include Unicode characters
|
||||||
|
|
||||||
|
;; .. will create a string out of it's arguments.
|
||||||
|
;; It will coerce numbers but nothing else.
|
||||||
|
(.. "Hello" " " "World") ; => "Hello World"
|
||||||
|
|
||||||
|
;; (print ...) will print all arguments with tabs in between
|
||||||
|
(print "Hello" "World") ; "Hello World" printed to screen
|
||||||
|
|
||||||
|
(local num 42) ;; Numbers can be integer or floating point.
|
||||||
|
|
||||||
|
;; Equality is =
|
||||||
|
(= 1 1) ; => true
|
||||||
|
(= 2 1) ; => false
|
||||||
|
|
||||||
|
;; Nesting forms works as you expect
|
||||||
|
(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2
|
||||||
|
|
||||||
|
;; Comparisons
|
||||||
|
(> 1 2) ; => false
|
||||||
|
(< 1 2) ; => true
|
||||||
|
(>= 1 1) ; => true
|
||||||
|
(<= 1 2) ; => true
|
||||||
|
(not= 1 2) ; => true
|
||||||
|
|
||||||
|
;; TODO: find some bitwise operator examples
|
||||||
|
;; (lshift 1) ; => 2
|
||||||
|
|
||||||
|
;; -------- ;;
|
||||||
|
;; 2. Types ;;
|
||||||
|
;; -------- ;;
|
||||||
|
|
||||||
|
;; Fennel uses Lua's types for booleans, strings & numbers.
|
||||||
|
;; Use `type` to inspect them.
|
||||||
|
(type 1) ; => "number"
|
||||||
|
(type 1.0) ; => "number"
|
||||||
|
(type "") ; => "string"
|
||||||
|
(type false) ; => "boolean"
|
||||||
|
(type nil) ; => "nil"
|
||||||
|
|
||||||
|
;; Booleans
|
||||||
|
true ; for true
|
||||||
|
false ; for false
|
||||||
|
(not true) ; => false
|
||||||
|
(and 0 false) ; => false
|
||||||
|
(or false 0) ; => 0
|
||||||
|
|
||||||
|
;; All values other than nil or false are treated as true.
|
||||||
|
|
||||||
|
;;,--------
|
||||||
|
;;| Binding
|
||||||
|
;;`--------
|
||||||
|
|
||||||
|
;; Use `let` to bind local vars to values.
|
||||||
|
;; Local binding: `me` is bound to "Bob" only within the (let ...)
|
||||||
|
(let [me "Bob"]
|
||||||
|
(print "returning Bob")
|
||||||
|
me) ; => "Bob"
|
||||||
|
;; Outside the body of the let, the bindings it introduces are no
|
||||||
|
;; longer visible. The last form in the body is used as the return
|
||||||
|
;; value. `set` does not work on `let` bound locals.
|
||||||
|
|
||||||
|
;; `local` introduces a new local inside an existing scope. Similar to
|
||||||
|
;; let but without a body argument. Recommended for use at the
|
||||||
|
;; top-level of a file for locals which will be used throughout the
|
||||||
|
;; file. `set` does not work on `locals`
|
||||||
|
(local tau-approx 6.28318)
|
||||||
|
|
||||||
|
;; `var` introduces a new local inside an existing scope which may have its
|
||||||
|
;; value changed. Identical to local apart from allowing set to work
|
||||||
|
;; on it. `set` works on `vars`
|
||||||
|
(var x 83)
|
||||||
|
|
||||||
|
;; `set` local variable or table field
|
||||||
|
;; Changes the value of a variable introduced with `var`. Will not work
|
||||||
|
;; on `globals` or `let`/`local`-bound locals.
|
||||||
|
(set x (+ x 91)) ; var
|
||||||
|
|
||||||
|
;; Can also be used to change a field of a table, even if the table is
|
||||||
|
;; bound with `let` or `local`. If the table field name is static, use
|
||||||
|
;; `tbl.field`; if the field name is dynamic, use `(. tbl field)`.
|
||||||
|
(let [t {:a 4 :b 8}] ; static table field
|
||||||
|
(set t.a 2) t) ; => {:a 2 :b 8}
|
||||||
|
|
||||||
|
;; In any context where you can make a new binding, you can use
|
||||||
|
;; multiple value binding. Otherwise, you will only capture the first
|
||||||
|
;; value.
|
||||||
|
(let [x (values 1 2 3)]
|
||||||
|
x) ; => 1
|
||||||
|
|
||||||
|
;; `global` set global variable
|
||||||
|
;; Sets a global variable to a new value. Note that there is no
|
||||||
|
;; distinction between introducing a new global and changing the value
|
||||||
|
;; of an existing one. This supports destructuring and multiple-value
|
||||||
|
;; binding.
|
||||||
|
(global tbl {:a 4 :b 8})
|
||||||
|
(global prettyprint (fn [x] (print (fennel.view x))))
|
||||||
|
|
||||||
|
;;,--------------------------
|
||||||
|
;;| Prettyprint and .fennelrc
|
||||||
|
;;`--------------------------
|
||||||
|
;; `prettyprint` is a good function to have in your repl
|
||||||
|
|
||||||
|
;; Return values in the repl will get pretty-printed, but calling
|
||||||
|
;; (print tbl) will emit output like table: 0x55a3a8749ef0. If you
|
||||||
|
;; don't already have one, it's recommended for debugging to define a
|
||||||
|
;; printer function which calls `fennel.view` on its argument before
|
||||||
|
;; printing it.
|
||||||
|
(local fennel (require :fennel))
|
||||||
|
(fn _G.pp [x] (print
|
||||||
|
(fennel.view x)))
|
||||||
|
;; Notice that adding it to `_G` puts the function in the global
|
||||||
|
;; table, similar to what `global` would do. The fennel documentation
|
||||||
|
;; says that using `_G` is the preferred method of defining and using
|
||||||
|
;; globals.
|
||||||
|
;; If you add this definition to your ~/.fennelrc
|
||||||
|
;; file it will be available in the standard repl.
|
||||||
|
(pp tbl)
|
||||||
|
|
||||||
|
;;,--------------------------------
|
||||||
|
;;| Collections & Sequences: Tables
|
||||||
|
;;`--------------------------------
|
||||||
|
|
||||||
|
;; Tables are the only compound data structure in Lua and fennel.
|
||||||
|
;; Similar to php arrays or js objects, they are
|
||||||
|
;; hash-lookup dicts that can also be used as lists.
|
||||||
|
|
||||||
|
;; tables can be treated as sequential or non-sequential: as hashmaps
|
||||||
|
;; or lists/arrays.
|
||||||
|
|
||||||
|
;; Using tables as dictionaries / maps:
|
||||||
|
(local t {:key1 "value1" :key2 false})
|
||||||
|
|
||||||
|
;; String keys can use dot notation:
|
||||||
|
(print t.key1) ;; Prints "value1"
|
||||||
|
|
||||||
|
;; Setting table keys and values
|
||||||
|
(tset t :newKey {}) ;; Adds a new key/value pair.
|
||||||
|
(tset t :key2 nil) ;; Removes key2 from the table.
|
||||||
|
;; Literal notation for any (non-nil) value as key
|
||||||
|
|
||||||
|
;; length string or table length
|
||||||
|
(+ (length [1 2 3 nil 8]) (length "abc")) ; => 8
|
||||||
|
|
||||||
|
;; . table lookup looks up a given key in a table. Multiple arguments
|
||||||
|
;; will perform nested lookup.
|
||||||
|
(. t :key1)
|
||||||
|
|
||||||
|
(let [t {:a [2 3 4]}] (. t :a 2)) ; => 3
|
||||||
|
|
||||||
|
;; If the field name is a string known at compile time, you don't need
|
||||||
|
;; this and can just use table.field (dot notation).
|
||||||
|
|
||||||
|
;; Nil-safe ?. table lookup
|
||||||
|
;; Looks up a given key in a table. Multiple arguments will perform
|
||||||
|
;; nested lookup. If any subsequent keys is not presnet, will
|
||||||
|
;; short-circuit to nil.
|
||||||
|
(?. t :key1) ; => "value"
|
||||||
|
(let [t {:a [2 3 4]}] (?. t :a 4 :b)) ; => nil
|
||||||
|
(let [t {:a [2 3 4 {:b 42}]}] (?. t :a 4 :b)) ; => 42
|
||||||
|
|
||||||
|
;; The table module
|
||||||
|
(let [t [1 2 3]]
|
||||||
|
(table.insert t 2 "a") ; t is now [1 "a" 2 3]
|
||||||
|
(table.insert t "last") ; now [1 "a" 2 3 "last"]
|
||||||
|
(print (table.remove t)) ; prints "last"
|
||||||
|
(table.remove t 1) ; t is now ["a" 2 3]
|
||||||
|
(print (table.concat t ", "))) ; prints "a, 2, 3"
|
||||||
|
|
||||||
|
;; The table.sort function sorts a table in-place, as a
|
||||||
|
;; side-effect. It takes an optional comparator function which should
|
||||||
|
;; return true when its first argument is less than the second.
|
||||||
|
|
||||||
|
;; The table.unpack function returns all the elements in the table as
|
||||||
|
;; multiple values. Note that table.unpack is just unpack in Lua 5.1.
|
||||||
|
;; This will make `unpack` work in both.
|
||||||
|
(var unpack (or _G.unpack table.unpack))
|
||||||
|
;; See "prettyprint" section about printing tables
|
||||||
|
|
||||||
|
|
||||||
|
;; --------------------- ;;
|
||||||
|
;; 3. Basic Flow Control ;;
|
||||||
|
;; --------------------- ;;
|
||||||
|
;; `if` checks a condition and evaluates the corresponding body.
|
||||||
|
;; Accepts any number of condition/body pairs. If an odd number of
|
||||||
|
;; args is given, the last value is treated as a catch-all "else,"
|
||||||
|
;; similar to cond in other lisps.
|
||||||
|
(let [x (math.random 64)]
|
||||||
|
(if (= 0 (% x 10))
|
||||||
|
"multiple of ten"
|
||||||
|
(= 0 (% x 2))
|
||||||
|
"even"
|
||||||
|
"I dunno, something else"))
|
||||||
|
;; All values other than nil or false are treated as true.
|
||||||
|
|
||||||
|
;; `when` takes a single condition and evalutes the rest as a body if
|
||||||
|
;; it's truthy. Intended for side-effects. The last form is the return
|
||||||
|
;; value.
|
||||||
|
(when launch-missiles?
|
||||||
|
(power-on)
|
||||||
|
(open-doors)
|
||||||
|
(fire))
|
||||||
|
|
||||||
|
;;,------------------
|
||||||
|
;;| Loops & Iteration
|
||||||
|
;;`------------------
|
||||||
|
|
||||||
|
;; each: general iteration
|
||||||
|
;; `each` runs the body once for each value provided by the iterator.
|
||||||
|
(each [key value (pairs mytbl)]
|
||||||
|
(print "executing key")
|
||||||
|
(print (f value)))
|
||||||
|
|
||||||
|
;; Any loop can be terminated early by placing an &until clause at the
|
||||||
|
;; end of the bindings
|
||||||
|
(local out [])
|
||||||
|
(each [_ value (pairs tbl) &until (< max-len (length out))]
|
||||||
|
(table.insert out value))
|
||||||
|
|
||||||
|
;; `for` is a numeric loop with start, stop and optional step.
|
||||||
|
(for [i 1 10 2]
|
||||||
|
(log-number i)
|
||||||
|
(print i)) ;; print odd numbers under 10
|
||||||
|
|
||||||
|
;; Like each, loops using for can also be terminated early with an
|
||||||
|
;; &until clause
|
||||||
|
(var x 0)
|
||||||
|
(for [i 1 128 &until (maxed-out? x)]
|
||||||
|
(set x (+ x i)))
|
||||||
|
|
||||||
|
;; while loops over a body until a condition is met
|
||||||
|
;; Returns nil.
|
||||||
|
(var done? false)
|
||||||
|
(while (not done?)
|
||||||
|
(print :not-done)
|
||||||
|
(when (< 0.95 (math.random))
|
||||||
|
(set done? true)))
|
||||||
|
;; while uses the native lua while loop
|
||||||
|
|
||||||
|
;; `do` evaluate multiple forms returning last value
|
||||||
|
;; Accepts any number of forms and evaluates all of them in order,
|
||||||
|
;; returning the last value. This is used for inserting side-effects
|
||||||
|
;; into a form which accepts only a single value, such as in a body of
|
||||||
|
;; an if when multiple clauses make it so you can't use when. Some
|
||||||
|
;; lisps call this begin or progn.
|
||||||
|
(if launch-missiles?
|
||||||
|
(do
|
||||||
|
(power-on)
|
||||||
|
(open-doors)
|
||||||
|
(fire))
|
||||||
|
false-alarm?
|
||||||
|
(promote lt-petrov))
|
||||||
|
|
||||||
|
;; Many functions and macros like fn & let have an implicit do at the
|
||||||
|
;; start, so you don't have to add it to use multiple forms.
|
||||||
|
|
||||||
|
;;,-----------------------
|
||||||
|
;;| `collect` & `icollect`
|
||||||
|
;;`-----------------------
|
||||||
|
;; icollect, collect: table comprehension macros
|
||||||
|
|
||||||
|
;; The icollect macro takes a "iterator binding table" in the same
|
||||||
|
;; format that `each` takes, and returns a sequential table containing
|
||||||
|
;; all the values produced by each iteration of the macro's body. This
|
||||||
|
;; is similar to how map works in several other languages, but it is a
|
||||||
|
;; macro, not a function.
|
||||||
|
|
||||||
|
;; If the value is nil, it is omitted from the return table. This is
|
||||||
|
;; analogous to filter in other languages.
|
||||||
|
(icollect [_ v (ipairs [1 2 3 4 5 6])]
|
||||||
|
(if (< 2 v) (* v v)))
|
||||||
|
;; -> [9 16 25 36]
|
||||||
|
;; equivalent to:
|
||||||
|
(let [tbl []]
|
||||||
|
(each [_ v (ipairs [1 2 3 4 5 6])]
|
||||||
|
(tset tbl (+ (length tbl) 1) (if (< 2 v) (* v v))))
|
||||||
|
tbl)
|
||||||
|
|
||||||
|
;; The `collect` macro is almost identical, except that the body should
|
||||||
|
;; return two things: a key and a value.
|
||||||
|
(collect [k v (pairs {:apple "red" :orange "orange" :lemon "yellow"})]
|
||||||
|
(if (not= v "yellow")
|
||||||
|
(values (.. "color-" v) k)))
|
||||||
|
;; -> {:color-orange "orange" :color-red "apple"}
|
||||||
|
;; equivalent to:
|
||||||
|
(let [tbl {}]
|
||||||
|
(each [k v (pairs {:apple "red" :orange "orange"})]
|
||||||
|
(if (not= v "yellow")
|
||||||
|
(match (values (.. "color-" v) k)
|
||||||
|
(key value) (tset tbl key value))))
|
||||||
|
tbl)
|
||||||
|
|
||||||
|
;; If the key and value are given directly in the body of collect and
|
||||||
|
;; not nested in an outer form, then the `values` call can be omitted
|
||||||
|
;; for brevity
|
||||||
|
(collect [k v (pairs {:a 85 :b 52 :c 621 :d 44})]
|
||||||
|
k (* v 5))
|
||||||
|
;; -> {:a 425 :b 260 :c 3105 :d 220}
|
||||||
|
|
||||||
|
;; If the index and value are given directly in the body of collect and
|
||||||
|
;; not nested in an outer form, then the values can be omitted for
|
||||||
|
;; brevity
|
||||||
|
(icollect [_ x (ipairs [2 3]) &into [9]]
|
||||||
|
(* x 11))
|
||||||
|
;; -> [9 22 33]
|
||||||
|
|
||||||
|
;; accumulate
|
||||||
|
;; Runs through an iterator and performs accumulation, similar to fold
|
||||||
|
;; and reduce commonly used in functional programming languages. Like
|
||||||
|
;; collect and icollect, it takes an iterator binding table and an
|
||||||
|
;; expression as its arguments. The difference is that in accumulate,
|
||||||
|
;; the first two items in the binding table are used as an
|
||||||
|
;; "accumulator" variable and its initial value. For each iteration
|
||||||
|
;; step, it evaluates the given expression and its value becomes the
|
||||||
|
;; next accumulator variable. accumulate returns the final value of
|
||||||
|
;; the accumulator variable.
|
||||||
|
(accumulate [sum 0
|
||||||
|
i n (ipairs [10 20 30 40])]
|
||||||
|
(+ sum n)) ; -> 100
|
||||||
|
|
||||||
|
;; `faccumulate` range accumulation Identical to accumulate, but
|
||||||
|
;; instead of taking an iterator and the same bindings as `each`, it
|
||||||
|
;; accepts the same bindings as `for` and will iterate the numerical
|
||||||
|
;; range. Accepts `&until` just like `for` and `accumulate`.
|
||||||
|
(faccumulate [n 0 i 1 5] (+ n i)) ; => 15
|
||||||
|
|
||||||
|
;; `fcollect` range comprehension Similarly to `icollect`, `fcollect`
|
||||||
|
;; provides a way of building a sequential table. Unlike `icollect`,
|
||||||
|
;; instead of an iterator it traverses a range, as accepted by the
|
||||||
|
;; `for` special. The `&into` and `&until` clauses work the same as in
|
||||||
|
;; `icollect.`
|
||||||
|
(fcollect [i 0 10 2]
|
||||||
|
(if (> i 2) (* i i)))
|
||||||
|
;; -> [16 36 64 100]
|
||||||
|
;; equivalent to:
|
||||||
|
(let [tbl {}]
|
||||||
|
(for [i 0 10 2]
|
||||||
|
(if (> i 2)
|
||||||
|
(table.insert tbl (* i i))))
|
||||||
|
tbl)
|
||||||
|
|
||||||
|
;; `values` Returns multiple values from a function. Usually used to
|
||||||
|
;; signal failure by returning nil followed by a message.
|
||||||
|
(fn [filename]
|
||||||
|
(if (valid-file-name? filename)
|
||||||
|
(open-file filename)
|
||||||
|
(values nil (.. "Invalid filename: " filename))))
|
||||||
|
|
||||||
|
;; See the Destructuring and Matching sections for more advanced Flow
|
||||||
|
;; Control.
|
||||||
|
|
||||||
|
;; ------------ ;;
|
||||||
|
;; 4. Functions ;;
|
||||||
|
;; ------------ ;;
|
||||||
|
|
||||||
|
;; Use fn to create new functions. A function always returns its last
|
||||||
|
;; statement.
|
||||||
|
(fn [] "Hello World") ; => #<function: 0x55630f9d7f20>
|
||||||
|
|
||||||
|
; (You need extra parens to call it)
|
||||||
|
((fn [] "Hello World")) ; => "Hello World"
|
||||||
|
|
||||||
|
;; Assign a function to a local variable
|
||||||
|
(local hello-world (fn [] "Hello World"))
|
||||||
|
(hello-world) ; => "Hello World"
|
||||||
|
|
||||||
|
;; You can use fn and name a function.
|
||||||
|
(fn hello-world [] "Hello World") ; => "Hello World"
|
||||||
|
|
||||||
|
;; The [] is the list of arguments to the function.
|
||||||
|
(fn hello [name]
|
||||||
|
(.. "Hello " name))
|
||||||
|
(hello "Steve") ; => "Hello Steve"
|
||||||
|
|
||||||
|
;; Will accept any number of arguments. ones in excess of the declared
|
||||||
|
;; ones are ignored, and if not enough arguments are supplied to cover
|
||||||
|
;; the declared ones, the remaining ones are given values of nil.
|
||||||
|
|
||||||
|
;; Providing a name that's a table field will cause it to be inserted
|
||||||
|
;; in a table instead of bound as a local
|
||||||
|
(local functions {})
|
||||||
|
|
||||||
|
(fn functions.p [x y z]
|
||||||
|
(print (* x (+ y z))))
|
||||||
|
|
||||||
|
;; equivalent to:
|
||||||
|
(set functions.p (fn [x y z]
|
||||||
|
(print (* x (+ y z)))))
|
||||||
|
|
||||||
|
;; Like Lua, functions in Fennel support tail-call optimization,
|
||||||
|
;; allowing (among other things) functions to recurse indefinitely
|
||||||
|
;; without overflowing the stack, provided the call is in a tail
|
||||||
|
;; position.
|
||||||
|
(fn factorial [x acc]
|
||||||
|
(if (= 0 x)
|
||||||
|
acc
|
||||||
|
(factorial (- x 1) (* x acc))))
|
||||||
|
(factorial 5 1) ;; -> 120
|
||||||
|
;; The final form in this and all other function forms is used as the
|
||||||
|
;; return value.
|
||||||
|
|
||||||
|
;; (lambda [...])
|
||||||
|
;; Creates a function like fn does, but throws an error at runtime if
|
||||||
|
;; any of the listed arguments are nil, unless its identifier begins
|
||||||
|
;; with ?.
|
||||||
|
(lambda [x ?y z]
|
||||||
|
(print (- x (* (or ?y 1) z))))
|
||||||
|
|
||||||
|
;; Note that the Lua runtime will fill in missing arguments with nil
|
||||||
|
;; when they are not provided by the caller, so an explicit nil
|
||||||
|
;; argument is no different than omitting an argument.
|
||||||
|
|
||||||
|
;; Docstrings and metadata
|
||||||
|
;; The fn, lambda, λ and macro forms accept an optional docstring.
|
||||||
|
(fn pxy [x y]
|
||||||
|
"Print the sum of x and y"
|
||||||
|
(print (+ x y)))
|
||||||
|
|
||||||
|
;; Hash function literal shorthand
|
||||||
|
|
||||||
|
;; hashfn is a special function that you can abbreviate as #
|
||||||
|
;; #foo expands to (hashfn foo)
|
||||||
|
|
||||||
|
;; Hash functions are anonymous functions of one form, with implicitly
|
||||||
|
;; named arguments.
|
||||||
|
|
||||||
|
#(+ $1 $2) ;; same as
|
||||||
|
(hashfn (+ $1 $2)) ; implementation detail; don't use directly
|
||||||
|
;; same as
|
||||||
|
(fn [a b] (+a b))
|
||||||
|
|
||||||
|
;; A lone $ in a hash function is treated as ana alias for $1.
|
||||||
|
#(+ $ 1)
|
||||||
|
|
||||||
|
#$ ; same as (fn [x] x) (aka the identity function
|
||||||
|
#val ; same as (fn [] val)
|
||||||
|
#[$1 $2 $3] ; same as (fn [a b c] [a b c])
|
||||||
|
|
||||||
|
;; ---------------------------------------------;;
|
||||||
|
;; 5. Destructuring, Binding & Pattern Matching ;;
|
||||||
|
;; ---------------------------------------------;;
|
||||||
|
;; Any time you bind a local, you can destructure it if the value is a
|
||||||
|
;; table or a function call which returns multiple values
|
||||||
|
(let [(x y z) (unpack [10 9 8])]
|
||||||
|
(+ x y z)) ; => 27
|
||||||
|
|
||||||
|
(let [[a b c] [1 2 3]]
|
||||||
|
(+ a b c)) ; => 6
|
||||||
|
;; If a table key is a string with the same name as the local you want
|
||||||
|
;; to bind to, you can use shorthand of just : for the key name
|
||||||
|
;; followed by the local name. This works for both creating tables and
|
||||||
|
;; destructuring them.
|
||||||
|
(let [{:msg message : val} {:msg "hello there" :val 19}]
|
||||||
|
(print message)
|
||||||
|
val) ; prints "hello there" and returns 19
|
||||||
|
|
||||||
|
;; When destructuring a sequential table, you can capture all the
|
||||||
|
;; remainder of the table in a local by using &
|
||||||
|
(let [[a b & c] [1 2 3 4 5 6]]
|
||||||
|
(table.concat c ",")) ; => "3,4,5,6"
|
||||||
|
|
||||||
|
;; When destructuring a non-sequential table, you can capture the
|
||||||
|
;; original table along with the destructuring by using &as
|
||||||
|
(let [{:a a :b b &as all} {:a 1 :b 2 :c 3 :d 4}]
|
||||||
|
(+ a b all.c all.d)) ; => 10
|
||||||
|
|
||||||
|
;;,-----------------------
|
||||||
|
;;| Multiple value binding
|
||||||
|
;;`-----------------------
|
||||||
|
;; In most contexts where you can make a new binding, you can use
|
||||||
|
;; multiple value binding.
|
||||||
|
(let [x (values 1 2 3)] x) ; = > 1
|
||||||
|
(let [(file-handle message code) (io.open "fooblah.blah")]
|
||||||
|
message) ; => "fooblah.blah: No such file or directory"
|
||||||
|
(do
|
||||||
|
(local (_ _ z) (table.unpack [:a :b :c :d :e]))
|
||||||
|
z) ; => c
|
||||||
|
|
||||||
|
;; tset sets the field of a given table to a new value.
|
||||||
|
(let [tbl {:d 32}
|
||||||
|
field :d]
|
||||||
|
(tset tbl field 19) tbl) ; => {:d 19}
|
||||||
|
;; You can provide multiple successive field names to perform
|
||||||
|
;; nested sets.
|
||||||
|
(let [tbl {:a
|
||||||
|
{:b {}}}
|
||||||
|
field :c]
|
||||||
|
(tset tbl :a :b field "d") tbl) ; => .. .. {:a {:b {:c "d"}}}
|
||||||
|
|
||||||
|
;;,------------------------
|
||||||
|
;;| `case` pattern matching
|
||||||
|
;;`------------------------
|
||||||
|
;; Evaluates its first argument, then searches thru the subsequent
|
||||||
|
;; pattern/body clauses to find one where the pattern matches the
|
||||||
|
;; value, and evaluates the corresponding body. Pattern matching can
|
||||||
|
;; be thought of as a combination of destructuring and conditionals.
|
||||||
|
(case mytable
|
||||||
|
59 :will-never-match-hopefully
|
||||||
|
[9 q 5] (print :q q)
|
||||||
|
[1 a b] (+ a b))q
|
||||||
|
|
||||||
|
;; Patterns can be tables, literal values, or symbols. Any symbol is
|
||||||
|
;; implicitly checked to be not nil. Symbols can be repeated in an
|
||||||
|
;; expression to check for the same value.
|
||||||
|
(case mytable
|
||||||
|
;; the first and second values of mytable are not nil and are the same value
|
||||||
|
[a a] (* a 2)
|
||||||
|
;; the first and second values are not nil and are not the same value
|
||||||
|
[a b] (+ a b))
|
||||||
|
|
||||||
|
;; It's important to note that expressions are checked in order! In
|
||||||
|
;; the above example, since [a a] is checked first
|
||||||
|
|
||||||
|
;; You may allow a symbol to optionally be nil by prefixing it with ?.
|
||||||
|
(case mytable
|
||||||
|
;; not-nil, maybe-nil
|
||||||
|
[a ?b] :maybe-one-maybe-two-values
|
||||||
|
;; maybe-nil == maybe-nil, both are nil or both are the same value
|
||||||
|
[?a ?a] :maybe-none-maybe-two-same-values
|
||||||
|
;; maybe-nil, maybe-nil
|
||||||
|
[?a ?b] :maybe-none-maybe-one-maybe-two-values)
|
||||||
|
|
||||||
|
;; Symbols prefixed by an _ are ignored and may stand in as positional
|
||||||
|
;; placeholders or markers for "any" value - including a nil value. A
|
||||||
|
;; single _ is also often used at the end of a case expression to
|
||||||
|
;; define an "else" style fall-through value.
|
||||||
|
(case mytable
|
||||||
|
;; not-nil, anything
|
||||||
|
[a _b] :maybe-one-maybe-two-values
|
||||||
|
;; anything, anything (different to the previous ?a example!)
|
||||||
|
;; note this is effectively the same as []
|
||||||
|
[_a _a] :maybe-none-maybe-one-maybe-two-values
|
||||||
|
;; anything, anything
|
||||||
|
;; this is identical to [_a _a] and in this example would never actually match.
|
||||||
|
[_a _b] :maybe-none-maybe-one-maybe-two-values
|
||||||
|
;; when no other clause matched, in this case any non-table value
|
||||||
|
_ :no-match)
|
||||||
|
|
||||||
|
;; You can match with multiple return values with
|
||||||
|
;; parenthesis, like you can with destructuring `let`
|
||||||
|
(case (io.open "/some/file")
|
||||||
|
(nil msg) (report-error msg)
|
||||||
|
f (read-file f))
|
||||||
|
|
||||||
|
;;,--------------
|
||||||
|
;;| Guard Clauses
|
||||||
|
;;`--------------
|
||||||
|
;; If you need to match on something more general than
|
||||||
|
;; a structure, use guard clauses:
|
||||||
|
(case [91 12 53]
|
||||||
|
(where [a b c] (= 5 a)) :will-not-match
|
||||||
|
(where [a b c]
|
||||||
|
(= 0 (math.fmod
|
||||||
|
(+ a b c) 2))
|
||||||
|
(= 91 a))
|
||||||
|
c) ; -> 53
|
||||||
|
;; Each form after the pattern is a condition. All conditions must
|
||||||
|
;; evaluate to true for the pattern to match.
|
||||||
|
|
||||||
|
;; If several patterns share the same body & guards, such patterns can
|
||||||
|
;; be with the `or` special in the `where` clause.
|
||||||
|
(case [5 1 2]
|
||||||
|
(where (or [a 3 9] [a 1 2]) (= 5 a))
|
||||||
|
"Either [5 3 9] or [5 1 2]"
|
||||||
|
_ "anything else")
|
||||||
|
|
||||||
|
;; Symbols bound inside a case pattern are independent from any
|
||||||
|
;; existing symbols in the current scope, Sometimes it may be
|
||||||
|
;; desirable to match against an existing value in the outer scope. To
|
||||||
|
;; do this we can "pin" a binding inside the pattern with an existing
|
||||||
|
;; outer binding with the unary (= binding-name) form. The unary (=
|
||||||
|
;; binding-name) form is only valid in a case pattern and must be
|
||||||
|
;; inside a (where) guard.
|
||||||
|
(let [x 1]
|
||||||
|
(case [1]
|
||||||
|
;; 1 == 1
|
||||||
|
(where [(= x)]) x
|
||||||
|
_ :no-match)) ; -> 1
|
||||||
|
;; Pinning is only required inside the pattern. Outer bindings are
|
||||||
|
;; automatically available inside guards and bodies as long as the
|
||||||
|
;; name has not been rebound in the pattern.
|
||||||
|
|
||||||
|
;; Note: The case macro can be used in place of the if-let macro from
|
||||||
|
;; Clojure. The reason Fennel doesn't have if-let is that case makes
|
||||||
|
;; it redundant.
|
||||||
|
|
||||||
|
;;,-------------------------
|
||||||
|
;;| `match` pattern matching
|
||||||
|
;;`-------------------------
|
||||||
|
;; match is conceptually equivalent to case, except symbols in the
|
||||||
|
;; patterns are always pinned with outer-scope symbols if they exist.
|
||||||
|
(let [x 95]
|
||||||
|
(match [52 85 95]
|
||||||
|
[b a a] :no ; because a=85 and a=95
|
||||||
|
[x y z] :no ; because x=95 and x=52
|
||||||
|
[a b x] :yes)) ; a and b are fresh values while x=95 and x=95
|
||||||
|
|
||||||
|
;;,-----------------------------------------------------
|
||||||
|
;;| `case-try` & `match-try` for matching multiple steps
|
||||||
|
;;`-----------------------------------------------------
|
||||||
|
;; Evaluates a series of pattern matching steps. The value from the
|
||||||
|
;; first expression is matched against the first pattern. If it
|
||||||
|
;; matches, the first body is evaluated and its value is matched
|
||||||
|
;; against the second pattern, etc.
|
||||||
|
;;
|
||||||
|
;; If there is a (catch pat1 body1 pat2 body2 ...) form at the end,
|
||||||
|
;; any mismatch from the steps will be tried against these patterns in
|
||||||
|
;; sequence as a fallback just like a normal case. If no catch pattern
|
||||||
|
;; matches, nil is returned.
|
||||||
|
;;
|
||||||
|
;; If there is no catch, the mismatched value will be returned as the
|
||||||
|
;; value of the entire expression.
|
||||||
|
(fn handle [conn token]
|
||||||
|
(case-try (conn:receive :*l)
|
||||||
|
input (parse input)
|
||||||
|
(command-name params (= token)) (commands.get command-name)
|
||||||
|
command (pcall command (table.unpack params))
|
||||||
|
(catch
|
||||||
|
(_ :timeout) nil
|
||||||
|
(_ :closed) (pcall disconnect conn "connection closed")
|
||||||
|
(_ msg) (print "Error handling input" msg))))
|
||||||
|
|
||||||
|
;; This is useful when you want to perform a series of steps, any of
|
||||||
|
;; which could fail. The catch clause lets you keep all your error
|
||||||
|
;; handling in one place. Note that there are two ways to indicate
|
||||||
|
;; failure in Fennel and Lua: using the assert/error functions or
|
||||||
|
;; returning nil followed by some data representing the failure. This
|
||||||
|
;; form only works on the latter, but you can use pcall to transform
|
||||||
|
;; error calls into values.
|
||||||
|
|
||||||
|
;; `match-try` for matching multiple steps
|
||||||
|
;; Unlike case-try, match-try will pin values in a given catch block
|
||||||
|
;; with those in the original steps.
|
||||||
|
(fn handle [conn token]
|
||||||
|
(match-try (conn:receive :*l)
|
||||||
|
input (parse input)
|
||||||
|
(command-name params token) (commands.get command-name)
|
||||||
|
command (pcall command (table.unpack params))
|
||||||
|
(catch
|
||||||
|
(_ :timeout) nil
|
||||||
|
(_ :closed) (pcall disconnect conn "connection closed")
|
||||||
|
(_ msg) (print "Error handling input" msg))))
|
||||||
|
|
||||||
|
|
||||||
|
;; ---------;;
|
||||||
|
;; 6. Other ;;
|
||||||
|
;; ---------;;
|
||||||
|
|
||||||
|
;; The `:` method call
|
||||||
|
;; Looks up a function in a table and calls it with the table as its
|
||||||
|
;; first argument. This is a common idiom in many Lua APIs, including
|
||||||
|
;; some built-in ones. Just like Lua, you can perform a method call by
|
||||||
|
;; calling a function name where : separates the table variable and
|
||||||
|
;; method name.
|
||||||
|
(let [f (assert (io.open "hello" "w"))]
|
||||||
|
(f:write "world")
|
||||||
|
(f:close))
|
||||||
|
|
||||||
|
;; If the name of the method or the table containing it isn't fixed,
|
||||||
|
;; you can use : followed by the table and then the method's name to
|
||||||
|
;; allow it to be a dynamic string instead
|
||||||
|
(let [f (assert (io.open "hello" "w"))
|
||||||
|
method1 :write
|
||||||
|
method2 :close]
|
||||||
|
(: f method1 "world")
|
||||||
|
(: f method2))
|
||||||
|
|
||||||
|
;; Unlike Lua, there's nothing special about defining functions that
|
||||||
|
;; get called this way; typically it is given an extra argument called
|
||||||
|
;; self but this is just a convention; you can name it anything.
|
||||||
|
(local t {})
|
||||||
|
(fn t.enable [self]
|
||||||
|
(set self.enabled? true))
|
||||||
|
(t:enable)
|
||||||
|
|
||||||
|
|
||||||
|
;; ->, ->>, -?> and -?>> threading macros The -> macro takes its first
|
||||||
|
;; value and splices it into the second form as the first
|
||||||
|
;; argument. The result of evaluating the second form gets spliced
|
||||||
|
;; into the first argument of the third form, and so on.
|
||||||
|
(-> 52
|
||||||
|
(+ 91 2) ; (+ 52 91 2)
|
||||||
|
(- 8) ; (- (+ 52 91 2) 8)
|
||||||
|
(print "is the answer")) ; (print (- (+ 52 91 2) 8) "is the answer")
|
||||||
|
|
||||||
|
;; The ->> macro works the same, except it splices it into the last
|
||||||
|
;; position of each form instead of the first. -?> and -?>>, the
|
||||||
|
;; thread maybe macros, are similar to -> & ->> but they also do
|
||||||
|
;; checking after the evaluation of each threaded form. If the result
|
||||||
|
;; is false or nil then the threading stops and the result is
|
||||||
|
;; returned. -?> splices the threaded value as the first argument,
|
||||||
|
;; like ->, and -?>> splices it into the last position, like ->>.
|
||||||
|
;; This example shows how to use them to avoid accidentally indexing a
|
||||||
|
;; nil value
|
||||||
|
(-?> {:a {:b {:c 42}}}
|
||||||
|
(. :a)
|
||||||
|
(. :missing)
|
||||||
|
(. :c)) ; -> nil
|
||||||
|
(-?>> :a
|
||||||
|
(. {:a :b})
|
||||||
|
(. {:b :missing})
|
||||||
|
(. {:c 42})) ; -> nil
|
||||||
|
;; While -> and ->> pass multiple values thru without any trouble, the
|
||||||
|
;; checks in -?> and -?>> prevent the same from happening there
|
||||||
|
;; without performance overhead, so these pipelines are limited to a
|
||||||
|
;; single value.
|
||||||
|
|
||||||
|
;; doto
|
||||||
|
;; Similarly, the doto macro splices the first value into subsequent
|
||||||
|
;; forms. However, it keeps the same value and continually splices the
|
||||||
|
;; same thing in rather than using the value from the previous form
|
||||||
|
;; for the next form.
|
||||||
|
(doto (io.open "/tmp/err.log")
|
||||||
|
(: :write contents)
|
||||||
|
(: :close))
|
||||||
|
;; equivalent to:
|
||||||
|
(let [x (io.open "/tmp/err.log")]
|
||||||
|
(: x :write contents)
|
||||||
|
(: x :close)
|
||||||
|
x)
|
||||||
|
|
||||||
|
;; tail!
|
||||||
|
;; the tail! form asserts that its argument is called in a tail
|
||||||
|
;; position. You can use this when the code depends on tail call
|
||||||
|
;; optimization; that way if the code is changed so that the recursive
|
||||||
|
;; call is no longer in the tail position, it will cause a compile
|
||||||
|
;; error instead of overflowing the stack later on large data sets.
|
||||||
|
(fn process-all [data i]
|
||||||
|
(case (process (. data i))
|
||||||
|
:done (print "Process completed.")
|
||||||
|
:next (process-all data (+ i 1))
|
||||||
|
:skip (do (tail! (process-all data (+ i 2)))
|
||||||
|
;; ^^^^^ Compile error: Must be in tail position
|
||||||
|
(print "Skipped" (+ i 1)))))
|
||||||
|
|
||||||
|
```
|
||||||
|
### Further Reading
|
||||||
|
|
||||||
|
The [fennel website] (https://fennel-lang.org/) is the best resource
|
||||||
|
on fennel. It links to the [fennel setup guide]
|
||||||
|
(https://fennel-lang.org/setup) and to the [fennel reference manual]
|
||||||
|
(https://fennel-lang.org/reference). This docuement borrows heavily in
|
||||||
|
parts from the fennel reference manual.
|
611
zh-tw/javascript-tw.html.markdown
Normal file
611
zh-tw/javascript-tw.html.markdown
Normal file
@ -0,0 +1,611 @@
|
|||||||
|
---
|
||||||
|
language: javascript
|
||||||
|
category: language
|
||||||
|
name: javascript
|
||||||
|
filename: javascript-zh-tw.js
|
||||||
|
contributors:
|
||||||
|
- ["Leigh Brenecki", "https://leigh.net.au"]
|
||||||
|
- ["Ariel Krakowski", "http://www.learneroo.com"]
|
||||||
|
translators:
|
||||||
|
- ["Woody Chang", "https://github.com/kazettique"]
|
||||||
|
lang: zh-tw
|
||||||
|
---
|
||||||
|
|
||||||
|
JavaScript 是由網景公司(Netscape)的布蘭登·艾克(Brendan Eich)於 1995 年創建的。它最初被設計為一種更簡單的網站腳本語言,用於補足 Java 在更複雜的網路應用程式中使用,但由於它與網頁的高度整合,以及瀏覽器對 JavaScript 的原生支援,使得它在網頁前端的應用遠比 Java 更加普及。
|
||||||
|
|
||||||
|
然而 JavaScript 並不僅限於網頁瀏覽器:Node.js,一個可提供 Google Chrome 的 V8 JavaScript 引擎執行環境的專案,正變得越來越熱門。
|
||||||
|
|
||||||
|
JavaScript 具備類似 C 語言的語法,所以若您曾使用過 C 或 Java 等語言,許多基本語法對您來說已經相當熟悉了。雖然在語法上、名稱上與 Java 很類似,但是 JavaScript 的物件模型卻與 Java 有顯著地不同。
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 這是單行註解
|
||||||
|
/* 這是
|
||||||
|
多行註解 */
|
||||||
|
|
||||||
|
// 陳述式可以用分號(;)終止
|
||||||
|
doStuff();
|
||||||
|
|
||||||
|
// ... 然而不一定要加分號,當換行時會自動插入分號(除了某些特殊情況)。
|
||||||
|
doStuff()
|
||||||
|
|
||||||
|
// 避免意外結果的情況,本文會繼續使用分號
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
// 1. 數字、字串和運算子
|
||||||
|
|
||||||
|
// JavaScript 只有一種數字型別(也就是 64 位元 IEEE 754 雙精度浮點數)。
|
||||||
|
// 雙精度浮點數有一個 52 位的尾數,足以精確儲存整數最大至 9✕10¹⁵ 的整數。
|
||||||
|
3; // = 3
|
||||||
|
1.5; // = 1.5
|
||||||
|
|
||||||
|
// 所有基本算術運算都如您預期。
|
||||||
|
1 + 1; // = 2
|
||||||
|
0.1 + 0.2; // = 0.30000000000000004
|
||||||
|
8 - 1; // = 7
|
||||||
|
10 * 2; // = 20
|
||||||
|
35 / 5; // = 7
|
||||||
|
|
||||||
|
// 包括無法整除的除法運算。
|
||||||
|
5 / 2; // = 2.5
|
||||||
|
|
||||||
|
// 以及餘數運算。
|
||||||
|
10 % 2; // = 0
|
||||||
|
30 % 4; // = 2
|
||||||
|
18.5 % 7; // = 4.5
|
||||||
|
|
||||||
|
// 位元運算也是如此,當執行位元運算時,浮點數會轉換「最多」32位元的有符號整數
|
||||||
|
1 << 2; // = 4
|
||||||
|
|
||||||
|
// 以括號決定運算優先級。
|
||||||
|
(1 + 3) * 2; // = 8
|
||||||
|
|
||||||
|
// 有三個非數值的值:
|
||||||
|
Infinity; // 1/0 的結果
|
||||||
|
-Infinity; // -1/0 的結果
|
||||||
|
NaN; // 0/0 的結果
|
||||||
|
|
||||||
|
// 也有布林值。
|
||||||
|
true;
|
||||||
|
false;
|
||||||
|
|
||||||
|
// 透過單引號(')或雙引號(")建立字串。
|
||||||
|
'abc';
|
||||||
|
"Hello, world";
|
||||||
|
|
||||||
|
// 以驚嘆號(!)執行否定運算
|
||||||
|
!true; // = false
|
||||||
|
!false; // = true
|
||||||
|
|
||||||
|
// 相等運算 `===`
|
||||||
|
1 === 1; // = true
|
||||||
|
2 === 1; // = false
|
||||||
|
|
||||||
|
// 不相等運算 !==
|
||||||
|
1 !== 1; // = false
|
||||||
|
2 !== 1; // = true
|
||||||
|
|
||||||
|
// 更多比較運算子
|
||||||
|
1 < 10; // = true
|
||||||
|
1 > 10; // = false
|
||||||
|
2 <= 2; // = true
|
||||||
|
2 >= 2; // = true
|
||||||
|
|
||||||
|
// 以加號(+)進行字串的串接
|
||||||
|
"Hello " + "world!"; // = "Hello world!"
|
||||||
|
|
||||||
|
// 也可以串接字串以外的資料型別
|
||||||
|
"1, 2, " + 3; // = "1, 2, 3"
|
||||||
|
"Hello " + ["world", "!"]; // = "Hello world,!"
|
||||||
|
|
||||||
|
// 這可能導致一些奇怪的行為
|
||||||
|
13 + !0; // 14
|
||||||
|
"13" + !0; // '13true'
|
||||||
|
|
||||||
|
// 以 `<` 和 `>` 進行比較運算
|
||||||
|
"a" < "b"; // = true
|
||||||
|
|
||||||
|
// 使用「兩個等號」(==)做運算時,會執行資料強制轉型
|
||||||
|
"5" == 5; // = true
|
||||||
|
null == undefined; // = true
|
||||||
|
|
||||||
|
// 除非使用「三個等號」(===)
|
||||||
|
"5" === 5; // = false
|
||||||
|
null === undefined; // = false
|
||||||
|
|
||||||
|
// 您可以使用 `charAt` 來取得字串中的字符
|
||||||
|
"This is a string".charAt(0); // = 'T'
|
||||||
|
|
||||||
|
// 或使用 `substring` 獲得更大的區塊。
|
||||||
|
"Hello world".substring(0, 5); // = "Hello"
|
||||||
|
|
||||||
|
// `length` 是一個屬性,因此不要使用 `()`。
|
||||||
|
"Hello".length; // = 5
|
||||||
|
|
||||||
|
// 還有 `null` 和 `undefined` 兩個特殊值。
|
||||||
|
null; // 用於表示刻意指定為空值
|
||||||
|
undefined; // 用來表示目前尚未指定值(儘管 `undefined` 實際上本身是一個值)
|
||||||
|
|
||||||
|
// `false`、`null`、`undefined`、`NaN`、`0` 及 `""`(空字串)皆為偽值(falsy),
|
||||||
|
// 其他的皆為真值(truthy)。
|
||||||
|
// 特別注意,`0` 是偽值,`"0"` 則是真值,儘管 0 == "0"。(因為隱含地轉型)
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
// 2. 變數、陣列與物件
|
||||||
|
|
||||||
|
// 以 `var` 關鍵字宣告變數。JavaScript 是動態型別,因此無需指定型別。
|
||||||
|
// 使用一個等號 `=` 來賦值。
|
||||||
|
var someVar = 5;
|
||||||
|
|
||||||
|
// 若忽略使用 `var` 關鍵字,也不會得到錯誤
|
||||||
|
someOtherVar = 10;
|
||||||
|
|
||||||
|
// ...但是您定義的變數將在自動建立在全域,而不在您定義的作用域中。
|
||||||
|
|
||||||
|
// 若在定義變數時未賦予初始值,則預設為 `undefined`。
|
||||||
|
var someThirdVar; // = undefined
|
||||||
|
|
||||||
|
// 若要一次宣告多個變數,可以使用逗號分隔
|
||||||
|
var someFourthVar = 2, someFifthVar = 4;
|
||||||
|
|
||||||
|
// 變數的數學運算有一些簡寫法:
|
||||||
|
someVar += 5; // 等效於 somevar = somevar + 5,現在為 10
|
||||||
|
someVar *= 10; // 現在 someVar 為 100
|
||||||
|
|
||||||
|
// 對於增減 1 的運算,還有更簡略的寫法:
|
||||||
|
someVar++; // 現在 someVar 為 101
|
||||||
|
someVar--; // 回到 100
|
||||||
|
|
||||||
|
// 陣列是以任何型別的資料所組成、有順序性的列表。
|
||||||
|
var myArray = ["Hello", 45, true];
|
||||||
|
|
||||||
|
// 可使用中括號(方括號)`[]` 語法存取其成員。
|
||||||
|
// 陣列的索引值(index)從 0 開始。
|
||||||
|
myArray[1]; // = 45
|
||||||
|
|
||||||
|
// 陣列是可變的,並有隨成員數量變動的長度 `length`。
|
||||||
|
myArray.push("World");
|
||||||
|
myArray.length; // = 4
|
||||||
|
|
||||||
|
// 於指定的索引值新增/修改陣列的成員
|
||||||
|
myArray[3] = "Hello";
|
||||||
|
|
||||||
|
// 從陣列的最前端或最後端新增或刪除元素
|
||||||
|
myArray.unshift(3); // 新增元素至最前端
|
||||||
|
someVar = myArray.shift(); // 移除第一個元素並回傳
|
||||||
|
myArray.push(3); // 新增元素至最後端
|
||||||
|
someVar = myArray.pop(); // 移除最後一個元素並回傳
|
||||||
|
|
||||||
|
// 以分號 `;` 結合陣列的所有元素
|
||||||
|
var myArray0 = [32, false, "js", 12, 56, 90];
|
||||||
|
myArray0.join(";"); // = "32;false;js;12;56;90"
|
||||||
|
|
||||||
|
// 取得索引 1(包括)到 4(排除)元素的子陣列
|
||||||
|
myArray0.slice(1, 4); // = [false, "js", 12]
|
||||||
|
|
||||||
|
// 從索引 2 開始刪除 4 個元素,然後在那裡插入字串 "hi"、"wr" 和"ld",並回傳刪除的子陣列
|
||||||
|
myArray0.splice(2, 4, "hi", "wr", "ld"); // = ["js", 12, 56, 90]
|
||||||
|
// myArray0 === [32, false, "hi", "wr", "ld"]
|
||||||
|
|
||||||
|
// JavaScript 的物件等同於其他語言中的「字典」(Dictionary)或「映射」(Map):
|
||||||
|
// 無順序性的鍵值對(key value pair)集合。
|
||||||
|
var myObj = { key1: "Hello", key2: "World" };
|
||||||
|
|
||||||
|
// 鍵的名稱是字串,若它們是有效的 JavaScript 標識字,則不需要使用引號。
|
||||||
|
// 值則可以是任何資料型別。
|
||||||
|
var myObj = { myKey: "myValue", "my other key": 4 };
|
||||||
|
|
||||||
|
// 物件屬性也可以使用下標語法存取
|
||||||
|
myObj["my other key"]; // = 4
|
||||||
|
|
||||||
|
// ...或使用 `.` 來存取,前提是該鍵值必須是有效的標識字。
|
||||||
|
myObj.myKey; // = "myValue"
|
||||||
|
|
||||||
|
// 物件是可變的;其值可修改,並可新增新的鍵值。
|
||||||
|
myObj.myThirdKey = true;
|
||||||
|
|
||||||
|
// 如果您嘗試存取不存在的值,將得到 `undefined` 值。
|
||||||
|
myObj.myFourthKey; // = undefined
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
// 3. 邏輯和控制結構
|
||||||
|
|
||||||
|
// `if` 結構,如同其他語言
|
||||||
|
var count = 1;
|
||||||
|
if (count == 3){
|
||||||
|
// count 等於 3 時執行
|
||||||
|
} else if (count == 4){
|
||||||
|
// count 等於 4 時執行
|
||||||
|
} else {
|
||||||
|
// 其他情況下執行
|
||||||
|
}
|
||||||
|
|
||||||
|
// `while` 迴圈
|
||||||
|
while (true){
|
||||||
|
// 無窮迴圈!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do-while 迴圈類似 While 迴圈,但它至少執行一次
|
||||||
|
var input;
|
||||||
|
do {
|
||||||
|
input = getInput();
|
||||||
|
} while (!isValid(input));
|
||||||
|
|
||||||
|
// `for` 迴圈和 C、Java 語言一樣:
|
||||||
|
// 初始化; 繼續條件; 迭代。
|
||||||
|
for (var i = 0; i < 5; i++){
|
||||||
|
// 會執行 5 次
|
||||||
|
}
|
||||||
|
|
||||||
|
// 類似 Java,利用迴圈標籤(label)來終止外層的迴圈
|
||||||
|
outer:
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
for (var j = 0; j < 10; j++) {
|
||||||
|
if (i == 5 && j ==5) {
|
||||||
|
break outer;
|
||||||
|
// 不但終止內部迴圈,也終止外部迴圈
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for/in 陳述式可以迭代物件中的所有屬性。
|
||||||
|
var description = "";
|
||||||
|
var person = { fname: "Paul", lname: "Ken", age: 18 };
|
||||||
|
for (var x in person){
|
||||||
|
description += person[x] + " ";
|
||||||
|
} // description 為 'Paul Ken 18 '
|
||||||
|
|
||||||
|
// for/of 陳述式允許迭代可迭代物件(包括內建的字串、陣列、例如類陣列(array-like)參數
|
||||||
|
// 或 NodeList 物件、Typedarray 物件、映射(Map)和集合(Set),
|
||||||
|
// 及用戶自定義的可迭代物件(iterables))。
|
||||||
|
var myPets = "";
|
||||||
|
var pets = ["cat", "dog", "hamster", "hedgehog"];
|
||||||
|
for (var pet of pets){
|
||||||
|
myPets += pet + " ";
|
||||||
|
} // myPets 為 'cat dog hamster hedgehog '
|
||||||
|
|
||||||
|
// `&&` 是邏輯且(and), `||` 是邏輯或(or)
|
||||||
|
if (house.size == "big" && house.colour == "blue"){
|
||||||
|
house.contains = "bear";
|
||||||
|
}
|
||||||
|
if (colour == "red" || colour == "blue"){
|
||||||
|
// colour 等於 "red" 或 "blue" 時執行
|
||||||
|
}
|
||||||
|
|
||||||
|
// `||` 可用來設定初始值,稱做「短路」(short circuit)陳述式
|
||||||
|
var name = otherName || "default";
|
||||||
|
|
||||||
|
// `switch` 陳述式使用 `===` 來檢查相等性。
|
||||||
|
// 在每個 case 後使用 `break`,否則之後的 case 也會被執行。
|
||||||
|
grade = 'B';
|
||||||
|
switch (grade) {
|
||||||
|
case 'A':
|
||||||
|
console.log("Great job");
|
||||||
|
break;
|
||||||
|
case 'B':
|
||||||
|
console.log("OK job");
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
console.log("You can do better");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("Oy vey");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
// 4. 函式、作用域與閉包
|
||||||
|
|
||||||
|
// JavaScript 函式是使用 `function` 關鍵字來宣告的。
|
||||||
|
function myFunction(thing) {
|
||||||
|
return thing.toUpperCase();
|
||||||
|
}
|
||||||
|
myFunction("foo"); // = "FOO"
|
||||||
|
|
||||||
|
// 值得注意的是,要回傳的值必須開始於關鍵字 `return` 那一行,
|
||||||
|
// 否則會因為自動插入分號,而將回傳 `undefined`。
|
||||||
|
// 在使用 Allman 程式碼風格時要特別注意。
|
||||||
|
function myFunction()
|
||||||
|
{
|
||||||
|
return // <- 分號在此自動插入
|
||||||
|
{
|
||||||
|
thisIsAn: 'object literal'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
myFunction(); // = undefined
|
||||||
|
|
||||||
|
// JavaScript 函式為一等物件(first-class objects),
|
||||||
|
// 所以它們可以被重新賦值給不同的變數,
|
||||||
|
// 並作為參數傳遞給其他函式 - 例如一個事件處理函式:
|
||||||
|
function myFunction() {
|
||||||
|
// 這段程式碼將在 5 秒後執行
|
||||||
|
}
|
||||||
|
setTimeout(myFunction, 5000);
|
||||||
|
// 註:setTimeout 並不是 JS 語言的一部分,而是由瀏覽器和 Node.js 提供的 API。
|
||||||
|
|
||||||
|
// setInterval 是瀏覽器提供的另一個 API
|
||||||
|
function myFunction() {
|
||||||
|
// 這段程式碼每 5 秒執行一次
|
||||||
|
}
|
||||||
|
setInterval(myFunction, 5000);
|
||||||
|
|
||||||
|
// 函式物件甚至不需要用名稱來宣告 - 你可以直接在另一個函數的參數中直接定義匿名函式。
|
||||||
|
setTimeout(function(){
|
||||||
|
// 這段程式碼將在 5 秒後執行
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
// JavaScript 具有函式作用域(scope);函式擁有自己的作用域,但其他區塊(block)則沒有。
|
||||||
|
if (true){
|
||||||
|
var i = 5;
|
||||||
|
}
|
||||||
|
i; // = 5 - 並非在其他語言所預期的 `undefined`
|
||||||
|
|
||||||
|
// 這導致了一種常見的使用方式,即「立即執行匿名函式」,它可以防止臨時變數洩漏到全域作用域。
|
||||||
|
(function(){
|
||||||
|
var temporary = 5;
|
||||||
|
// 我們可以透過賦值給「全域物件」來訪問全域作用域,
|
||||||
|
// 在網頁瀏覽器中,這個全域物件始終是 `window`。
|
||||||
|
// 在非瀏覽器環境中(例如 Node.js),全域物件可能有不同的名稱。
|
||||||
|
window.permanent = 10;
|
||||||
|
})();
|
||||||
|
temporary; // 拋出錯誤 ReferenceError
|
||||||
|
permanent; // = 10
|
||||||
|
|
||||||
|
// 閉包:JavaScript 最強大的特性之一。
|
||||||
|
// 若一個函式在另一個函式內部定義,即使外部函式已經執行結束,
|
||||||
|
// 內部函式仍可以存取外部函式的所有變數。
|
||||||
|
function sayHelloInFiveSeconds(name){
|
||||||
|
var prompt = "Hello, " + name + "!";
|
||||||
|
// 預設情況下,內部函式會被置於局部作用域中,就如同它們是以 `var` 宣告。
|
||||||
|
function inner(){
|
||||||
|
alert(prompt);
|
||||||
|
}
|
||||||
|
setTimeout(inner, 5000);
|
||||||
|
// setTimeout 是非同步的,所以 sayHelloInFiveSeconds 函式會立即退出。
|
||||||
|
// 而 setTimeout 會在之後呼叫 inner 函式。
|
||||||
|
// 然而,由於 inner 函式被「閉合包含」(closed over)在 sayHelloInFiveSeconds 函式中,
|
||||||
|
// 因此當它最終被呼叫時,仍然可以存取 `prompt` 變數。
|
||||||
|
}
|
||||||
|
sayHelloInFiveSeconds("Adam"); // 將在 5 秒後跳出 "Hello, Adam!" 訊息
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
// 5. 更多的物件、建構函式、原型
|
||||||
|
|
||||||
|
// 物件可以包含函式
|
||||||
|
var myObj = {
|
||||||
|
myFunc: function(){
|
||||||
|
return "Hello world!";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
myObj.myFunc(); // 回傳 "Hello world!"
|
||||||
|
|
||||||
|
// 當物件裡的函式被呼叫時,它們可以使用 `this` 關鍵字來存取所屬物件的其他成員。
|
||||||
|
myObj = {
|
||||||
|
myString: "Hello world!",
|
||||||
|
myFunc: function() {
|
||||||
|
return this.myString;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
myObj.myFunc(); // 回傳 "Hello world!"
|
||||||
|
|
||||||
|
// `this` 的設定與函式如何被呼叫有關,而非定義的位置。
|
||||||
|
// 因此,若我們的函式不是在物件的脈絡(context)中被呼叫,就無法正常運作。
|
||||||
|
var myFunc = myObj.myFunc;
|
||||||
|
myFunc(); // = undefined
|
||||||
|
|
||||||
|
// 反之,一個函式可以被賦值給物件並透過 `this` 獲得對它的存取權限,
|
||||||
|
// 即使它在定義時並未依附於該物件上。
|
||||||
|
var myOtherFunc = function() {
|
||||||
|
return this.myString.toUpperCase();
|
||||||
|
};
|
||||||
|
myObj.myOtherFunc = myOtherFunc;
|
||||||
|
myObj.myOtherFunc(); // 回傳 "HELLO WORLD!"
|
||||||
|
|
||||||
|
// 我們也可以在呼叫函式時使用 `call` 或 `apply` 來指定函式的脈絡。
|
||||||
|
var anotherFunc = function(s) {
|
||||||
|
return this.myString + s;
|
||||||
|
};
|
||||||
|
anotherFunc.call(myObj, " And Hello Moon!"); // = "Hello World! And Hello Moon!"
|
||||||
|
|
||||||
|
// `apply` 函式的用法幾乎一樣,差別在於要用陣列的格式傳遞參數
|
||||||
|
anotherFunc.apply(myObj, [" And Hello Sun!"]); // = "Hello World! And Hello Sun!"
|
||||||
|
|
||||||
|
// 這在處理接受一系列參數時很有用,特別是當您想要傳遞一個陣列時。
|
||||||
|
Math.min(42, 6, 27); // = 6
|
||||||
|
Math.min([42, 6, 27]); // = NaN(噢!)
|
||||||
|
Math.min.apply(Math, [42, 6, 27]); // = 6
|
||||||
|
|
||||||
|
// 然而 `call` 和 `apply` 只是暫時的。當我們希望它永久有效時,我們可以使用 `bind`。
|
||||||
|
var boundFunc = anotherFunc.bind(myObj);
|
||||||
|
boundFunc(" And Hello Saturn!"); // = "Hello World! And Hello Saturn!"
|
||||||
|
|
||||||
|
// `bind` 也可以用於部分應用,例如:柯里化(curry)的函式。
|
||||||
|
var product = function(a, b) { return a * b; };
|
||||||
|
var doubler = product.bind(this, 2);
|
||||||
|
doubler(8); // = 16
|
||||||
|
|
||||||
|
// 當您使用 `new` 關鍵字呼叫一個函式時,會建立一個新的物件,
|
||||||
|
// 並透過 `this` 關鍵字使該物件可用於該函式。用此方式呼叫的函式被稱為「建構函式」。
|
||||||
|
var MyConstructor = function(){
|
||||||
|
this.myNumber = 5;
|
||||||
|
};
|
||||||
|
myNewObj = new MyConstructor(); // = { myNumber: 5 }
|
||||||
|
myNewObj.myNumber; // = 5
|
||||||
|
|
||||||
|
// 與大多數其他流行的物件導向語言不同,JavaScript 沒有從類別(class)藍圖建立
|
||||||
|
// 實例(instance)的概念;相反地,JavaScript 將實例化(instantiation)和
|
||||||
|
// 繼承(inheritance)結合成單一的概念:「原型」(prototype)。
|
||||||
|
|
||||||
|
// 每個 JavaScript 物件都有一個「原型」(prototype)。
|
||||||
|
// 當你試圖存取一個物件上不存在的屬性時,直譯器(interpreter)會嘗試查找它的原型。
|
||||||
|
|
||||||
|
// 某些 JS 實作允許你透過屬性 `__proto__` 存取原型。雖然這對於解釋原型很有幫助,
|
||||||
|
// 但它不是標準的操作方式;稍後會介紹使用原型的標準方法。
|
||||||
|
var myObj = {
|
||||||
|
myString: "Hello world!"
|
||||||
|
};
|
||||||
|
var myPrototype = {
|
||||||
|
meaningOfLife: 42,
|
||||||
|
myFunc: function() {
|
||||||
|
return this.myString.toLowerCase();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
myObj.__proto__ = myPrototype;
|
||||||
|
myObj.meaningOfLife; // = 42
|
||||||
|
|
||||||
|
// 函式照常運作。
|
||||||
|
myObj.myFunc(); // = "hello world!"
|
||||||
|
|
||||||
|
// 當然,若您的屬性不在原型上,則會搜尋原型的原型,以此類推。
|
||||||
|
myPrototype.__proto__ = {
|
||||||
|
myBoolean: true
|
||||||
|
};
|
||||||
|
myObj.myBoolean; // = true
|
||||||
|
|
||||||
|
// 這裡沒有涉及任何複製;每個物件都儲存了一個指向其原型的參考。
|
||||||
|
// 這意味著我們可以修改原型,而此修改將反映在任何地方。
|
||||||
|
myPrototype.meaningOfLife = 43;
|
||||||
|
myObj.meaningOfLife; // = 43
|
||||||
|
|
||||||
|
// for/in 陳述句允許循覽物件的屬性,沿著原型鏈向上查找,直到遇到 `null` 原型為止。
|
||||||
|
for (var x in myObj){
|
||||||
|
console.log(myObj[x]);
|
||||||
|
}
|
||||||
|
// 印出:
|
||||||
|
// Hello world!
|
||||||
|
// 43
|
||||||
|
// [Function: myFunc]
|
||||||
|
// true
|
||||||
|
|
||||||
|
// 若只考慮直接附加在物件本身而非其原型上的屬性,請使用 `hasOwnProperty()` 做檢查。
|
||||||
|
for (var x in myObj){
|
||||||
|
if (myObj.hasOwnProperty(x)){
|
||||||
|
console.log(myObj[x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 印出:
|
||||||
|
// Hello world!
|
||||||
|
|
||||||
|
// 我們提到 `__proto__` 是非標準用法,而且沒有標準的方法來修改現有物件的原型。
|
||||||
|
// 然而,有兩種方法可以建立具有指定原型的新物件。
|
||||||
|
|
||||||
|
// 第一種方法是 `Object.create`。
|
||||||
|
var myObj = Object.create(myPrototype);
|
||||||
|
myObj.meaningOfLife; // = 43
|
||||||
|
|
||||||
|
// 第二種方法與建構函式有關,可在任何地方使用。建構函式有一個稱為 `prototype` 的屬性。
|
||||||
|
// 這「不是」建構函式本身的原型;而是當使用該建構函式和 `new` 關鍵字建立新物件時,所賦予的原型。
|
||||||
|
MyConstructor.prototype = {
|
||||||
|
myNumber: 5,
|
||||||
|
getMyNumber: function(){
|
||||||
|
return this.myNumber;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var myNewObj2 = new MyConstructor();
|
||||||
|
myNewObj2.getMyNumber(); // = 5
|
||||||
|
myNewObj2.myNumber = 6;
|
||||||
|
myNewObj2.getMyNumber(); // = 6
|
||||||
|
|
||||||
|
// 內建型別如字串和數字也有建構函式,可以建立等效的包裝物件(wrapper object)。
|
||||||
|
var myNumber = 12;
|
||||||
|
var myNumberObj = new Number(12);
|
||||||
|
myNumber == myNumberObj; // = true
|
||||||
|
|
||||||
|
// 然而,它們並非嚴格相等。
|
||||||
|
typeof myNumber; // = 'number'
|
||||||
|
typeof myNumberObj; // = 'object'
|
||||||
|
myNumber === myNumberObj; // = false
|
||||||
|
if (0){
|
||||||
|
// 這段程式碼不會執行,因為 0 是偽值。
|
||||||
|
}
|
||||||
|
if (new Number(0)){
|
||||||
|
// 這段程式碼將會執行,因為包裝過的數字是物件,而物件皆為真值。
|
||||||
|
}
|
||||||
|
|
||||||
|
// 然而,包裝物件和常見的內建型別共享一個原型,所以你實際上可以為字串擴充功能,例如:
|
||||||
|
String.prototype.firstCharacter = function(){
|
||||||
|
return this.charAt(0);
|
||||||
|
};
|
||||||
|
"abc".firstCharacter(); // = "a"
|
||||||
|
|
||||||
|
// 此技巧常被用於「填充」(polyfilling),即在舊版的 JavaScript 中
|
||||||
|
// 實作新版 JavaScript 才有的功能,以便它們可以在較舊的環境(例如過時的瀏覽器)中使用。
|
||||||
|
|
||||||
|
// 舉例來說,方才提到的 `Object.create` 並非在舊瀏覽器上使用,
|
||||||
|
// 但我們仍然可以透過這個 polyfill 來補足其功能:
|
||||||
|
if (Object.create === undefined){ // 若此方法存在,則不覆蓋
|
||||||
|
Object.create = function(proto){
|
||||||
|
// 製作一個具有正確原型的暫時建構函式
|
||||||
|
var Constructor = function(){};
|
||||||
|
Constructor.prototype = proto;
|
||||||
|
// 然後使用它來建立一個具有適當原型的新物件
|
||||||
|
return new Constructor();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ES6 語法
|
||||||
|
|
||||||
|
// `let` 關鍵字讓您在語彙作用域(lexical scope)中定義變數,
|
||||||
|
// 而不像 `var` 關鍵字那樣在函式作用域(function scope)中定義。
|
||||||
|
let name = "Billy";
|
||||||
|
|
||||||
|
// 以 `let` 關鍵字定義的變數,可以賦予新值。
|
||||||
|
name = "William";
|
||||||
|
|
||||||
|
// `const` 關鍵字如同 `let` 可在語彙作用域中定義變數,差別在於歐,一旦賦值後就不能更改其值。
|
||||||
|
const pi = 3.14;
|
||||||
|
|
||||||
|
pi = 4.13; // 此操作並不合法。
|
||||||
|
|
||||||
|
// ES6 中有一種新的函式語法,稱為「lambda 語法」(lambda syntax)。
|
||||||
|
// 這允許函式在語彙作用域中定義,如同 `const` 和 `let` 來定義變數。
|
||||||
|
const isEven = (number) => {
|
||||||
|
return number % 2 === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
isEven(7); // 回傳 false 值
|
||||||
|
|
||||||
|
// 「等效」於以下傳統函式的宣告方式:
|
||||||
|
function isEven(number) {
|
||||||
|
return number % 2 === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 前面特別強調「等效」一詞,是因為使用 lambda 語法定義的函式不能在定義之前被呼叫。
|
||||||
|
// 以下爲錯誤示範:
|
||||||
|
add(1, 8);
|
||||||
|
|
||||||
|
const add = (firstNumber, secondNumber) => {
|
||||||
|
return firstNumber + secondNumber;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 延伸閱讀
|
||||||
|
|
||||||
|
[Mozilla 開發者網路(Mozilla Developer Network)][1] 為 JavaScript 詳細的文件,主要針對瀏覽器環境。此外,它是一個維基百科,當你學到更多時,你可以透過分享自己的知識來幫助他人。
|
||||||
|
|
||||||
|
MDN 的 [重新介紹 JavaScript][2] 一文中,提到關於本文之外的更多細節。本文聚焦於 JavaScript 語言本身;若您想學習更多關於如何在網頁中使用 JavaScript,可以從閱讀 [文件物件模型][3] 開始。
|
||||||
|
|
||||||
|
[Learn Javascript by Example and with Challenges][4] 是另一個參考網站,其包含線上的實戰練習。
|
||||||
|
|
||||||
|
[JavaScript Garden][5] 是一份深入探討這門語言所有反直覺部分的指南。
|
||||||
|
|
||||||
|
[JavaScript 大全][6] 是一本經典的指南和參考書。
|
||||||
|
|
||||||
|
[精通 Javascript][8] 由 Marijn Haverbeke 所著,是一本優秀的 JS 書籍/電子書,附帶終端機指令
|
||||||
|
|
||||||
|
[Javascript: The Right Way][10] 是一本指南,旨在向新入門的開發者介紹 JavaScript,並幫助有經驗的開發者學習更多關於其最佳實踐的知識。
|
||||||
|
|
||||||
|
[現代 JavaScript 教學][11] 為現代的 JavaScript 教學網站,涵蓋了基礎知識(核心語言和瀏覽器操作)以及進階主題,並提供簡明扼要的解釋。
|
||||||
|
|
||||||
|
除了本文的直接貢獻者外,部分內容改編自本站 Louie Dinh 的 Python 教學,以及 MDN 的 [重新介紹 JavaScript][2]。
|
||||||
|
|
||||||
|
[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript
|
||||||
|
[2]: https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Language_overview
|
||||||
|
[3]: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Using_the_Document_Object_Model
|
||||||
|
[4]: http://www.learneroo.com/modules/64/nodes/350
|
||||||
|
[5]: https://shamansir.github.io/JavaScript-Garden/zhtw/
|
||||||
|
[6]: https://www.tenlong.com.tw/products/9789865027322
|
||||||
|
[8]: https://www.tenlong.com.tw/products/9789865029890?list_name=srh
|
||||||
|
[10]: http://jstherightway.org/
|
||||||
|
[11]: https://javascriptinfo.dev.org.tw
|
286
zh-tw/typescript-tw.html.markdown
Normal file
286
zh-tw/typescript-tw.html.markdown
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
---
|
||||||
|
language: TypeScript
|
||||||
|
contributors:
|
||||||
|
- ["Philippe Vlérick", "https://github.com/pvlerick"]
|
||||||
|
- ["Kiwimoe", "https://github.com/kiwimoe"]
|
||||||
|
translators:
|
||||||
|
- ["Woody Chang", "https://github.com/kazettique"]
|
||||||
|
filename: learntypescript-zh-tw.ts
|
||||||
|
lang: zh-tw
|
||||||
|
---
|
||||||
|
|
||||||
|
TypeScript 是為開發大型 JavaScript 應用程式而設計的語言。它為 JavaScript 導入某些程式語言常見的一些概念,諸如:類別(class)、模組(module)、介面(interface)、泛型(generic type)和靜態型別(static type)。TypeScript 是 JavaScript 的「超集」(superset):意即建立在 JavaScript 的基礎上,所有 JavaScript 語法皆可在 TypeScript 中使用。因此,TypeScript 可以無縫導入到任何 JavaScript 專案中。TypeScript 編譯器最終會編譯成 JavaScript 程式碼。
|
||||||
|
|
||||||
|
本文將只專注於 TypeScript 的額外語法,其他請參考 [JavaScript 的指南](/docs/javascript-tw)
|
||||||
|
|
||||||
|
要測試 TypeScript 的編譯器,請前往 [Playground](https://www.typescriptlang.org/play),在那裡你可以輸入程式碼,獲得自動完成(autocomplete)功能,並查看編譯過的 JavaScript 程式碼。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// TS 基本型別有三種
|
||||||
|
let isDone: boolean = false;
|
||||||
|
let lines: number = 42;
|
||||||
|
let name: string = "Anders";
|
||||||
|
|
||||||
|
// 當變數有賦值時,也可以省略型別定義
|
||||||
|
let isDone = false;
|
||||||
|
let lines = 42;
|
||||||
|
let name = "Anders";
|
||||||
|
|
||||||
|
// 若無法確定型別,則可以定義為 `any`
|
||||||
|
let notSure: any = 4;
|
||||||
|
notSure = "maybe a string instead";
|
||||||
|
notSure = false; // 布林值也屬於 `any` 型別
|
||||||
|
|
||||||
|
// 以 `const` 關鍵字定義常數
|
||||||
|
const numLivesForCat = 9;
|
||||||
|
numLivesForCat = 1; // 報錯,常數初始化之後,無法指定新值
|
||||||
|
|
||||||
|
// 關於集合類型的資料,有型別化陣列(typed array)和泛型陣列(generic array)
|
||||||
|
let list: number[] = [1, 2, 3];
|
||||||
|
// 或使用泛型陣列類型
|
||||||
|
let list: Array<number> = [1, 2, 3];
|
||||||
|
|
||||||
|
// 列舉型別:
|
||||||
|
enum Color { Red, Green, Blue };
|
||||||
|
let c: Color = Color.Green;
|
||||||
|
console.log(Color[c]); // "Green"
|
||||||
|
|
||||||
|
// `void` 用於函式不回傳任何值的特殊情況
|
||||||
|
function bigHorribleAlert(): void {
|
||||||
|
alert("I'm a little annoying box!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 函式是一等公民,支援 lambda「胖箭頭」 `=>` 語法,並使用型別推斷
|
||||||
|
|
||||||
|
// 以下幾種函式寫法是等效的,編譯器會生成相同的 JavaScript 程式碼
|
||||||
|
// 一般的函式
|
||||||
|
let f1 = function (i: number): number { return i * i; }
|
||||||
|
// 自動推斷回傳型別
|
||||||
|
let f2 = function (i: number) { return i * i; }
|
||||||
|
// 使用胖箭頭語法
|
||||||
|
let f3 = (i: number): number => { return i * i; }
|
||||||
|
// 胖箭頭語法(自動推斷回傳型別)
|
||||||
|
let f4 = (i: number) => { return i * i; }
|
||||||
|
// 胖箭頭語法(自動推斷回傳型別、省略函式的括號與 `return` 關鍵字)
|
||||||
|
let f5 = (i: number) => i * i;
|
||||||
|
|
||||||
|
// 函式的參數也可以同時定義多種型別的連集
|
||||||
|
function f6(i: string | number): void {
|
||||||
|
console.log("The value was " + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 介面是結構化的,任何擁有這些屬性的物件都要符合該介面的定義
|
||||||
|
interface Person {
|
||||||
|
name: string;
|
||||||
|
// 以問號(`?`)來表示選填的屬性
|
||||||
|
age?: number;
|
||||||
|
// 當然也可以包含函式
|
||||||
|
move(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 實作 `Person` 介面的物件
|
||||||
|
// 可被視為一個 `Person` 物件,因為它具有 `name` 和 `move` 屬性
|
||||||
|
let p: Person = { name: "Bobby", move: () => { } };
|
||||||
|
// 包含選填屬性的物件:
|
||||||
|
let validPerson: Person = { name: "Bobby", age: 42, move: () => { } };
|
||||||
|
// 此物件非 `Person` 物件,因為 `age` 屬性非數字
|
||||||
|
let invalidPerson: Person = { name: "Bobby", age: true };
|
||||||
|
|
||||||
|
// 介面也可以描述一個函式的型別
|
||||||
|
interface SearchFunc {
|
||||||
|
(source: string, subString: string): boolean;
|
||||||
|
}
|
||||||
|
// 函式的型別定義著重於各個參數以及回傳值的型別,而函式名稱並不重要
|
||||||
|
let mySearch: SearchFunc;
|
||||||
|
mySearch = function (src: string, sub: string) {
|
||||||
|
return src.search(sub) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 類別的屬性,其存取權限預設為公開(public)
|
||||||
|
class Point {
|
||||||
|
// 定義屬性
|
||||||
|
x: number;
|
||||||
|
|
||||||
|
// 在建構函式種使用 `public`、`private` 關鍵字,會實例化的時候自動生成屬性
|
||||||
|
// 以此為例,`y` 如同 `x` 定義其屬性,並於實例化時賦值,但寫法更為簡潔,同時支援預設值
|
||||||
|
constructor(x: number, public y: number = 0) {
|
||||||
|
this.x = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 函式,在類別中,又稱為方法(method)
|
||||||
|
dist(): number {
|
||||||
|
return Math.sqrt(this.x * this.x + this.y * this.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 靜態成員(static member)
|
||||||
|
static origin = new Point(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 類別可以被明確標記為實作某個介面。
|
||||||
|
// 任何缺少的屬性或方法都會在編譯時引發錯誤。
|
||||||
|
class PointPerson implements Person {
|
||||||
|
name: string
|
||||||
|
move() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let p1 = new Point(10, 20);
|
||||||
|
let p2 = new Point(25); // y 值將預設為 0
|
||||||
|
|
||||||
|
// 類別的繼承
|
||||||
|
class Point3D extends Point {
|
||||||
|
constructor(x: number, y: number, public z: number = 0) {
|
||||||
|
super(x, y); // 必須明確呼叫父類別的建構函式,使用 `super` 關鍵字
|
||||||
|
}
|
||||||
|
|
||||||
|
// 複寫父類別的方法
|
||||||
|
dist(): number {
|
||||||
|
let d = super.dist();
|
||||||
|
return Math.sqrt(d * d + this.z * this.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模組,以 `.` 語法存取子模組
|
||||||
|
module Geometry {
|
||||||
|
export class Square {
|
||||||
|
constructor(public sideLength: number = 0) {
|
||||||
|
}
|
||||||
|
area() {
|
||||||
|
return Math.pow(this.sideLength, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let s1 = new Geometry.Square(5);
|
||||||
|
|
||||||
|
// 引用模組,可以在本地使用別名命名並使用之
|
||||||
|
import G = Geometry;
|
||||||
|
|
||||||
|
let s2 = new G.Square(10);
|
||||||
|
|
||||||
|
// 泛用型別,泛型(generic type)
|
||||||
|
// 在類別使用泛型
|
||||||
|
class Tuple<T1, T2> {
|
||||||
|
constructor(public item1: T1, public item2: T2) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在介面使用泛型
|
||||||
|
interface Pair<T> {
|
||||||
|
item1: T;
|
||||||
|
item2: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在函式使用泛型
|
||||||
|
let pairToTuple = function <T>(p: Pair<T>) {
|
||||||
|
return new Tuple(p.item1, p.item2);
|
||||||
|
};
|
||||||
|
|
||||||
|
let tuple = pairToTuple({ item1: "hello", item2: "world" });
|
||||||
|
|
||||||
|
// 引用型別定義檔:
|
||||||
|
/// <reference path="jquery.d.ts" />
|
||||||
|
|
||||||
|
// 樣板字串(template string)(使用反引號「`」的字串)
|
||||||
|
// 以樣板字串進行字串內插(interpolation)
|
||||||
|
let name = 'Tyrone';
|
||||||
|
let greeting = `Hi ${name}, how are you?`
|
||||||
|
// 多行的樣板字串
|
||||||
|
let multiline = `This is an example
|
||||||
|
of a multiline string`;
|
||||||
|
|
||||||
|
// 唯讀存取子:TypeScript 3.1 的新語法
|
||||||
|
interface Person {
|
||||||
|
readonly name: string;
|
||||||
|
readonly age: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
var p1: Person = { name: "Tyrone", age: 42 };
|
||||||
|
p1.age = 25; // 錯誤,`p1.age` 為唯讀屬性
|
||||||
|
|
||||||
|
var p2 = { name: "John", age: 60 };
|
||||||
|
var p3: Person = p2; // 正確,`p2` 的唯讀別名
|
||||||
|
p3.age = 35; // 錯誤,`p3.age` 為唯讀屬性
|
||||||
|
p2.age = 45; // 正確,但因為 `p3` 參照可 `p2`,因此 `p3.age` 將會被修改
|
||||||
|
|
||||||
|
class Car {
|
||||||
|
readonly make: string;
|
||||||
|
readonly model: string;
|
||||||
|
readonly year = 2018;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.make = "Unknown Make"; // 唯讀屬性在建構函式被允許賦值
|
||||||
|
this.model = "Unknown Model"; // 唯讀屬性在建構函式被允許賦值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let numbers: Array<number> = [0, 1, 2, 3, 4];
|
||||||
|
let moreNumbers: ReadonlyArray<number> = numbers;
|
||||||
|
moreNumbers[5] = 5; // 錯誤,陣列的成員為唯讀
|
||||||
|
moreNumbers.push(5); // 錯誤,無 `push` 方法(因為 `push` 方法會改變陣列的值)
|
||||||
|
moreNumbers.length = 3; // 錯誤,`length` 為唯讀
|
||||||
|
numbers = moreNumbers; // 錯誤,修改陣列的方法並不存在於唯讀陣列
|
||||||
|
|
||||||
|
// 可以使用聯合型別(union type)來定義不同的資料型別
|
||||||
|
type State =
|
||||||
|
| { type: "loading" }
|
||||||
|
| { type: "success", value: number }
|
||||||
|
| { type: "error", message: string };
|
||||||
|
|
||||||
|
declare const state: State;
|
||||||
|
if (state.type === "success") {
|
||||||
|
console.log(state.value);
|
||||||
|
} else if (state.type === "error") {
|
||||||
|
console.error(state.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 樣板實字(template literal)型別
|
||||||
|
// 可以用來建立複雜的字串型別
|
||||||
|
type OrderSize = "regular" | "large";
|
||||||
|
type OrderItem = "Espresso" | "Cappuccino";
|
||||||
|
type Order = `A ${OrderSize} ${OrderItem}`;
|
||||||
|
|
||||||
|
let order1: Order = "A regular Cappuccino";
|
||||||
|
let order2: Order = "A large Espresso";
|
||||||
|
let order3: Order = "A small Espresso"; // 錯誤
|
||||||
|
|
||||||
|
// 迭代器(iterator)與產生器(generator)
|
||||||
|
|
||||||
|
// for..of 陳述式
|
||||||
|
// 循覽物件的每個成員「值」(value)
|
||||||
|
let arrayOfAnyType = [1, "string", false];
|
||||||
|
for (const val of arrayOfAnyType) {
|
||||||
|
console.log(val); // 1, "string", false
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = [4, 5, 6];
|
||||||
|
for (const i of list) {
|
||||||
|
console.log(i); // 4, 5, 6
|
||||||
|
}
|
||||||
|
|
||||||
|
// for..in 陳述式
|
||||||
|
// 循覽物件的每個成員的「鍵」(key)
|
||||||
|
for (const i in list) {
|
||||||
|
console.log(i); // 0, 1, 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// 型別斷言(assertion)
|
||||||
|
let foo = {} // 建立一個名為 `foo` 的空物件
|
||||||
|
foo.bar = 123 // 錯誤,`bar` 屬性並不存在於 `{}`
|
||||||
|
foo.baz = 'hello world' // 錯誤:`baz` 屬性並不存在於 `{}`
|
||||||
|
|
||||||
|
// 因為 `foo` 的推斷型別是 `{}`(一個無任何屬性的物件),所以不允許新增 `bar`、`baz` 及其他任何名稱的屬性。然而,通過型別斷言,以下程式碼將能夠通過 TS 的檢查:
|
||||||
|
interface Foo {
|
||||||
|
bar: number;
|
||||||
|
baz: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let foo = {} as Foo; // 這裡使用型別斷言
|
||||||
|
foo.bar = 123;
|
||||||
|
foo.baz = 'hello world'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 延伸閱讀
|
||||||
|
|
||||||
|
* [TypeScript官網](https://www.typescriptlang.org/)
|
||||||
|
* [TypeScript原始碼](https://github.com/microsoft/TypeScript)
|
||||||
|
* [Learn TypeScript](https://learntypescript.dev/)
|
Loading…
Reference in New Issue
Block a user