go中的關鍵字-defer

1. defer的使用

  defer 延遲調用。我們先來看一下,有defer關鍵字的代碼執行順序:

1 func main() {
2     defer func() {
3         fmt.Println("1號輸出")
4     }()
5     defer func() {
6         fmt.Println("2號輸出")
7     }()
8 }

  輸出結果:

1 2號出來
2 1號出來

  結論:多個defer的執行順序是倒序執行(同入棧先進后出)。

  由例子可以看出來,defer有延遲生效的作用,先使用defer的語句延遲到最後執行。

1.1 defer與返回值之間的順序

 1 func defertest() int
 2 
 3 func main() {
 4     fmt.Println("main:", defertest())
 5 }
 6 
 7 func defertest() int {
 8     var i int
 9     defer func() {
10         i++
11         fmt.Println("defer2的值:", i)
12     }()
13     defer func() {
14         i++
15         fmt.Println("defer1的值:", i)
16     }()
17     return i
18 }

  輸出結果:

1 defer1的值: 1
2 defer2的值: 2
3 main: 0

  結論:return最先執行->return負責將結果寫入返回值中->接着defer開始執行一些收尾工作->最後函數攜帶當前返回值退出

   return的時候已經先將返回值給定義下來了,就是0,由於i是在函數內部聲明所以即使在defer中進行了++操作,也不會影響return的時候做的決定。

 1 func test() (i int)
 2 
 3 func main() {
 4     fmt.Println("main:", test())
 5 }
 6 
 7 func test() (i int) {
 8     defer func() {
 9         i++
10         fmt.Println("defer2的值:", i)
11     }()
12     defer func() {
13         i++
14         fmt.Println("defer1的值:", i)
15     }()
16     return i
17 }

  詳解:由於返回值提前聲明了,所以在return的時候決定的返回值還是0,但是後面兩個defer執行後進行了兩次++,將i的值變為2,待defer執行完后,函數將i值進行了返回。

2. defer定義和執行

 1 func test(i *int) int {
 2     return *i
 3 }
 4 
 5 func main(){
 6     var i = 1
 7 
 8     // defer定義的時候test(&i)的值就已經定了,是1,後面就不會變了
 9     defer fmt.Println("i1 ="  , test(&i))
10     i++
11 
12     // defer定義的時候test(&i)的值就已經定了,是2,後面就不會變了
13     defer fmt.Println("i2 ="  , test(&i))
14 
15     // defer定義的時候,i就已經確定了是一個指針類型,地址上的值變了,這裏跟着變
16     defer func(i *int) {
17         fmt.Println("i3 ="  , *i)
18     }(&i)
19 
20     // defer定義的時候i的值就已經定了,是2,後面就不會變了
21     defer func(i int) {
22         //defer 在定義的時候就定了
23         fmt.Println("i4 ="  , i)
24     }(i)
25 
26     defer func() {
27         // 地址,所以後續跟着變
28         var c = &i
29         fmt.Println("i5 ="  , *c)
30     }()
31 
32     // 執行了 i=11 后才調用,此時i值已是11
33     defer func() {
34         fmt.Println("i6 ="  , i)
35     }()
36 
37     i = 11
38 }

  結論:會先將defer后函數的參數部分的值(或者地址)給先下來【你可以理解為()裡頭的會先確定】,後面函數執行完,才會執行defer后函數的{}中的邏輯。

例題分析

 1 //例子1
 2 func f() (result int) {
 3     defer func() {
 4         result++
 5     }()
 6     return 0
 7 }
 8 //例子2
 9 func f() (r int) {
10      t := 5
11      defer func() {
12        t = t + 5
13      }()
14      return t
15 }
16 //例子3
17 func f() (r int) {
18     defer func(r int) {
19           r = r + 5
20     }(r)
21     return 1
22 }

  例1的正確答案不是0,例2的正確答案不是10,例3的正確答案不是6……

  這裏先說一下返回值。defer是在return之前執行的。這條規則毋庸置疑,但最重要的一點是要明白,return xxx這一條語句並不是一條原子指令!

  函數返回的過程:先給返回值賦值,然後調用defer表達式,最後才是返回到調用函數中。defer表達式可能會在設置函數返回值之後,且在返回到調用函數之前去修改返回值,使最終的函數返回值與你想象的不一致。

  return xxx 可被改寫成:

1 返回值 = xxx
2 調用defer函數
3 空的return

  所以例子也可以改寫成:

 1 //例1
 2 func f() (result int) {
 3      result = 0  //return語句不是一條原子調用,return xxx其實是賦值+ret指令
 4      func() { //defer被插入到return之前執行,也就是賦返回值和ret指令之間
 5          result++
 6      }()
 7      return
 8 }
 9 //例2
10 func f() (r int) {
11      t := 5
12      r = t //賦值指令
13      func() {        //defer被插入到賦值與返回之間執行,這個例子中返回值r沒被修改過
14          t = t + 5
15      }
16      return        //空的return指令
17 }
18 例3
19 func f() (r int) {
20      r = 1  //給返回值賦值
21      func(r int) {        //這裏改的r是傳值傳進去的r,不會改變要返回的那個r值
22           r = r + 5
23      }(r)
24      return        //空的return
25 }

  所以例1的結果是1,例2的結果是5,例3的結果是1.

3. defer內部原理

  從例子開始看:

1 packmage main
2 
3 import()
4 
5 func main() {
6   defer println("這是一個測試")
7 }

  反編譯一下看看:

 1   src $ go build -o test test.go
 2   src $ go tool objdump -s "main\.main" test
 1 TEXT main.main(SB) /Users/tushanshan/go/src/test3.go
 2   test3.go:5        0x104ea70        65488b0c2530000000      MOVQ GS:0x30, CX
 3   test3.go:5        0x104ea79        483b6110                CMPQ 0x10(CX), SP
 4   test3.go:5        0x104ea7d        765f                    JBE 0x104eade
 5   test3.go:5        0x104ea7f        4883ec28                SUBQ $0x28, SP
 6   test3.go:5        0x104ea83        48896c2420              MOVQ BP, 0x20(SP)
 7   test3.go:5        0x104ea88        488d6c2420              LEAQ 0x20(SP), BP
 8   test3.go:6        0x104ea8d        c7042410000000          MOVL $0x10, 0(SP)
 9   test3.go:6        0x104ea94        488d05e5290200          LEAQ go.func.*+57(SB), AX
10   test3.go:6        0x104ea9b        4889442408              MOVQ AX, 0x8(SP)
11   test3.go:6        0x104eaa0        488d05e6e50100          LEAQ go.string.*+173(SB), AX
12   test3.go:6        0x104eaa7        4889442410              MOVQ AX, 0x10(SP)
13   test3.go:6        0x104eaac        48c744241804000000      MOVQ $0x4, 0x18(SP)
14   test3.go:6        0x104eab5        e8b631fdff              CALL runtime.deferproc(SB)
15   test3.go:6        0x104eaba        85c0                    TESTL AX, AX
16   test3.go:6        0x104eabc        7510                    JNE 0x104eace
17   test3.go:7        0x104eabe        90                      NOPL
18   test3.go:7        0x104eabf        e83c3afdff              CALL runtime.deferreturn(SB)
19   test3.go:7        0x104eac4        488b6c2420              MOVQ 0x20(SP), BP
20   test3.go:7        0x104eac9        4883c428                ADDQ $0x28, SP
21   test3.go:7        0x104eacd        c3                      RET
22   test3.go:6        0x104eace        90                      NOPL
23   test3.go:6        0x104eacf        e82c3afdff              CALL runtime.deferreturn(SB)
24   test3.go:6        0x104ead4        488b6c2420              MOVQ 0x20(SP), BP
25   test3.go:6        0x104ead9        4883c428                ADDQ $0x28, SP
26   test3.go:6        0x104eadd        c3                      RET
27   test3.go:5        0x104eade        e8cd84ffff              CALL runtime.morestack_noctxt(SB)
28   test3.go:5        0x104eae3        eb8b                    JMP main.main(SB)
29   :-1               0x104eae5        cc                      INT $0x3
30   :-1               0x104eae6        cc                      INT $0x3
31   :-1               0x104eae7        cc                      INT $0x3

   編譯器將defer處理成兩個函數調用 deferproc 定義一個延遲調用對象,然後在函數結束前通過 deferreturn 完成最終調用。在defer出現的地方,插入了指令call runtime.deferproc,然後在函數返回之前的地方,插入指令call runtime.deferreturn。

內部結構

 1 //defer
 2 type _defer struct {
 3    siz     int32   // 參數的大小
 4    started bool    // 是否執行過了
 5    sp      uintptr // sp at time of defer
 6    pc      uintptr
 7    fn      *funcval 
 8    _panic  *_panic // defer中的panic
 9    link    *_defer // defer鏈表,函數執行流程中的defer,會通過 link這個 屬性進行串聯
10 }
11 //panic
12 type _panic struct {
13    argp      unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
14    arg       interface{}    // argument to panic
15    link      *_panic        // link to earlier panic
16    recovered bool           // whether this panic is over
17    aborted   bool           // the panic was aborted
18 }
19 //g
20 type g struct {
21    _panic         *_panic // panic組成的鏈表
22    _defer         *_defer // defer組成的先進后出的鏈表,同棧
23 }

  因為 defer panic 都是綁定在運行的g上的,這裏也說一下g中與 defer panic相關的屬性

  再把defer, panic, recover放一起看一下:

1 func main() {
2     defer func() {
3         recover()
4     }()
5     panic("error")
6 }

  反編譯結果:

1 go build -gcflags=all="-N -l" main.go
2 go tool objdump -s "main.main" main
1 go tool objdump -s "main\.main" main | grep CALL
2   main.go:4             0x4548d0                e81b00fdff              CALL runtime.deferproc(SB)              
3   main.go:7             0x4548f2                e8b90cfdff              CALL runtime.gopanic(SB)                
4   main.go:4             0x4548fa                e88108fdff              CALL runtime.deferreturn(SB)            
5   main.go:3             0x454909                e85282ffff              CALL runtime.morestack_noctxt(SB)       
6   main.go:5             0x4549a6                e8d511fdff              CALL runtime.gorecover(SB)              
7   main.go:4             0x4549b5                e8a681ffff              CALL runtime.morestack_noctxt(SB)

  defer 關鍵字首先會調用 runtime.deferproc 定義一個延遲調用對象,然後再函數結束前,調用 runtime.deferreturn 來完成 defer 定義的函數的調用

  panic 函數就會調用 runtime.gopanic 來實現相關的邏輯

  recover 則調用 runtime.gorecover 來實現 recover 的功能

deferproc

  根據 defer 關鍵字後面定義的函數 fn 以及 參數的size,來創建一個延遲執行的 函數,並將這個延遲函數,掛在到當前g的 _defer 的鏈表上,下面是deferproc的實現:

 1 func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
 2    sp := getcallersp()
 3    argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
 4    callerpc := getcallerpc()
 5    // 獲取一個_defer對象, 並放入g._defer鏈表的頭部
 6    d := newdefer(siz)
 7      // 設置defer的fn pc sp等,後面調用
 8    d.fn = fn
 9    d.pc = callerpc
10    d.sp = sp
11    switch siz {
12    case 0:
13       // Do nothing.
14    case sys.PtrSize:
15       // _defer 後面的內存 存儲 argp的地址信息
16       *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
17    default:
18       // 如果不是指針類型的參數,把參數拷貝到 _defer 的後面的內存空間
19       memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))
20    }
21    return0()
22 }

  通過newproc 獲取一個 _defer 的對象,並加入到當前g的 _defer 鏈表的頭部,然後再把參數或參數的指針拷貝到 獲取到的 _defer對象的後面的內存空間。

  再看看newdefer 的實現:

 1 func newdefer(siz int32) *_defer {
 2    var d *_defer
 3    // 根據 size 通過deferclass判斷應該分配的 sizeclass,就類似於 內存分配預先確定好幾個sizeclass,然後根據size確定sizeclass,找對應的緩存的內存塊
 4    sc := deferclass(uintptr(siz))
 5    gp := getg()
 6    // 如果sizeclass在既定的sizeclass範圍內,去g綁定的p上找
 7    if sc < uintptr(len(p{}.deferpool)) {
 8       pp := gp.m.p.ptr()
 9       if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil {
10          // 當前sizeclass的緩存數量==0,且不為nil,從sched上獲取一批緩存
11          systemstack(func() {
12             lock(&sched.deferlock)
13             for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {
14                d := sched.deferpool[sc]
15                sched.deferpool[sc] = d.link
16                d.link = nil
17                pp.deferpool[sc] = append(pp.deferpool[sc], d)
18             }
19             unlock(&sched.deferlock)
20          })
21       }
22       // 如果從sched獲取之後,sizeclass對應的緩存不為空,分配
23       if n := len(pp.deferpool[sc]); n > 0 {
24          d = pp.deferpool[sc][n-1]
25          pp.deferpool[sc][n-1] = nil
26          pp.deferpool[sc] = pp.deferpool[sc][:n-1]
27       }
28    }
29    // p和sched都沒有找到 或者 沒有對應的sizeclass,直接分配
30    if d == nil {
31       // Allocate new defer+args.
32       systemstack(func() {
33          total := roundupsize(totaldefersize(uintptr(siz)))
34          d = (*_defer)(mallocgc(total, deferType, true))
35       })
36    }
37    d.siz = siz
38    // 插入到g._defer的鏈表頭
39    d.link = gp._defer
40    gp._defer = d
41    return d
42 }

  newdefer的作用是獲取一個_defer對象, 並推入 g._defer鏈表的頭部。根據size獲取sizeclass,對sizeclass進行分類緩存,這是內存分配時的思想,先去p上分配,然後批量從全局 sched上獲取到本地緩存,這種二級緩存的思想真的在go源碼的各個部分都有。

deferreturn

 1 func deferreturn(arg0 uintptr) {
 2    gp := getg()
 3    // 獲取g defer鏈表的第一個defer,也是最後一個聲明的defer
 4    d := gp._defer
 5    // 沒有defer,就不需要干什麼事了
 6    if d == nil {
 7       return
 8    }
 9    sp := getcallersp()
10    // 如果defer的sp與callersp不匹配,說明defer不對應,有可能是調用了其他棧幀的延遲函數
11    if d.sp != sp {
12       return
13    }
14    // 根據d.siz,把原先存儲的參數信息獲取並存儲到arg0裏面
15    switch d.siz {
16    case 0:
17       // Do nothing.
18    case sys.PtrSize:
19       *(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
20    default:
21       memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
22    }
23    fn := d.fn
24    d.fn = nil
25    // defer用過了就釋放了,
26    gp._defer = d.link
27    freedefer(d)
28    // 跳轉到執行defer
29    jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
30 }

freedefer

  釋放defer用到的函數,應該跟調度器、內存分配的思想是一樣的。

 1 func freedefer(d *_defer) {
 2    // 判斷defer的sizeclass
 3    sc := deferclass(uintptr(d.siz))
 4    // 超出既定的sizeclass範圍的話,就是直接分配的內存,那就不管了
 5    if sc >= uintptr(len(p{}.deferpool)) {
 6       return
 7    }
 8    pp := getg().m.p.ptr()
 9    // p本地sizeclass對應的緩衝區滿了,批量轉移一半到全局sched
10    if len(pp.deferpool[sc]) == cap(pp.deferpool[sc]) {
11       // 使用g0來轉移
12       systemstack(func() {
13          var first, last *_defer
14          for len(pp.deferpool[sc]) > cap(pp.deferpool[sc])/2 {
15             n := len(pp.deferpool[sc])
16             d := pp.deferpool[sc][n-1]
17             pp.deferpool[sc][n-1] = nil
18             pp.deferpool[sc] = pp.deferpool[sc][:n-1]
19             // 先將需要轉移的那批defer對象串成一個鏈表
20             if first == nil {
21                first = d
22             } else {
23                last.link = d
24             }
25             last = d
26          }
27          lock(&sched.deferlock)
28          // 把這個鏈表放到sched.deferpool對應sizeclass的鏈表頭
29          last.link = sched.deferpool[sc]
30          sched.deferpool[sc] = first
31          unlock(&sched.deferlock)
32       })
33    }
34    // 清空當前要釋放的defer的屬性
35    d.siz = 0
36    d.started = false
37    d.sp = 0
38    d.pc = 0
39    d.link = nil
40 
41    pp.deferpool[sc] = append(pp.deferpool[sc], d)
42 }

gopanic

 1 func gopanic(e interface{}) {
 2    gp := getg()
 3 
 4    var p _panic
 5    p.arg = e
 6    p.link = gp._panic
 7    gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
 8 
 9    atomic.Xadd(&runningPanicDefers, 1)
10    // 依次執行 g._defer鏈表的defer對象
11    for {
12       d := gp._defer
13       if d == nil {
14          break
15       }
16 
17       // If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),
18       // take defer off list. The earlier panic or Goexit will not continue running.
19       // 正常情況下,defer執行完成之後都會被移除,既然這個defer沒有移除,原因只有兩種: 1. 這個defer裏面引發了panic 2. 這個defer裏面引發了 runtime.Goexit,但是這個defer已經執行過了,需要移除,如果引發這個defer沒有被移除是第一個原因,那麼這個panic也需要移除,因為這個panic也執行過了,這裏給panic增加標誌位,以待後續移除
20       if d.started {
21          if d._panic != nil {
22             d._panic.aborted = true
23          }
24          d._panic = nil
25          d.fn = nil
26          gp._defer = d.link
27          freedefer(d)
28          continue
29       }
30       d.started = true
31 
32       // Record the panic that is running the defer.
33       // If there is a new panic during the deferred call, that panic
34       // will find d in the list and will mark d._panic (this panic) aborted.
35       // 把當前的panic 綁定到這個defer上面,defer裏面有可能panic,這種情況下就會進入到 上面d.started 的邏輯裏面,然後把當前的panic終止掉,因為已經執行過了 
36       d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
37       // 執行defer.fn
38       p.argp = unsafe.Pointer(getargp(0))
39       reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
40       p.argp = nil
41 
42       // reflectcall did not panic. Remove d.
43       if gp._defer != d {
44          throw("bad defer entry in panic")
45       }
46       // 解決defer與panic的綁定關係,因為 defer函數已經執行完了,如果有panic或Goexit就不會執行到這裏了
47       d._panic = nil
48       d.fn = nil
49       gp._defer = d.link
50 
51       // trigger shrinkage to test stack copy. See stack_test.go:TestStackPanic
52       //GC()
53 
54       pc := d.pc
55       sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
56       freedefer(d)
57       // panic被recover了,就不需要繼續panic了,繼續執行剩餘的代碼
58       if p.recovered {
59          atomic.Xadd(&runningPanicDefers, -1)
60 
61          gp._panic = p.link
62          // Aborted panics are marked but remain on the g.panic list.
63          // Remove them from the list.
64          // 從panic鏈表中移除aborted的panic,下面解釋
65          for gp._panic != nil && gp._panic.aborted {
66             gp._panic = gp._panic.link
67          }
68          if gp._panic == nil { // must be done with signal
69             gp.sig = 0
70          }
71          // Pass information about recovering frame to recovery.
72          gp.sigcode0 = uintptr(sp)
73          gp.sigcode1 = pc
74          // 調用recovery, 恢復當前g的調度執行
75          mcall(recovery)
76          throw("recovery failed") // mcall should not return
77       }
78    }
79      // 打印panic信息
80    preprintpanics(gp._panic)
81      // panic
82    fatalpanic(gp._panic) // should not return
83    *(*int)(nil) = 0      // not reached
84 }

  看下裏面gp._panic.aborted 的作用:

 1 func main() {
 2    defer func() { // defer1
 3       recover()
 4    }()
 5    panic1()
 6 }
 7 
 8 func panic1() {
 9    defer func() {  // defer2
10       panic("error1") // panic2
11    }()
12    panic("error")  // panic1
13 }

  執行順序詳解:

  • 當執行到 panic("error") 時

  g._defer鏈表: g._defer->defer2->defer1

  g._panic鏈表:g._panic->panic1 

  • 當執行到 panic("error1") 時 

  g._defer鏈表: g._defer->defer2->defer1

  g._panic鏈表:g._panic->panic2->panic1

  • 繼續執行到 defer1 函數內部,進行recover()
    此時會去恢復 panic2 引起的 panic, panic2.recovered = true,應該順着g._panic鏈表繼續處理下一個panic了,但是我們可以發現 panic1 已經執行過了,這也就是下面的代碼的邏輯了,去掉已經執行過的panic
1 for gp._panic != nil && gp._panic.aborted {
2    gp._panic = gp._panic.link
3 }

panic的邏輯:

  程序在遇到panic的時候,就不再繼續執行下去了,先把當前panic 掛載到 g._panic 鏈表上,開始遍歷當前g的g._defer鏈表,然後執行_defer對象定義的函數等,如果 defer函數在調用過程中又發生了 panic,則又執行到了 gopanic函數,最後,循環打印所有panic的信息,並退出當前g。然而,如果調用defer的過程中,遇到了recover,則繼續進行調度(mcall(recovery))。

recovery

 1 func recovery(gp *g) {
 2    // Info about defer passed in G struct.
 3    sp := gp.sigcode0
 4    pc := gp.sigcode1
 5    // Make the deferproc for this d return again,
 6    // this time returning 1.  The calling function will
 7    // jump to the standard return epilogue.
 8    // 記錄defer返回的sp pc
 9    gp.sched.sp = sp
10    gp.sched.pc = pc
11    gp.sched.lr = 0
12    gp.sched.ret = 1
13    // 重新恢復執行調度
14    gogo(&gp.sched)
15 }

gorecover

  gorecovery 僅僅只是設置了 g._panic.recovered 的標誌位

 1 func gorecover(argp uintptr) interface{} {
 2    gp := getg()
 3    p := gp._panic
 4    // 需要根據 argp的地址,判斷是否在defer函數中被調用
 5    if p != nil && !p.recovered && argp == uintptr(p.argp) {
 6       // 設置標誌位,上面gopanic中會對這個標誌位做判斷
 7       p.recovered = true
 8       return p.arg
 9    }
10    return nil
11 }

goexit

  當手動調用 runtime.Goexit() 退出的時候,defer函數也會執行:

 1 func Goexit() {
 2     // Run all deferred functions for the current goroutine.
 3     // This code is similar to gopanic, see that implementation
 4     // for detailed comments.
 5     gp := getg()
 6   // 遍歷defer鏈表
 7     for {
 8         d := gp._defer
 9         if d == nil {
10             break
11         }
12     // 如果 defer已經執行過了,與defer綁定的panic 終止掉
13         if d.started {
14             if d._panic != nil {
15                 d._panic.aborted = true
16                 d._panic = nil
17             }
18             d.fn = nil
19       // 從defer鏈表中移除
20             gp._defer = d.link
21       // 釋放defer
22             freedefer(d)
23             continue
24         }
25     // 調用defer內部函數
26         d.started = true
27         reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
28         if gp._defer != d {
29             throw("bad defer entry in Goexit")
30         }
31         d._panic = nil
32         d.fn = nil
33         gp._defer = d.link
34         freedefer(d)
35         // Note: we ignore recovers here because Goexit isn't a panic
36     }
37   // 調用goexit0,清除當前g的屬性,重新進入調度
38     goexit1()
39 }

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

【編程題與分析題】Javascript 之繼承的多種實現方式和優缺點總結

[!NOTE]
能熟練掌握每種繼承方式的手寫實現,並知道該繼承實現方式的優缺點。

原型鏈繼承

    function Parent() {
      this.name = 'zhangsan';
      this.children = ['A', 'B', 'C'];
    }
    Parent.prototype.getName = function() {
      console.log(this.name);
    }
    
    function Child() {
      
    }
    Child.prototype = new Parent();
    var child = new Child();
    console.log(child.getName());

[!NOTE]
主要問題:
1. 引用類型的屬性被所有實例共享(this.children.push(‘name’))
2. 在創建Child的實例的時候,不能向Parent傳參

借用構造函數(經典繼承)

    function Parent(age) {
      this.names = ['zhangsan', 'lisi'];
      this.age = age;
      
      this.getName = function() {
        return this.names;
      }
      
      this.getAge = function() {
        return this.age;
      }
    }
    
    function Child(age) {
      Parent.call(this, age);
    }
    var child = new Child(18);
    child.names.push('haha');
    console.log(child.names);
    
    var child2 = new Child(20);
    child2.names.push('yaya');
    console.log(child2.names);

[!NOTE]
優點:
1. 避免了引用類型的屬性被所有實例共享
2. 可以直接在Child中向Parent傳參
缺點:
方法都在構造函數中定義了,每次創建實例都會創建一遍方法

組合繼承(原型鏈繼承和經典繼承雙劍合璧)

    /**
    * 父類構造函數
    * @param name
    * @constructor
    */
    function Parent(name) {
      this.name = name;
      this.colors = ['red', 'green', 'blue'];
    }
    
    Parent.prototype.getName = function() {
      console.log(this.name);
    }
    
    // child
    function Child(name, age) {
      Parent.call(this, name);
      this.age = age;
    }
    
    Child.prototype = new Parent();
    // 校正child的構造函數
    Child.prototype.constructor = Child;
    
    // 創建實例
    var child1 = new Child('zhangsan', 18);
    child1.colors.push('orange');
    console.log(child1.name, child1.age, child1.colors);    // zhangsan 18 (4) ["red", "green", "blue", "orange"]
    
    var child2 = new Child('lisi', 28);
    console.log(child2.name, child2.age, child2.colors);    // lisi 28 (3) ["red", "green", "blue"]

[!NOTE]
優點: 融合了原型鏈繼承和構造函數的優點,是Javascript中最常用的繼承模式

—— 高級繼承的實現

原型式繼承

    function createObj(o) {
      function F(){};
      // 關鍵:將傳入的對象作為創建對象的原型
      F.prototype = o;
      return new F();
    }
    
    // test
    var person = {
        name: 'zhangsan',
        friends: ['lisi', 'wangwu']
    }
    var person1 = createObj(person);
    var person2 = createObj(person);
    
    person1.name = 'wangdachui';
    console.log(person1.name, person2.name);  // wangdachui, zhangsan
    
    person1.friends.push('songxiaobao');
    console.log(person2.friends);       // lisi wangwu songxiaobao

[!WARNING]
缺點:
對於引用類型的屬性值始終都會共享相應的值,和原型鏈繼承一樣

寄生式繼承

    // 創建一個用於封裝繼承過程的函數,這個函數在內部以某種形式來增強對象
    function createObj(o) {
      var clone = Object.create(o);
      clone.sayName = function() {
        console.log('say HelloWorld');
      }
      return clone;
    }

[!WARNING]
缺點:與借用構造函數模式一樣,每次創建對象都會創建一遍方法

寄生組合式繼承

基礎版本

    function Parent(name) {
      this.name = name;
      this.colors = ['red', 'green', 'blue'];
    }
    
    Parent.prototype.getName = function() {
      console.log(this, name);
    }
    
    function Child(name, age) {
      Parent.call(this, name);
      this.age = age;
    }
    
    // test1:
    // 1. 設置子類實例的時候會調用父類的構造函數
    Child.prototype = new Parent();
    // 2. 創建子類實例的時候也會調用父類的構造函數
    var child1 = new Child('zhangsan', 18);   // Parent.call(this, name);
    
    
    // 思考:如何減少父類構造函數的調用次數呢?
    var F = function(){};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    
    // 思考:下面的這一句話可以嗎?
    /* 分析:因為此時Child.prototype和Parent.prototype此時指向的是同一個對象,
            因此部分數據相當於此時是共享的(引用)。
            比如此時增加 Child.prototype.testProp = 1; 
            同時會影響 Parent.prototype 的屬性的。
          如果不模擬,直接上 es5 的話應該是下面這樣吧
          Child.prototype = Object.create(Parent.prototype);*/
    Child.prototype = Parent.prototype;
    
    // 上面的三句話可以簡化為下面的一句話
    Child.prototype = Object.create(Parent.prototype);
    
    
    
    // test2:
    var child2 = new Child('lisi', 24);

優化版本

    // 自封裝一個繼承的方法
    function object(o) {
      // 下面的三句話實際上就是類似於:var o = Object.create(o.prototype)
      function F(){};
      F.prototype = o.prototype;
      return new F();
    }
    
    function prototype(child, parent) {
      var prototype = object(parent.prototype);
      // 維護原型對象prototype裏面的constructor屬性
      prototype.constructor = child;
      child.prototype = prototype;
    }
    
    // 調用的時候
    prototype(Child, Parent)

創建對象的方法

  • 字面量創建
  • 構造函數創建
  • Object.create()
var o1 = {name: 'value'};
var o2 = new Object({name: 'value'});

var M = function() {this.name = 'o3'};
var o3 = new M();

var P = {name: 'o4'};
var o4 = Object.create(P)

原型

  • JavaScript 的所有對象中都包含了一個 __proto__ 內部屬性,這個屬性所對應的就是該對象的原型
  • JavaScript 的函數對象,除了原型 __proto__ 之外,還預置了 prototype 屬性
  • 當函數對象作為構造函數創建實例時,該 prototype 屬性值將被作為實例對象的原型 __proto__

原型鏈

任何一個實例對象通過原型鏈可以找到它對應的原型對象,原型對象上面!

的實例和方法都是實例所共享的。

一個對象在查找以一個方法或屬性時,他會先在自己的對象上去找,找不到時,他會沿着原型鏈依次向上查找。

注意: 函數才有prototype,實例對象只有有__proto__, 而函數有的__proto__是因為函數是Function的實例對象

instanceof原理

判斷實例對象的__proto__屬性與構造函數的prototype是不是用一個引用。如果不是,他會沿着對象的__proto__向上查找的,直到頂端Object。

判斷對象是哪個類的直接實例

使用對象.construcor直接可判斷

構造函數,new時發生了什麼?

   var obj  = {}; 
   obj.__proto__ = Base.prototype;
   Base.call(obj);  
  1. 創建一個新的對象 obj;
  2. 將這個空對象的__proto__成員指向了Base函數對象prototype成員對象
  3. Base函數對象的this指針替換成obj, 相當於執行了Base.call(obj);
  4. 如果構造函數显示的返回一個對象,那麼則這個實例為這個返回的對象。 否則返回這個新創建的對象

類的聲明

// 普通寫法
function Animal() {
  this.name = 'name'
}

// ES6
class Animal2 {
  constructor () {
    this.name = 'name';
  }
}

繼承

借用構造函數法

在構造函數中 使用Parent.call(this)的方法繼承父類屬性。

原理: 將子類的this使用父類的構造函數跑一遍

缺點: Parent原型鏈上的屬性和方法並不會被子類繼承

function Parent() {
  this.name = 'parent'
}

function Child() {
  Parent.call(this);
  this.type = 'child'
}

原型鏈實現繼承

原理:把子類的prototype(原型對象)直接設置為父類的實例

缺點:因為子類只進行一次原型更改,所以子類的所有實例保存的是同一個父類的值。
當子類對象上進行值修改時,如果是修改的原始類型的值,那麼會在實例上新建這樣一個值;
但如果是引用類型的話,他就會去修改子類上唯一一個父類實例裏面的這個引用類型,這會影響所有子類實例

function Parent() {
  this.name = 'parent'
  this.arr = [1,2,3]
}

function Child() {
  this.type = 'child'
}

Child.prototype = new Parent();
var c1 = new Child();
var c2 = new Child();
c1.__proto__ === c2.__proto__

組合繼承方式

組合構造函數中使用call繼承和原型鏈繼承。

原理: 子類構造函數中使用Parent.call(this);的方式可以繼承寫在父類構造函數中this上綁定的各屬性和方法;
使用Child.prototype = new Parent()的方式可以繼承掛在在父類原型上的各屬性和方法

缺點: 父類構造函數在子類構造函數中執行了一次,在子類綁定原型時又執行了一次

function Parent() {
  this.name = 'parent'
  this.arr = [1,2,3]
}

function Child() {
  Parent.call(this);
  this.type = 'child'
}

Child.prototype = new Parent();

組合繼承方式 優化1:

因為這時父類構造函數的方法已經被執行過了,只需要關心原型鏈上的屬性和方法了

Child.prototype = Parent.prototype;

缺點:

  • 因為原型上有一個屬性為constructor,此時直接使用父類的prototype的話那麼會導致 實例的constructor為Parent,即不能區分這個實例對象是Child的實例還是父類的實例對象。
  • 子類不可直接在prototype上添加屬性和方法,因為會影響父類的原型

注意:這個時候instanseof是可以判斷出實例為Child的實例的,因為instanceof的原理是沿着對象的__proto__判斷是否有一個原型是等於該構造函數的原型的。這裏把Child的原型直接設置為了父類的原型,那麼: 實例.__proto__ === Child.prototype === Child.prototype

組合繼承方式 優化2 – 添加中間對象【最通用版本】:

function Parent() {
  this.name = 'parent'
  this.arr = [1,2,3]
}

function Child() {
  Parent.call(this);
  this.type = 'child'
}

Child.prototype = Object.create(Parent.prototype); //提供__proto__
Child.prototype.constrctor = Child;

Object.create()方法創建一個新對象,使用現有的對象來提供新創建的對象的__proto__

創建JS對象的多種方式總結

工廠模式

 
    /**
    * 工廠模式創建對象
    * @param name
    * @return {Object}
    */
    function createPerson(name){
        var o = new Object();
        o.name = name;
        o.getName = function() {
          console.log(this.name);
        }
        return o;
    }
    var person = createPerson('zhangsan');
    console.log(person.__proto__ === Object.prototype); // true

缺點:無法識別當前的對象,因為創建的所有對象實例都指向的是同一個原型

構造函數模式

構造函數創建對象基礎版本

    /**
    * 使用構造函數的方式來創建對象
    * @param name
    * @constructor
    */
    function Person(name) {
      this.name = name;
      this.getName = function() {
        console.log(this.name)
      }
    }
    var person = new Person('lisi');
    console.log(person.__proto__ === Person.prototype)

優點:實例剋識別偽一個特定的類型
缺點:每次創建實例對象的時候,每個方法都會被創建一次

構造函數模式優化

    function Person(name) {
      this.name = name;
      this.getName = getName;
    }
    
    function getName() {
      console.log(this.name);
    }
    
    var person = new Person('zhangsan');
    console.log(person.__proto__ === Person.prototype);

優點:解決了每個方法都要被重新創建的問題
缺點:不合乎代碼規範……

原型模式

原型模式基礎版

    function Person(name) {
      
    }
    Person.prototype.name = 'lisi';
    Person.prototype.getName = function() {
      console.log(this.name);
    }
    var person = new Person();
    console.log(Person.prototype.constructor)       // Person

優點:方法不會被重新創建
缺點:1. 所有的屬性和方法所有的實例上面都是共享的;2. 不能初始化參數

原型模式優化版本一

    function Person(name) {
      
    }
    Person.prototype = {
        name: 'lisi',
        getName: function() {
          console.log(this.name);
        }
    }
    var person = new Person();
    console.log(Person.prototype.constructor)       // Object
    console.log(person.constructor == person.__proto__.constructor) // true

優點:封裝性好了一些
缺點:重寫了Person的原型prototype屬性,丟失了原始的prototype上的constructor屬性

原型模式優化版本二

    function Person(name) {
      
    }
    Person.prototype = {
        constructor: Person,
        name: 'lisi',
        getName: function() {
          console.log(this.name)
        }
    }
    var person = new Person();

優點:實例可以通過constructor屬性找到所屬的構造函數
缺點:所有的屬性和方法都共享,而且不能初始化參數

組合模式

    function Person(name) {
      this.name = name;
    }
    Person.prototype = {
        constructor: Person,
        getName: function() {
          console.log(this.name)
        }
    }
    var person = new Person('zhangsan');

優點:基本符合預期,屬性私有,方法共享,是目前使用最廣泛的方式
缺點:方法和屬性沒有寫在一起,封裝性不是太好

動態原型模式

    // 第一種創建思路:
    function Person(name) {
       this.name = name;
       if (typeof this.getName !== 'function') {
           Person.prototype.getName = function() {
             console.log(this.name);
           }
       }
    }
    var person = new Person();

    // 第二種創建的思路:使用對象字面量重寫原型上的方法
    function Person(name) {
      this.name = name;
      if (typeof this.getName !== 'function') {
          Person.prototype = {
              constructor: Person,
              getName: function() {
                console.log(this.name)
              }
          }
          return new Person(name);
      }
    }
    
    var person1 = new Person('zhangsan');
    var person2 = new Person('lisi');
    console.log(person1.getName());
    console.log(person2.getName());
    

寄生構造函數模式

    /**
    * 寄生構造函數模式
    * @param name
    * @return {Object}
    * @constructor
    */
   function Person(name){
        var o = new Object();
        o.name = name;
        o.getName = function() {
          console.log(this.name)
        }
        return o;
   }
   var person = new Person('zhangsan');
   console.log(person instanceof Person);   // false
   console.log(person instanceof Object);   // true
   
   
   // 使用寄生-構造函數-模式來創建一個自定義的數組
   /**
    * 特殊數組的構造器
    * @constructor
    */
   function SpecialArray() {
     var values = new Array();
     /*for (var i = 0, len = arguments.length; i < len; i++) {
         values.push(arguments[i]);
     }*/
     // 開始添加數據(可以直接使用apply的方式來優化代碼)
     values.push.apply(values, arguments);
     
     // 新增的方法
     values.toPipedString = function(){
         return this.join('|');
     }
     
     return values;
   }
   
   // 使用new來創建對象
   var colors1 = new SpecialArray('red1', 'green1', 'blue1');
   // 不使用new來創建對象
   var colors2 = SpecialArray('red2', 'green2', 'blue2');
   
   console.log(colors1, colors1.toPipedString());
   console.log(colors2, colors2.toPipedString());

穩妥構造函數模式

    /**
    * 穩妥的創建對象的方式
    * @param name
    * @return {number}
    * @constructor
    */
    function Person(name){
        var o = new Object();
        o.sayName = function() {
           // 這裡有點類似於在一個函數裏面使用外部的變量
           // 這裏直接輸出的是name
          console.log(name);
        }
        return o;
    }
    var person =  Person('lisi');
    person.sayName();
    person.name = 'zhangsan';
    person.sayName();
    console.log(person instanceof Person);      // false
    console.log(person instanceof Object);      // false

[!NOTE]
與寄生的模式的不同點:1. 新創建的實例方法不引用this 2.不使用new操作符調用構造函數
優點:最適合一些安全的環境中使用
缺點:和工廠模式一樣,是無法識別對象的所屬類型的

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

賓士 Future Bus 無人巴士上路,會閃行人還懂得看紅綠燈

如果你從荷蘭阿姆斯特丹史基浦機場出來搭上快捷巴士(BRT),注意到巴士明明正在行進中,司機的雙手卻輕鬆擺在一旁,先別太緊張,你可能搭上了裝載賓士CityPilot 自動駕駛系統的「未來巴士(Future Bus)」!

正在阿姆斯特丹史基浦機場和哈勒姆鎮之間進行長距離測試的Future Bus,改裝自賓士的Citaro 型號巴士,以賓士220 kw 的六缸引擎驅動,最高時速達70 公里,並由CityPilot 自動駕駛系統負責將車輛保持在車道中央,方向導航以及加減速。

根據賓士Citaro 改裝自動駕駛巴士,裝有GPS 與兩組雷達系統。

當巴士穩定停下,車門打開時,出入口的冷光條會由禁止的紅色轉成通行的綠色。上車刷卡後,你可以看到司機的方向盤前方設置一個長方形螢幕,隨時顯示車速、電量以及鄰近道路交通號誌等資訊,如果車身感應到有東西靠近,也會跳出警示符號。

基於法規而存在的駕駛平時只需要坐在駕駛座上監控,特殊狀況才需要接手控車,而CityPilot 自動駕駛系統被認為能減少人為駕駛疏失,並靠著更穩定的行車方式增進交通效率和搭乘舒適度。

Future Bus 總共安裝10 台攝影機監控道路與車身四周的情況,4 個短程雷達感應器負責巴士前方50 公分至10 公尺的車況,還有兩個範圍達50 公尺的立體相機為系統提供3D 視覺與障礙物辨識。

自動駕駛巴士主要運用雷達、GPS、道路追蹤攝影機和環景相機來判定自己的位置,車底還有相機負責辨識路面坑洞狀況。

基於當地法規,雖然是自動駕駛巴士,但還是會配有一名司機。

這幾年已有不少在校園或遊樂園等封閉場域進行載客的無人巴士,與IBM 超級電腦華生合作的自動駕駛小巴Olli 也已經在華盛頓特區上路了。不過賓士Future Bus 負責的機場300 號路線巴士專用通道,行駛路段包含十字路口、行人區與隧道,因此除了為乘客自動開關車門、閃避行人等突然靠近的物體,還具備判讀交通號誌、在正確時機通過路口的能力。可說是自動駕駛公車測試的又一里程碑!

(本文由 授權提供。所有圖片來源:mercedes-benz)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

中國國產電動汽車續航超500km 遠超特斯拉

由中國中能東道集團自主研發,與四川野馬汽車公司聯合生產的「野馬u能純電動汽車」在大連進行了路演。行駛502公里,仍有餘電22%,這遠遠優於領先國際先進水準的特斯拉(400公里)和比亞迪(260公里),其續航能力是目前中國國內普通純電動汽車的3倍。  
  該純電動汽車不依賴專用充電站或充電樁,220v民用電即可完成充電。該車已進入國家工信部目錄並實現量產。中能東道集團已規劃建設5個電動汽車「動力總成核新部件產業基地」,可實現年產50萬套目標。   2014年大連市政府專門出臺檔要求2016年全市公交、公務、郵政、環衛、出租等公共領域新能源汽車置換率不低於30%;個人購買使用新能源汽車,除享受由中央和地方財政分別給予總車款60%左右的政策性補貼外,還可享受免車輛購置稅和車輛不限行、電價優惠等鼓勵政策。2016年底大連市區將實現充電樁網路化建設,基本可滿足新能源汽車充電需求。   中能東道集團已完成全國31個省管理服務中心佈局,並在大連設立運營中心。野馬u能純電動汽車即將落戶大連。   文章來源:新浪新聞看點

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

高通、Lear簽署電動車無線充電授權協議

手機晶片公司美國高通,與汽車座椅與電子系統供應商Lear簽訂了電動汽車無線充電(wireless electric vehicle charging,WEVC)授權協議。Lear將在高通授權下,將高通的Halo WEVC技術應用於旗下產品,以支援PHEV、EV製造商與無線充電基礎設施公司推動WEVC的商業應用。高通將提供技術與工程支援。

高通與Lear正與多家車廠合作推動WEVC生產計畫。高通副總裁暨無線充電部門總經理Steve Pazol表示,Lear能研發多樣化的WEVC系統,包括多線圈、螺絲館、循環系統等,足以滿足眾多客戶的多種需求。與Lear的合作,也將推動高通Halo技術的商業化,並進一步使WEVC技術走向實用市場。

Lear將無線充電技術視為新的市場契機,並表示無線充電技術將有助客戶擬定策略、搶進現有與未來的新車市場。Lear正在向高通取得全面的技術轉讓組合,希望研發出兼具技術與商業性的WEVC系統,以支援後續各種進階款的WEVC系統設計。

(圖片來源:高通Halo技術專頁截圖)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

特斯拉即將在台開賣,選擇台灣原因曝光

美國電動車龍頭品牌特斯拉(Tesla)正式來台設點,預計將在今年9月展開銷售服務。特斯拉之所以選擇台灣的原因近日曝光,主要是因為台灣擁有完整的供應鏈,且幅員適中之故。

《中央社》指出,台灣南北全長不到400公里,且全島幅員不算太大;而Tesla Model S每次充飽電可行駛至少260公里,使車主每次充飽電都可以直接開車往來南北,特斯拉要布建全島充電網也會相對容易。

特斯拉目前在全球已設置681個超級充電站,充飽電的時間約需30分鐘,通常設置於餐廳、商場與Wi-Fi熱點周遭,讓車主可在等待充電的同時進行日常消費。

另一方面,特斯拉許多關鍵零件,如電動馬達、電控系統等零組件都來自台灣,零件供應量比例最高達25%,類別也可達40%左右。強大的在地供應鏈,也強化了特斯拉在台拓展市場的實力。台灣與特斯拉電動車相關的業者,包括動力電池線束公司貿聯-KY、變速箱齒輪廠和大、DC/DC轉換器與車上充電系統供應商台達電、電動馬達廠富田、大電流端子廠健和興等。

最後,台灣品牌電動機車Gogoro已在台灣本地打出市場名聲。有了Gogoro的領航經驗,台灣人對電動車輛的接受度更高,也將有利特斯拉在台灣開疆拓土。

(照片來源:Tesla Taiwan臉書專頁)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大眾汽車集團「以人為本」戰略 再推15款電動汽車

大眾汽車集團攜手旗下大眾汽車、奧迪、斯柯達、西雅特、保時捷、賓利六大品牌及合資企業一汽大眾、上海大眾,聯合發佈了』以人為本」的中國戰略。  
  該戰略基於三個重要支柱——客戶、員工與社會,這三大支柱將促成創新且滿足消費者需求的車型。   作為該戰略的首項活動,大眾汽車集團攜旗下各品牌與合資公司共同向中國家庭捐贈5000多個安全坐椅。   作為中國戰略的一部分,大眾汽車集團將在未來三年中不斷推進節能環保車型的生產,加強技術創新,推出一系列低排放車型。據大眾汽車集團總裁兼CEO海茲曼介紹,大眾未來每一款新車型的燃油效率都將比其上一代車型提高20%。   大眾汽車集團已宣佈其新能源汽車戰略,計畫在2018年之前再推出超過15款電動車新車型,並從2016年開始國產其中多款車型。到2018年,大眾汽車集團在中國的國產車型將超過35款。   文章來源:新京報

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

Gogoro 騎進柏林,與Bosch子公司共推租賃服務

來自台灣的智慧雙輪交通工具品牌Gogoro再下一城,宣布與德國Bosch集團旗下的新創品牌Coup合作,於德國柏林展開Gogoro電動機車Smartscooter™的租賃服務,正式進軍歐洲。

Gogoro 執行長暨共同創辦人陸學森表示:「Gogoro 與德國BOSCH 集團有一個共同的願景:希望透過COUP 雙輪共享服務提供城市一種更進化的移動方式。」

他指出,選擇以柏林作為歐洲首站的原因,不僅是因為柏林是全球最進步、創新的代表性城市之一,也是因為柏林對機車的依賴度高。透過Coup服務平台,柏林消費者只要年滿21歲、或擁有機車駕照,下載APP後就能騎乘Gogoro電動機車。租用Gogoro電動機車的價格是每30分鐘3歐元,全日租賃則為20歐元。Gogoro初步規劃設置200輛智慧雙輪機車於柏林,並將其最高速度設定為時速45公里,以符合市區騎乘的安全性。

銷售破萬,推回娘家活動

除了成功進軍海外市場之外,Gogoro也宣布台灣市場銷售創下佳績。今年七月,Gogoro智慧雙輪機車在台灣共售出1,300輛,累計車主突破10,000人。為感謝破萬車主的支持,Gogoro從8月4日至9月30日止舉辦車主回娘家活動,只要在Gogoro網路或實體商店消費周邊產品,一律享九折優惠。

陸學森感謝車主與經營團隊的支持,表示:「隨著台灣市場成功達到車主破萬的里程碑,我們也順利的走向國際進軍歐洲,這一切都要歸功於所有車主、員工與夥伴的支持,Gogoro 才能有今天的成績。」

在過去一年間,Gogoro所有車主的總騎乘公里數已接近2,000萬公里,省下85萬公升的汽油、減排1,634公噸的二氧化碳排放。目前Gogoro在全台共有225座GoStation電池交換站,分別位於桃園以北與台中、彰化,每天服務超過7,000顆電池進行交換。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

工信部:109款純電動車型進入第286批目錄 3款勒令停產

近日,工信部發佈《道路機動車輛生產企業及產品(第286批)》公告。據統計發現,約有109款純電動車型進入此批汽車生產企業目錄,其中專用車和運輸車佔據比例較高。第286批目錄對不符合管理規定的車企產品進行了公示,其中2家企業的3款純電動車型被勒令自公告發佈之日起暫停生產銷售。  
  其中,福建新龍馬汽車股份有限公司涉及的2款車型是FJ6410BEVA1純電動多用途乘用車和FJ6411BEVA1純電動多用途乘用車。資料顯示,FJ6410BEVA1純電動多用途乘用車的動力電池由河南鋰想動力科技有限公司生產,該公司並不在動力電池57家白名單之中。而FJ6411BEVA1純電動多用途乘用車的動力電池由廈門華鋰能源有限公司提供,廈門華鋰也不在動力電池57家白名單之中。   鄭州日產汽車有限公司生產的ZN6440V1Y純電動乘用車也被勒令停止生產銷售。資料顯示,該車型的動力電池由河南環宇賽爾新能源科技有限公司提供,而環宇賽爾在57家電池白名單之中。對於該款車型為何被停止生產銷售,目前不得而知。   文章來源:蓋世汽車

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

新加坡贈比亞迪100張電動計程車牌照 打造智慧交通

近日,新加坡政府向比亞迪頒發100個電動計程車牌照,意味著比亞迪正式成為有史以來進入新加坡計程車市場的第一家中國企業。  
  據悉,此次頒發的100個計程車牌照將用於e6純電動車。宏達同新加坡計程車私人有限公司將負責這100台e6計程車的日常運營和管理,首批車輛預計在今年9月上路運營,100台計程車計畫于明年一季度前全部投入服務。屆時,比亞迪將與其新加坡合作夥伴對這支e6車隊進行即時資料採集、分析和處理,聯合研發自動駕駛電動汽車技術和智慧交通管理系統,以支援新加坡政府打造智慧交通。   目前,比亞迪在新加坡的車型由最初的e6逐步擴展到電動巴士、叉車和電動商用車等多種車型,並與GrabCar等交通出行領軍企業展開合作。今年5月30日,比亞迪還攜手SMART集團在新加坡開啟了全球首家純電動車體驗中心,並簽署在新加坡聯合推廣1,000輛e6的合作諒解備忘錄。   文章來源:環球網

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選