nodejs的部署的問題

因爲我們要管理很多依賴,

最後我們選擇使用autod來管理,

autod:一個自動分析項目所有的文件,獲取所有的項目依賴和它們的版本的工具。

autod 同時可以根據我們傳遞的一些選項和參數,來直接更新 package.json 文件:

通過這個工具,我們可以很輕鬆的跟蹤到所有依賴的最新版本,同時可以自動更新我們的 package.json 文件,新引入的模塊也不需要手動去更新 package.json 文件了,一切都可以交給 autod 來完成。

通常,我們會在 Makefile 中加入 autod 相關的配置項,來自動化完成這個過程:

比如我想完成這個任務:

autod:
	@autod -w
	@npm install

但是一直報錯*** missing separator.  Stop.

因爲是makefile的命令行,開頭必須用tab鍵,目前沒有發現tabstop的設定值的不同,會引起error,但是我已經換成tab鍵開頭了還是一直報這個錯。最後才發現原來是webstrom文檔的tab鍵不行,後來換成是文本用tab鍵才行。真是坑啊。

最後執行代碼我們看到了:

zewdeMacBook-Pro:bin zew$ make autod

[DEPENDENCIES]

  "dependencies": {
    "colors": "1.3.2",
    "commander": "2.19.0",
    "minimatch": "3.0.4",
    "printable": "0.0.3"
  }
nothing to update in Dependencies

nothing to update in DevDependencies

[INFO] Write dependencies into package.jso

ok,執行成功了。

可以看出來都下載完成了,可以看出我們通過MakeFile+autod自動化幫package.json裏的依賴都下載完,

我們那麼我現在如果通過改變package.json裏的依賴的版本號可以幫我們動態控制嗎?

首先我們執行autod -f獲取到版本號:

zewdeMacBook-Pro:bin zew$ autod -f

[DEPENDENCIES]

  "dependencies": {
    "colors": "~1.3.2",
    "commander": "~2.19.0",
    "minimatch": "~3.0.4",
    "printable": "~0.0.3"
  }

好了,下面我來修改一下。把printable修改爲0.0.4,再執行一遍腳本

 

下面我們安裝stf

brew install rethinkdb graphicsmagick zeromq protobuf yasm pkg-config
npm install -g stf

再啓動:

rethinkdb

新開個窗口啓動stf

 

 

後來裝stf也遇到了一堆的問題,結果是因爲nodejs版本太高,我用的是10.x的後來換成8.2的就好了。但是還遇到了權限問題後來修改

sudo chown -R [owner] /usr/local/lib/node_modules

總是安裝成功了

打印記過是:

#pragma message("Warning: zmq_utils.h is deprecated. All its functionali...
        ^
3 warnings generated.
  SOLINK_MODULE(target) Release/zmq.node
+ [email protected]
added 755 packages from 681 contributors in 47.692s

就好了。

然後啓動一下

rethinkdb

新開個窗口啓動stf

 

zewdeMacBook-Pro:~ zew$ stf local
2018-10-10T05:12:18.271Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli migrate"
2018-10-10T05:12:18.503Z INF/db 48059 [*] Connecting to 127.0.0.1:28015
2018-10-10T05:12:18.535Z INF/db:setup 48059 [*] Database "stf" created
2018-10-10T05:12:20.444Z INF/db:setup 48059 [*] Table "devices" created
2018-10-10T05:12:20.472Z INF/db:setup 48059 [*] Table "logs" created
2018-10-10T05:12:20.528Z INF/db:setup 48059 [*] Index "devices"."owner" created
2018-10-10T05:12:20.528Z INF/db:setup 48059 [*] Waiting for index "devices"."owner"
2018-10-10T05:12:20.621Z INF/db:setup 48059 [*] Index "devices"."present" created
2018-10-10T05:12:20.621Z INF/db:setup 48059 [*] Waiting for index "devices"."present"
2018-10-10T05:12:20.713Z INF/db:setup 48059 [*] Index "devices"."providerChannel" created
2018-10-10T05:12:20.713Z INF/db:setup 48059 [*] Waiting for index "devices"."providerChannel"
2018-10-10T05:12:20.916Z INF/db:setup 48059 [*] Table "accessTokens" created
2018-10-10T05:12:20.962Z INF/db:setup 48059 [*] Table "vncauth" created
2018-10-10T05:12:21.097Z INF/db:setup 48059 [*] Table "users" created
2018-10-10T05:12:21.098Z INF/db:setup 48059 [*] Index "accessTokens"."email" created
2018-10-10T05:12:21.098Z INF/db:setup 48059 [*] Waiting for index "accessTokens"."email"
2018-10-10T05:12:21.152Z INF/db:setup 48059 [*] Index "vncauth"."response" created
2018-10-10T05:12:21.152Z INF/db:setup 48059 [*] Waiting for index "vncauth"."response"
2018-10-10T05:12:21.347Z INF/db:setup 48059 [*] Index "devices"."owner" is ready
2018-10-10T05:12:21.352Z INF/db:setup 48059 [*] Index "users"."adbKeys" created
2018-10-10T05:12:21.353Z INF/db:setup 48059 [*] Waiting for index "users"."adbKeys"
2018-10-10T05:12:21.465Z INF/db:setup 48059 [*] Index "vncauth"."responsePerDevice" created
2018-10-10T05:12:21.465Z INF/db:setup 48059 [*] Waiting for index "vncauth"."responsePerDevice"
2018-10-10T05:12:22.660Z INF/db:setup 48059 [*] Index "accessTokens"."email" is ready
2018-10-10T05:12:22.759Z INF/db:setup 48059 [*] Index "vncauth"."response" is ready
2018-10-10T05:12:22.924Z INF/db:setup 48059 [*] Index "users"."adbKeys" is ready
2018-10-10T05:12:23.033Z INF/db:setup 48059 [*] Index "vncauth"."responsePerDevice" is ready
2018-10-10T05:12:23.796Z INF/db:setup 48059 [*] Index "devices"."present" is ready
2018-10-10T05:12:23.889Z INF/db:setup 48059 [*] Index "devices"."providerChannel" is ready
2018-10-10T05:12:23.896Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli triproxy app001 --bind-pub tcp://127.0.0.1:7111 --bind-dealer tcp://127.0.0.1:7112 --bind-pull tcp://127.0.0.1:7113"
2018-10-10T05:12:23.898Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli triproxy dev001 --bind-pub tcp://127.0.0.1:7114 --bind-dealer tcp://127.0.0.1:7115 --bind-pull tcp://127.0.0.1:7116"
2018-10-10T05:12:23.900Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli processor proc001 --connect-app-dealer tcp://127.0.0.1:7112 --connect-dev-dealer tcp://127.0.0.1:7115"
2018-10-10T05:12:23.902Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli processor proc002 --connect-app-dealer tcp://127.0.0.1:7112 --connect-dev-dealer tcp://127.0.0.1:7115"
2018-10-10T05:12:23.905Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli reaper reaper001 --connect-push tcp://127.0.0.1:7116 --connect-sub tcp://127.0.0.1:7111"
2018-10-10T05:12:23.917Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli provider --name zewdeMacBook-Pro.local --min-port 7400 --max-port 7700 --connect-sub tcp://127.0.0.1:7114 --connect-push tcp://127.0.0.1:7116 --group-timeout 900 --public-ip localhost --storage-url http://localhost:7100/ --adb-host 127.0.0.1 --adb-port 5037 --vnc-initial-size 600x800 --mute-master never"
2018-10-10T05:12:23.941Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli auth-mock --port 7120 --secret kute kittykat --app-url http://localhost:7100/"
2018-10-10T05:12:23.976Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli app --port 7105 --secret kute kittykat --auth-url http://localhost:7100/auth/mock/ --websocket-url http://localhost:7110/"
2018-10-10T05:12:24.034Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli api --port 7106 --secret kute kittykat --connect-push tcp://127.0.0.1:7113 --connect-sub tcp://127.0.0.1:7111"
2018-10-10T05:12:24.039Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli websocket --port 7110 --secret kute kittykat --storage-url http://localhost:7100/ --connect-sub tcp://127.0.0.1:7111 --connect-push tcp://127.0.0.1:7113"
2018-10-10T05:12:24.044Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli storage-temp --port 7102"
2018-10-10T05:12:24.053Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli storage-plugin-image --port 7103 --storage-url http://localhost:7100/"
2018-10-10T05:12:24.063Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli storage-plugin-apk --port 7104 --storage-url http://localhost:7100/"
2018-10-10T05:12:24.068Z INF/util:procutil 48057 [*] Forking "/usr/local/lib/node_modules/stf/lib/cli poorxy --port 7100 --app-url http://localhost:7105/ --auth-url http://localhost:7120/ --api-url http://localhost:7106/ --websocket-url http://localhost:7110/ --storage-url http://localhost:7102/ --storage-plugin-image-url http://localhost:7103/ --storage-plugin-apk-url http://localhost:7104/"
2018-10-10T05:12:25.152Z INF/triproxy 48060 [app001] PUB socket bound on tcp://127.0.0.1:7111
2018-10-10T05:12:25.156Z INF/triproxy 48060 [app001] DEALER socket bound on tcp://127.0.0.1:7112
2018-10-10T05:12:25.157Z INF/triproxy 48060 [app001] PULL socket bound on tcp://127.0.0.1:7113
2018-10-10T05:12:25.162Z INF/triproxy 48061 [dev001] PUB socket bound on tcp://127.0.0.1:7114
2018-10-10T05:12:25.165Z INF/triproxy 48061 [dev001] DEALER socket bound on tcp://127.0.0.1:7115
2018-10-10T05:12:25.166Z INF/triproxy 48061 [dev001] PULL socket bound on tcp://127.0.0.1:7116
2018-10-10T05:12:25.678Z INF/poorxy 48073 [*] Listening on port 7100
2018-10-10T05:12:25.659Z INF/reaper 48064 [reaper001] Subscribing to permanent channel "*ALL"
2018-10-10T05:12:25.738Z INF/reaper 48064 [reaper001] Reaping devices with no heartbeat
2018-10-10T05:12:25.747Z INF/db 48063 [*] Connecting to 127.0.0.1:28015
2018-10-10T05:12:25.761Z INF/db 48064 [reaper001] Connecting to 127.0.0.1:28015
2018-10-10T05:12:25.771Z INF/reaper 48064 [reaper001] Receiving input from "tcp://127.0.0.1:7111"
2018-10-10T05:12:25.773Z INF/reaper 48064 [reaper001] Sending output to "tcp://127.0.0.1:7116"
2018-10-10T05:12:25.819Z INF/db 48062 [*] Connecting to 127.0.0.1:28015
2018-10-10T05:12:25.867Z INF/processor 48063 [proc002] App dealer connected to "tcp://127.0.0.1:7112"
2018-10-10T05:12:25.869Z INF/processor 48063 [proc002] Device dealer connected to "tcp://127.0.0.1:7115"
2018-10-10T05:12:25.923Z INF/processor 48062 [proc001] App dealer connected to "tcp://127.0.0.1:7112"
2018-10-10T05:12:25.926Z INF/processor 48062 [proc001] Device dealer connected to "tcp://127.0.0.1:7115"
2018-10-10T05:12:26.213Z INF/provider 48065 [*] Subscribing to permanent channel "8jpbUM0ETfG4xGk5QWGySQ=="
2018-10-10T05:12:26.286Z INF/provider 48065 [*] Sending output to "tcp://127.0.0.1:7116"
2018-10-10T05:12:26.289Z INF/provider 48065 [*] Receiving input from "tcp://127.0.0.1:7114"
2018-10-10T05:12:26.305Z INF/provider 48065 [*] Tracking devices
2018-10-10T05:12:26.247Z INF/storage:plugins:apk 48072 [*] Listening on port 7104
2018-10-10T05:12:26.320Z INF/provider 48065 [*] Found device "emulator-5554" (device)
2018-10-10T05:12:26.343Z INF/storage:plugins:image 48071 [*] Listening on port 7103
2018-10-10T05:12:26.362Z INF/auth-mock 48066 [*] Listening on port 7120
2018-10-10T05:12:26.425Z INF/provider 48065 [*] Registered device "emulator-5554"
2018-10-10T05:12:26.426Z INF/reaper 48064 [reaper001] Device "emulator-5554" is present
2018-10-10T05:12:26.512Z INF/storage:temp 48070 [*] Listening on port 7102
2018-10-10T05:12:26.810Z INF/app 48067 [*] Using pre-built resources
2018-10-10T05:12:26.833Z INF/app 48067 [*] Listening on port 7105
2018-10-10T05:12:26.836Z INF/db 48067 [*] Connecting to 127.0.0.1:28015
2018-10-10T05:12:26.902Z INF/websocket 48069 [*] Subscribing to permanent channel "*ALL"
2018-10-10T05:12:26.908Z INF/websocket 48069 [*] Listening on port 7110
2018-10-10T05:12:26.921Z INF/db 48069 [*] Connecting to 127.0.0.1:28015
2018-10-10T05:12:26.927Z INF/websocket 48069 [*] Sending output to "tcp://127.0.0.1:7113"
2018-10-10T05:12:26.928Z INF/websocket 48069 [*] Receiving input from "tcp://127.0.0.1:7111"
2018-10-10T05:12:27.048Z INF/device:support:push 48074 [emulator-5554] Sending output to "tcp://127.0.0.1:7116"
2018-10-10T05:12:27.054Z INF/device 48074 [emulator-5554] Preparing device
2018-10-10T05:12:27.470Z INF/device:support:sub 48074 [emulator-5554] Receiving input from "tcp://127.0.0.1:7114"
2018-10-10T05:12:27.473Z INF/device:support:sub 48074 [emulator-5554] Subscribing to permanent channel "*ALL"
2018-10-10T05:12:27.494Z INF/device:support:properties 48074 [emulator-5554] Loading properties
2018-10-10T05:12:27.494Z INF/api 48068 [*] Subscribing to permanent channel "*ALL"
2018-10-10T05:12:27.505Z INF/device:support:sdk 48074 [emulator-5554] Supports SDK 23
2018-10-10T05:12:27.505Z INF/device:support:abi 48074 [emulator-5554] Supports ABIs x86
2018-10-10T05:12:27.533Z INF/device:resources:minicap 48074 [emulator-5554] Installing "/usr/local/lib/node_modules/stf/node_modules/minicap-prebuilt/prebuilt/x86/bin/minicap" as "/data/local/tmp/minicap"
2018-10-10T05:12:27.534Z INF/device:resources:minicap 48074 [emulator-5554] Installing "/usr/local/lib/node_modules/stf/node_modules/minicap-prebuilt/prebuilt/x86/lib/android-23/minicap.so" as "/data/local/tmp/minicap.so"
2018-10-10T05:12:27.583Z INF/api 48068 [*] Listening on port 7106
2018-10-10T05:12:27.603Z INF/device:resources:service 48074 [emulator-5554] Checking whether we need to install STFService
2018-10-10T05:12:27.635Z INF/api 48068 [*] Sending output to "tcp://127.0.0.1:7113"
2018-10-10T05:12:27.637Z INF/api 48068 [*] Receiving input from "tcp://127.0.0.1:7111"
2018-10-10T05:12:27.793Z INF/db 48068 [*] Connecting to 127.0.0.1:28015
2018-10-10T05:12:27.818Z INF/device:resources:service 48074 [emulator-5554] Running version check
2018-10-10T05:12:28.194Z INF/device:resources:service 48074 [emulator-5554] STFService up to date
2018-10-10T05:12:28.196Z INF/device:plugins:service 48074 [emulator-5554] Launching agent
2018-10-10T05:12:28.388Z INF/device:plugins:service 48074 [emulator-5554] Agent says: "Listening on @stfagent"
2018-10-10T05:12:28.428Z INF/device:plugins:service 48074 [emulator-5554] Launching service
2018-10-10T05:12:28.429Z INF/device:plugins:service 48074 [emulator-5554] Agent says: "InputClient started"
2018-10-10T05:12:28.839Z INF/device:plugins:display 48074 [emulator-5554] Reading display info
2018-10-10T05:12:28.853Z INF/device:plugins:phone 48074 [emulator-5554] Fetching phone info
2018-10-10T05:12:28.866Z INF/device:plugins:identity 48074 [emulator-5554] Solving identity
2018-10-10T05:12:28.867Z INF/device:plugins:solo 48074 [emulator-5554] Subscribing to permanent channel "+dMhGpFI28OEk/LJBQj8PqTFjWQ="
2018-10-10T05:12:28.869Z INF/device:plugins:screen:stream 48074 [emulator-5554] Starting WebSocket server on port 7400
2018-10-10T05:12:28.890Z INF/device:resources:minitouch 48074 [emulator-5554] Installing "/usr/local/lib/node_modules/stf/node_modules/minitouch-prebuilt/prebuilt/x86/bin/minitouch" as "/data/local/tmp/minitouch"
2018-10-10T05:12:28.910Z WRN/device:plugins:data 48074 [emulator-5554] Unable to find device data { serial: 'emulator-5554',
  platform: 'Android',
  manufacturer: 'UNKNOWN',
  operator: 'Android',
  model: ' SDK built for x86',
  version: '6.0',
  abi: 'x86',
  sdk: '23',
  product: 'sdk_phone_x86',
  cpuPlatform: '',
  openGLESVersion: '2.0',
  display: 
   { id: 0,
     width: 1080,
     height: 1920,
     xdpi: 480,
     ydpi: 480,
     fps: 60.000003814697266,
     density: 3,
     rotation: 0,
     secure: true,
     size: 4.589389937671455,
     url: 'ws://localhost:7400' },
  phone: 
   { imei: '358240051111110',
     imsi: '310260000000000',
     phoneNumber: '+15555215554',
     iccid: '89014103211118510720',
     network: 'LTE' } }
2018-10-10T05:12:28.928Z INF/device:plugins:touch 48074 [emulator-5554] Touch origin is top left
2018-10-10T05:12:28.929Z INF/device:plugins:touch 48074 [emulator-5554] Requesting touch consumer to start
2018-10-10T05:12:28.930Z INF/device:plugins:touch 48074 [emulator-5554] Launching touch service
2018-10-10T05:12:28.938Z INF/device:plugins:touch 48074 [emulator-5554] Connecting to minitouch service
2018-10-10T05:12:29.014Z INF/device:plugins:touch 48074 [emulator-5554] minitouch says: "Note: device /dev/input/mice is not supported by libevdev"
2018-10-10T05:12:29.073Z INF/device:plugins:touch 48074 [emulator-5554] minitouch says: "Type B touch device qwerty2 (32767x32767 with 10 contacts) detected on /dev/input/event1 (score 43776)"
2018-10-10T05:12:29.262Z INF/device:plugins:touch 48074 [emulator-5554] minitouch says: "Connection established"
2018-10-10T05:12:29.264Z INF/device:plugins:touch 48074 [emulator-5554] Reading minitouch banner
2018-10-10T05:12:29.269Z INF/device:plugins:vnc 48074 [emulator-5554] Starting VNC server on port 7402
2018-10-10T05:12:29.271Z INF/device:plugins:browser 48074 [emulator-5554] Loading browser list
2018-10-10T05:12:29.274Z INF/device:plugins:browser 48074 [emulator-5554] Updating browser list
2018-10-10T05:12:29.276Z INF/device:plugins:mute 48074 [emulator-5554] Will not mute master volume
2018-10-10T05:12:29.291Z INF/device:resources:minirev 48074 [emulator-5554] Installing "/usr/local/lib/node_modules/stf/vendor/minirev/x86/minirev" as "/data/local/tmp/minirev"
2018-10-10T05:12:29.319Z INF/device:plugins:forward 48074 [emulator-5554] Launching reverse port forwarding service
2018-10-10T05:12:29.323Z INF/device:plugins:forward 48074 [emulator-5554] Connecting to reverse port forwarding service
2018-10-10T05:12:29.611Z INF/device:plugins:connect 48074 [emulator-5554] Listening on port 7401
2018-10-10T05:12:29.614Z INF/device 48074 [emulator-5554] Fully operational
2018-10-10T05:12:36.338Z INF/provider 48065 [*] Providing all 1 device(s)

 

rethinkdb的日誌

zewdeMacBook-Pro:~ zew$ cd /usr/local/Cellar/rethinkdb/2.3.6/bin
zewdeMacBook-Pro:bin zew$ rethinkdb
Recursively removing directory /usr/local/Cellar/rethinkdb/2.3.6/bin/rethinkdb_data/tmp
Initializing directory /usr/local/Cellar/rethinkdb/2.3.6/bin/rethinkdb_data
Running rethinkdb 2.3.6 (CLANG 8.0.0 (clang-800.0.42.1))...
Running on Darwin 15.6.0 x86_64
Loading data from directory /usr/local/Cellar/rethinkdb/2.3.6/bin/rethinkdb_data
warn: Cache size does not leave much memory for server and query overhead (available memory: 1053 MB).
warn: Cache size is very low and may impact performance.
Listening for intracluster connections on port 29015
Listening for client driver connections on port 28015
Listening for administrative HTTP connections on port 8080
Listening on cluster addresses: 127.0.0.1, ::1
Listening on driver addresses: 127.0.0.1, ::1
Listening on http addresses: 127.0.0.1, ::1
To fully expose RethinkDB on the network, bind to all addresses by running rethinkdb with the `--bind all` command line option.
Server ready, "zewdeMacBook_Pro_local_0ez" 5a3dd5c0-0f4e-40bb-83cb-8a94f989852c

 

一切都準備好了,我們來看看stf的運行原理是什麼?

require('../lib/cli/please') //很多人會好奇,nodejs上來就這麼一出是什麼意思呢?
  • 模塊是什麼,爲什麼要用模塊
  • 那麼模塊的查找流程是怎麼樣的?

1:其實這個就是nodejs模塊機制,學習一門語言我們首先要思考的是爲什麼要引入模塊機制,是爲了解決哪些問題才這麼設計的,如果是你你要怎麼優化呢?帶着這個問題我們想下模塊是爲了讓開發邊的簡單將一些獨立的邏輯封裝成一個模塊可以複用。

2:開發的過程中我們經常使用到require方法,但是可能會忽略它的內部細節,到底require是怎麼查找到對應的模塊的呢?其實require的時候node面對不同的路徑標識會有不同的處理細節,下面我們分別來說明,1:內置模塊,像http, net, stream 這樣的模塊,node早已經將這樣的模塊編譯成二進制模塊,在Node啓動的時候就已經常駐內存中了,所以就省略了(1):查找定位文件,(2):編譯文件的步驟,同時這樣的文件加載也是最快的,用數據結構的思維就是空間換時間嘛,2:絕對路徑或者相對路徑模塊,在標識路徑符的時候如果標識符前面有 ./ 或 .. 或者 / 這樣的字符,那麼就會被當作相對路徑或者是絕對路徑,Node.js對原生模塊和文件模塊都進行了緩存,於是在第二次require時,是不會有重複開銷的。其中原生模塊都被定義在lib這個目錄下面,文件模塊則不定性。

必須將“./”指定爲導入本地模塊的根文件夾的路徑。

比如說我們定義一個circle.js

var PI = Math.PI;
exports.area = function (r) {
    return PI * r * r;
};
exports.circumference = function (r) {
    return 2 * PI * r;
};

然後在再相同文件夾的express.js中引用並且打印一下,就會看到

var circle = require('./circle.js');
console.log('The area of a circle of radius 4 is ' + circle.area(4));

打印結果

The area of a circle of radius 4 is 50.26548245743669
可以看出這個路徑是相對的路徑,那麼如果我們在這個目錄下沒有這個js文件呢

如果我把circle.js文件放到同目錄test文件夾下呢,執行命令報錯是:

Error: Cannot find module './circle.js'
    at Function.Module._resolveFilename (module.js:548:15)
    at Function.Module._load (module.js:475:25)
    at Module.require (module.js:597:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/Users/zew/mygit/minicap/example/express.js:16:14)
    at Module._compile (module.js:653:30)
    at Object.Module._extensions..js (module.js:664:10)
    at Module.load (module.js:566:32)
    at tryModuleLoad (module.js:506:12)

如果換成require('./test/circle.js')就執行成功了

var circle = require('./test/circle.js');
console.log('The area of a circle of radius 4 is ' + circle.area(4));

The area of a circle of radius 4 is 50.26548245743669

如果這些都清楚了那麼下面這個帶便什麼意思呢?

var circle = require('./');
console.log('The area of a circle of radius 4 is ' + circle.area(4));

執行一遍:

Error: Cannot find module './'
    at Function.Module._resolveFilename (module.js:548:15)
    at Function.Module._load (module.js:475:25)
    at Module.require (module.js:597:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/Users/zew/mygit/minicap/example/express.js:16:14)
    at Module._compile (module.js:653:30)
    at Object.Module._extensions..js (module.js:664:10)
    at Module.load (module.js:566:32)
    at tryModuleLoad (module.js:506:12)
    at Function.Module._load (module.js:498:3)

如果我們在同目錄下放一個index.js文件,且文件內容如下的話

打印結果:The area of a circle of radius 4 is 51.26548245743669

由此我們可以看出如果require('./'),則表示在同目錄下尋找一個index.js文件模塊,加載進來,否則就報錯。

 

知道了這個模塊的機制以後我們就知道stf在模塊里加載了please模塊

require('../lib/cli/please')

那麼please.js做了什麼呢?其實他也是加載了另外兩個模塊,上面那個方式相對路徑加載很容易理解,下面看到了require('./'),其實就是把同目錄那個index.js加載進去,由上圖可以看出旁邊恰好就有一個index.js文件。

require('./doctor')這個含義就是加載的是同目錄下的docker文件夾裏的index.js文件,值得注意

下面我們來講講yargs模塊是幹什麼的, 介紹這個之前我們先只管的感受一下腳本的書寫。

比如我寫了一個mytest1文件,內容:

如果內容是:
#!/usr/bin/env node
console.log('hello world');

輸入mytest1
打印結果是:hello world

#!/usr/bin/env node
console.log('hello ', process.argv);

輸入:mytest1
打印結果是:
hello  [ '/usr/local/bin/node', '/Users/zew/mygit/autod/bin/mytest1' ]

那麼這個process.argv的參數是[x,x]格式的

如果在命令輸入
mytest1 ew

則打印的是:

hello  [ '/usr/local/bin/node',
  '/Users/zew/mygit/autod/bin/mytest1',
  'ew' ]

如果在命令輸入
mytest1 ew ew2

則打印:
 hello  [ '/usr/local/bin/node',
  '/Users/zew/mygit/autod/bin/mytest1',
  'ew',
  'ew2' ]



而 yargs 模塊能夠解決如何處理命令行參數。它也需要安裝。

yargs 模塊提供 argv 對象,用來讀取命令行參數。請看改寫後的 hello 。

如果是
#!/usr/bin/env node
var argv = require('yargs').argv;

console.log('hello ', argv);

命令:mytest1
打印結果:
hello  { _: [], '$0': 'mytest1' }

如果是命令:mytest1 --name=tom
打印結果:hello  { _: [], name: 'tom', '$0': 'mytest1' }

如果是命令是:mytest1  --name tom
打印結果是:hello  { _: [], name: 'tom', '$0': 'mytest1' }






從上面兩個操作可以看出yargs 可以上面的結果改爲一個對象,每個參數項就是一個鍵值對。

可以使用 alias 方法,指定 name 是 n 的別名。

比如說剛剛

#!/usr/bin/env node
var argv = require('yargs').alias('name', 'zew').argv;

console.log('hello ', argv);

命令:mytest1  --name tom


打印結果:hello  { _: [], name: 'tom', zew: 'tom', '$0': 'mytest1' }
可以看出這裏面有兩個都是tom的,其實意思就是制定“zew”這個鍵值對是“name”的別名,那麼我們打印一下這兩個結果試試:

代碼
#!/usr/bin/env node
var argv = require('yargs').alias('name', 'zew').argv;

console.log('hello ', argv);
console.log('hello ', argv.name);
console.log('hello ', argv.zew);


執行命令:mytest1  --name tom

打印結果:
hello  { _: [], name: 'tom', zew: 'tom', '$0': 'mytest1' }
hello  tom
hello  tom






如果將 argv.name 改成 argv.n,就可以使用一個字母的短參數形式了。

#!/usr/bin/env node
var argv = require('yargs').argv;

console.log('hello ', argv);

命令: mytest1  -n tom

打印結果:
hello  { _: [], n: 'tom', '$0': 'mytest1' }

那麼有的人會好奇難道就只能是n或者name嗎,其實當然不是他的意思就是如果是超過一個字符的話就用--x,如果是一個字符的話可以用-x或者--x,這裏的n就是一個,name就是多個。

那麼我們換一個試試

#!/usr/bin/env node
var argv = require('yargs')
  .argv;

console.log('hello ', argv);

命令:mytest1  --adsd tom
打印結果是:
hello  { _: [], adsd: 'tom', '$0': 'mytest1' }

如果是隨便起的一個a呢
比如命令: mytest1  -a tom
打印結果:hello  { _: [], a: 'tom', '$0': 'mytest1' }

如果是命令:mytest1  --a tom

打印結果是:hello  { _: [], a: 'tom', '$0': 'mytest1' }


那麼命令如果是:mytest1  -sdfsdf tom
打印結果:
hello  { _: [],
  s: [ true, true ],
  d: [ true, true ],
  f: [ true, 'tom' ],
  '$0': 'mytest1' }

argv 對象有一個下劃線(_)屬性,可以獲取非連詞線開頭的參數。

yargs 模塊還提供3個方法,用來配置命令行參數。

  • demand:是否必選
  • default:默認值
  • describe:提示

這三個參數是什麼意思呢?

#!/usr/bin/env node
var argv = require('yargs')
  .demand(['n'])
  .default({n: 'tom'})
  .describe({n: 'your name'})
  .argv;

console.log('hello ', argv.n);

命令:mytest1
打印:
hello  tom

意思就是上面代碼指定 n 參數不可省略,默認值爲 tom,並給出一行提示,而不是像以前那種mytest1 -n tom

options 方法允許將所有這些配置寫進一個對象。這個是什麼意思呢?

#!/usr/bin/env node
var argv = require('yargs')
  .option('n', {
    alias : 'name',
    demand: true,
    default: 'tom',
    describe: 'your name',
    type: 'string'
  })
  .argv;

console.log('hello ', argv);

命令:mytest1
打印:hello  { _: [], n: 'tom', name: 'tom', '$0': 'mytest1' }
它的意思就是把多個參數配置到一個對象裏。

有時,某些參數不需要值,只起到一個開關作用,這時可以用 boolean 方法指定這些參數返回布爾值。

#!/usr/bin/env node
var argv = require('yargs')
  .boolean(['n'])
  .argv;

console.log('hello ', argv);

命令:
mytest1

打印結果:
hello  { _: [], n: false, '$0': 'mytest1' }
可以看出n參數這裏就是一個默認的false值

如果命令是:mytest1 -n
打印結果:hello  { _: [], n: true, '$0': 'mytest1' }//n參數爲true


如果命令是:mytest1 -n ded
打印結果是:hello  { _: [ 'ded' ], n: true, '$0': 'mytest1' }//n參數也爲true

可以看出只要命令中包含了-n參數爲true啦


boolean 方法也可以作爲屬性,寫入 option 對象。

#!/usr/bin/env node
var argv = require('yargs')
  .option('n', {
    boolean: true
  })
  .argv;

console.log('hello ', argv.n);

命令:mytest1
打印結果:
hello  { _: [], n: false, '$0': 'mytest1' }

另外yargs 模塊提供以下方法,生成幫助信息

  • usage:用法格式
  • example:提供例子
  • help:顯示幫助信息
  • epilog:出現在幫助信息的結尾
#!/usr/bin/env node
var argv = require('yargs')
  .option('f', {
    alias : 'name',
    demand: true,
    default: 'tom',
    describe: 'your name',
    type: 'string'
  })
  .usage('Usage: hello [options]')
  .example('hello -n tom', 'say hello to Tom')
  .help('h')//顯示幫助信息
  .alias('h', 'help')//這個就是輸入mytest1 h和mytest1  help效果是一樣的
  .epilog('copyright 2015')//出現在幫助信息的結尾
  .argv;

console.log('hello ', argv.n);



命令:mytest1 -h
打印結果:
Usage: hello [options]

Options:
  -f, --name  your name                     [string] [required] [default: "tom"]
  -h, --help  Show help                                                [boolean]

Examples:
  hello -n tom  say hello to Tom

copyright 2015

yargs 模塊還允許通過 command 方法,設置 Git 風格的子命令。

#!/usr/bin/env node
var argv = require('yargs')
  .command("morning", "good morning", function (yargs) {
    console.log("Good Morning");
  })
  .command("evening", "good evening", function (yargs) {
    console.log("Good Evening");
  })
  .argv;

console.log('hello ', argv.n);

命令:
 mytest1 morning

打印:
Good Morning
hello  undefined

命令:mytest1 morning -n tom

打印:Good Morning
hello  tom

命令: mytest1 evening

打印:Good Evening
hello  undefined

可以看出我們可以自定義一些命令名稱進行輸出

 

好啦,基本語法我們講完了,接着看

var yargs = require('yargs')
var Promise = require('bluebird')

Promise.longStackTraces()

var _argv = yargs.usage('Usage: $0 <command> [options]')
  .strict()
  .command(require('./api'))
  .command(require('./app'))
  .command(require('./auth-ldap'))
  .command(require('./auth-mock'))
  .command(require('./auth-oauth2'))
  .command(require('./auth-openid'))
  .command(require('./auth-saml2'))
  .command(require('./device'))
  .command(require('./doctor'))
  .command(require('./generate-fake-device'))
  .command(require('./local'))
  .command(require('./log-rethinkdb'))
  .command(require('./migrate'))
  .command(require('./notify-hipchat'))
  .command(require('./notify-slack'))
  .command(require('./poorxy'))
  .command(require('./processor'))
  .command(require('./provider'))
  .command(require('./reaper'))
  .command(require('./storage-plugin-apk'))
  .command(require('./storage-plugin-image'))
  .command(require('./storage-s3'))
  .command(require('./storage-temp'))
  .command(require('./triproxy'))
  .command(require('./websocket'))
  .demandCommand(1, 'Must provide a valid command.')
  .help('h', 'Show help.')
  .alias('h', 'help')
  .version('V', 'Show version.', function() {
    return require('../../package').version
  })
  .alias('V', 'version')
  .argv

這個的意思就是註冊各個模塊。

這裏順便在強調下exports 和 module.exports區別是什麼?

Node裏的模塊系統遵循的是CommonJS規範,那問題又來了,什麼是CommonJS規範呢?由於js以前比較混亂,各寫各的代碼,沒有一個模塊的概念,而這個規範出來其實就是對模塊的一個定義。

CommonJS定義的模塊分爲: 模塊標識(module)、模塊定義(exports) 、模塊引用(require)

先解釋 exports 和 module.exports
在一個node執行一個文件時,會給這個文件內生成一個 exportsmodule對象,
module又有一個exports屬性。他們之間的關係如下圖,都指向一塊{}內存區域。

exports = module.exports = {};

那下面我們來看看代碼的吧。

//utils.js
let a = 100;

console.log(module.exports); //能打印出結果爲:{}
console.log(exports); //能打印出結果爲:{}

exports.a = 200; //這裏辛苦勞作幫 module.exports 的內容給改成 {a : 200}

exports = '指向其他內存區'; //這裏把exports的指向指走

//test.js

var a = require('/utils');
console.log(a) // 打印爲 {a : 200}

從上面可以看出,其實require導出的內容是module.exports的指向的內存塊內容,並不是exports的。
簡而言之,區分他們之間的區別就是 exports 只是 module.exports的引用,輔助後者添加內容用的。

用白話講就是,exports只輔助module.exports操作內存中的數據,辛辛苦苦各種操作數據完,累得要死,結果到最後真正被require出去的內容還是module.exports的,

 

言歸正傳:

.command(require('./doctor'))
require('./doctor')其實這個返回的就是一個module.exports,這個exports有四個部分組成,module.exports.command,module.exports.describe,module.exports.builder ,module.exports.handler.至於這四個部分進去做了什麼處理我們可以追蹤進去看看
點進去是yargs的一個方法,恰好也包含了這四個參數。

self.command = function (cmd, description, builder, handler) {
    command.addHandler(cmd, description, builder, handler)
    return self
  }
如果這個不理解,上面我寫過一個例子介紹command方法的使用

這個代表什麼意思呢?回答這個問題我們可以看看上面的那個代碼
command("morning", "good morning", function (yargs) {
    console.log("Good Morning");
  })
輸出的就是Good Morning

那麼現在問題是這四個參數分別代表什麼意思呢?

module.exports.command,module.exports.describe,module.exports.builder ,module.exports.handler.

command毋庸置疑就是命令嘛,describe就是這個命令的描述,builder顧名思義就是建造者,他其實就是給commend修飾用的,當然你也可以不修飾,這個看你命令的構成是什麼樣子。

module.exports.builder = function(yargs) {
  var os = require('os')

  return yargs
    .env('STF_LOCAL')
    .strict()
    .option('adb-host', {
      describe: 'The ADB server host.'
    , type: 'string'
    , default: '127.0.0.1'
    })
    .option('adb-port', {
      describe: 'The ADB server port.'
    , type: 'number'
    , default: 5037
    })
    .epilog('Each option can be be overwritten with an environment variable ' +
      'by converting the option to uppercase, replacing dashes with ' +
      'underscores and prefixing it with `STF_LOCAL_` (e.g. ' +
      '`STF_LOCAL_ALLOW_REMOTE`).')

handler最後一個纔是這個命令最終要執行的內容

我們看doctor裏的index.js裏面的

module.exports.handler = function() {

xxxxx


return Promise.all([
    checkOSArch()
  , checkOSPlatform()
  , checkOSRelease()
  , checkNodeVersion()
  , checkLocalRethinkDBVersion()
  , checkGraphicsMagick()
  , checkZeroMQ()
  , checkProtoBuf()
  , checkADB()
  ])
}

最終返回的是一個這個東西。

那麼Promise.all又是什麼呢?下面給大家介紹下bluebird,bluebird是一個第三方的Promise規範的實現庫,它不僅僅兼容原生的

Promise對象,且比原生對象更強大。

解釋這個問題,先延伸一下,衆所周知,javascript作爲一個單線程語言,所有工作都是阻塞的,有好多人不理解爲什麼說是javascript是阻塞的,怎麼可以做到異步機制呢?我們可以用瀏覽器來觸發Ajax來異步獲取數據,而這些不是JavaScript語言本身來決定這些異步操作的,而是JavaScript的瀏覽器去操縱線程做多線程操作的,可以把這些方法理解爲瀏覽器拋出的多線程API,而Nodejs是基於高性能的V8來實現的,它像瀏覽器一樣,可以拋出很多操縱線程的API,從而實現異步,這也就是Promise/A+的規範,在ES6中也增加了原生的Promise的使用,而之前Promise庫有promise,Q,bluebird等,在這些庫中現在已經慢慢對ES6的原生Promise作了兼容,雖然ES6現在還沒有大規模投入使用過程中在其中最爲出名的則是bluebird和Q庫,我使用的是bluebird,先貼一段bluebird的使用代碼感受感受

//關係嵌套任務
var Promise = require("bluebird"),
    readFileAsync = Promise.promisify(require("fs").readFile);//這個方法是什麼呢,其實就是爲了給後面參數的readFile添加Promise機制,那Promise機制又是什麼呢,就是你想使用我的優雅的設計必須得具備我的機制,具備的前提就要添加這麼一行代碼,添加了Promise機制的方法或者屬性後,記得,屬性也可以,讓整個方法返回一個Promise對象,我們可以通過Promise來調用then方法,從而對Promise方法的回調進行處理,在Promise中都默認將第一個參數的err放在了後面的catch中,從而使得我們不用寫了那麼多的if(err),而在then方法後,其返回的也是一個Promise對象,我們可以在其後再次進行then來獲取上一個then的數據並進行處理。當然,我們也可以人爲地去決定這個then的返回參數,但是整個then方法返回的都是一個Promise對象。

readFileAsync("MrFileOne.txt", "utf8")
    .then(function (data) {
        //if the data contains the path of MrFileTow
        var path = "MrFileTwo.txt"//do something with data
        console.log(data);
        return readFileAsync(path, "utf8");
    })
    .then(function (data) {
        //if the data contains the path of MrFileThree
        var path = "MrFileThree.txt" //do something with data
        console.log(data);
        return readFileAsync(path, "utf8");
    })
    .then(function (data) {
        //get the data of MrFileThree
        //do something
        console.log(data);
    })
    .catch(function (err) {
        console.log(err);
    });

這裏的關係嵌套,就是下一步是基於上一步的結果,所以最後我們打印的結果:
MrFileOne文件第一行內容
MrFileOne文件第二行內容
MrFileTwo文件第一行內容
MrFileTwo文件第二行內容

MrFileThree文件第一行內容
MrFileThree文件第二行內容

這樣其實是同步的動作,這樣也有這樣的好處就是省略了一堆的if(){}/else{}的判斷

那麼是不是有很多小夥伴發怒了,這樣效率豈不是太低了。能不能讓三個任務同時異步進行,這纔是node發揮了自己的優點,處理時間省了很多,這纔是我們大Nodejs。這裏介紹幾個異步任務的模塊async是國外強大的異步模塊,它的功能與eventproxy相似,但是維護速度與週期特別快,當然最著名也是我接下來要說的還是基於Promise模範bluebird框架。上面那個代碼也是,下面我們能不能優化一下,是其進行無關係的彙總。

var Promise = require("bluebird"),
    readFileAsync = Promise.promisify(require("fs").readFile);
//無關係彙總任務
Promise.all([
    readFileAsync("MrFileOne.txt", "utf8"),
    readFileAsync("MrFileTwo.txt", "utf8"),
    readFileAsync("MrFileThree.txt", "utf8")
])
    .then(function (datas) {

        console.log(datas);
        //do something with three data form our actors
    })
    .catch(function (err) {
        console.log(err);
    });

這裏每次的函數的回調都寫在了then方法裏,併爲then返回一個新的Promise,以供下一次then去回調本返回的結果。

打印結果:
[ 'MrFileOne文件第一行內容\nMrFileOne文件第二行內容',
  'MrFileTwo文件第一行內容\nMrFileTwo文件第二行內容\n',
  'MrFileThree文件第一行內容\nMrFileThree文件第二行內容' ]


那麼這行異常是怎麼回事呢?如果去使用呢?

var Promise = require("bluebird"),
    readFileAsync = Promise.promisify(require("fs").readFile);
//無關係彙總任務

readFileAsync("MrFileOne.txt", "utf8")
    .then(function (data) {
        if (true) {  //data isn't what we want
            console.log("1");
            Promise.reject("It's not correct data!");
        } else {
            console.log("2");
            return data;
        }
    })
    .then(function () {
        console.log("yeah! we got data!");
    })
    .catch(function (err) {
        console.log(err);
    })
打印結果是:

yeah! we got data!
Unhandled rejection It's not correct data!

這個很奇怪的是每次還是會走then方法,按照我的理解調用Promise.reject拋出一個ERROR,
並直接交給catch來處理錯誤,所以在控制檯我們能得到的是“It's not correct data!”,
並不會得到“yeah! we got data!”,因爲拋出錯誤後其之後的then方法並不會跟着執行
。然而這裏我也不是很清楚,有知道的朋友可以提出自己的意見

上面這個問題暫時先留着以後再來解惑,有知道的朋友可以給我私信一起交流。

那好了我們目光在放到stf框架的docter的index.js中


  return Promise.all([
    checkOSArch()
  , checkOSPlatform()
  , checkOSRelease()
  , checkNodeVersion()
  , checkLocalRethinkDBVersion()
  , checkGraphicsMagick()
  , checkZeroMQ()
  , checkProtoBuf()
  , checkADB()
  ])

隨便點進去checkADB()方法
 function checkADB() {
    return check('ADB', function(checker) {
      return checker.call('adb', ['version'])
        .then(checker.extract('version', /Android Debug Bridge version ([^\s]+)/))
        .then(checker.version(pkg.externalDependencies.adb))
    })
  }

點進去check方法
function check(label, fn) {
    function Check() {
    }

    Check.prototype.call = function(command, args, options) {
      return call(command, args, options).catch(function(err) {
        if (err.code === 'ENOENT') {
          throw new CheckError(
            '%s is not installed (`%s` is missing)'
          , label
          , command
          )
        }

        throw err
      })
    }

那麼Check的prototype是什麼意思呢?



回答這麼問題就是要介紹我們學習nodejs逃不了的一個問題,NodeJs是如何進行繼承的呢?看下下面這段代碼你能看懂嗎?如果不能那麼聽我介紹完一定就理解了!

 function CheckError() {
    Error.captureStackTrace(this, this.constructor)
    this.name = 'CheckError'
    this.message = util.format.apply(util, arguments)
  }

  util.inherits(CheckError, Error)

  function call(command, args, options) {
    return new Promise(function(resolve, reject) {
      var proc = cp.spawn(command, args, options)
      var stdout = []

      proc.stdout.on('readable', function() {
        var chunk
        while ((chunk = proc.stdout.read())) {
          stdout.push(chunk)
        }
      })

      proc.on('error', reject)

      proc.on('exit', function(code, signal) {
        if (signal) {
          reject(new CheckError('Exited with signal %s', signal))
        }
        else if (code === 0) {
          resolve(Buffer.concat(stdout).toString())
        }
        else {
          reject(new CheckError('Exited with status %s', code))
        }
      })
    })
  }

  function check(label, fn) {
    function Check() {
    }

    Check.prototype.call = function(command, args, options) {
      return call(command, args, options).catch(function(err) {
        if (err.code === 'ENOENT') {
          throw new CheckError(
            '%s is not installed (`%s` is missing)'
          , label
          , command
          )
        }

        throw err
      })
    }

    Check.prototype.extract = function(what, re) {
      return function(input) {
        return Promise.try(function() {
          var match = re.exec(input)
          if (!match) {
            throw new CheckError(util.format('%s %s cannot be detected', label, what))
          }
          return match[1]
        })
      }
    }

    Check.prototype.version = function(wantedVersion) {
      return function(currentVersion) {
        return Promise.try(function() {
          log.info('Using %s %s', label, currentVersion)
          var sanitizedVersion = currentVersion.replace(/~.*/, '')
          return semver.satisfies(sanitizedVersion, wantedVersion)
        })
        .then(function(satisfied) {
          if (!satisfied) {
            throw new CheckError(
              '%s is currently %s but needs to be %s'
            , label
            , currentVersion
            , wantedVersion
            )
          }
        })
      }
    }

    return Promise.try(function() {
        return fn(new Check())
      })
      .catch(CheckError, function(err) {
        log.error(err.message)
      })
      .catch(function(err) {
        log.error('Unexpected error checking %s: %s', label, err)
      })
  }

繼承是面向對象的一個繞不開的問題,那麼Nodejs是如何實現繼承的呢?nodejs在util中提供了inherits用於基於原型的繼承,實現如下


var inherits = function(Child, Parent) {
    var hasProp = {}.hasOwnProperty;

    function T() {
        this.constructor = Child;
        this.constructor$ = Parent;
        for (var propertyName in Parent.prototype) {
            if (hasProp.call(Parent.prototype, propertyName) &&
                propertyName.charAt(propertyName.length-1) !== "$"
           ) {
                this[propertyName + "$"] = Parent.prototype[propertyName];
            }
        }
    }
    T.prototype = Parent.prototype;
    Child.prototype = new T();
    return Child.prototype;
};

我們來嘗試一下,baseClass內容:

module.exports = Base;

function Base() {
    this.name = "name"
    this.showName = function () {
        console.log(this.name)
    };
}

Base.prototype.name1 ="name1"
Base.prototype.showName1 = function () {
    console.log(this.name1)
}

extendClass內容:

var Base = require('./baseClass')
var util = require('util')

module.exports = Extend

function Extend() {

}

util.inherits(Extend, Base)

這樣就實現了從Base到Extend的繼承

調用:

var Extend = require('./extendClass')
var e = new Extend()
e.showName1()
e.showName()

打印結果:

zewdeMacBook-Pro:bin zew$ node express.js 
name1
/Users/zew/mygit/autod/bin/express.js:101
e.showName()
  ^

TypeError: e.showName is not a function
    at Object.<anonymous> (/Users/zew/mygit/autod/bin/express.js:101:3)
    at Module._compile (module.js:653:30)
    at Object.Module._extensions..js (module.js:664:10)
    at Module.load (module.js:566:32)
    at tryModuleLoad (module.js:506:12)
    at Function.Module._load (module.js:498:3)
    at Function.Module.runMain (module.js:694:10)
    at startup (bootstrap_node.js:204:16)
    at bootstrap_node.js:625:3

showName()方法報錯了,說是not a function但是我們showName明明是在Base類裏定義的一個函數啊

那麼就很值得我們其考思考了,分別調用父類的兩個方法,爲什麼只有第二個會報錯呢,這裏就要說兩個概念了

(1)在構造函數內部定義的函數,

function Base() {
    this.name = {}
    this.showName = function () {
        console.log(this.name)
    };
}

(2)原型方法

Base.prototype.name1 ="name1"
Base.prototype.showName1 = function () {
    console.log(this.name1)
}

node.js自帶的inherits是無法繼承非原型方法和屬性的,也就是說在父類構造函數內部定義的方法和屬性子類中都無法得到繼承,只有父類中的原型方法和屬性纔可以被子類繼承(其實從源代碼中就可以看出,inherits方法其實只複製了原型鏈而已)。那麼我們如何實現內建屬性和方法的繼承呢?

我們先來理解一下構造函數內建屬性和方法的本質

JS中this指針的本質是上下文對象的概念,其作用是在一個函數內部應用調用它對象的本身,那麼我們只要將父類的上下文對象完全的複製給子類不就可以讓子類調用父類構造函數中的內建屬性和對象了嘛~~~

JS非常強大可以使用Call方法來進行方法執行時上下文的替換,所以修改ExtendClass,在構造函數中增加一句神奇的代碼:

function Extend() {
    Base.call(this)
}

util.inherits(Extend, Base)

Base.call(this)的作用爲

(1)在Extend中執行了Base的構造函數

(2)執行base構造函數的時候其實真正的上下文對象爲Extend的上下文對象

所以可以理解爲在Extend的上下文對象中創建了Base構造函數中的所有屬性和方法(如果無法理解我說的,大家下個斷點走一下就可以完全明白了)

 

現在我們再來執行一下之前的調用:

name1
name

可以看到我們即繼承了base中的原型方法和屬性,同時也可以訪問到base的內建屬性和方法了!!!

以上是node.js官方的繼承方式,唯一的問題就是我們不但需要調用util.inherits來繼承原型鏈,還需要調用父類的call來構造父類的內建方法和屬性。網上還流傳着另外一種實現繼承的方式,比官方的方法更爲完善一些,而且只需要調用一行就可以了~~

var Base = require('./baseClass')
var util = require('util')

module.exports = Extend

function Extend() {
    Base.call(this)
}

Extend.prototype = new Base()

打印結果是:
name1
name

這種方式可以同時繼承父類的原型方法以及內建方法!還少了一句代碼~~

其本質是將Extend的原型鏈連向Base的原型對象(new 方法的本質可以詳見我上一篇文章)。至於這種方法有何不好。。還沒有發現

簡單介紹一下這個概念:JS的繼承最常用應該就是借用構造函數繼承(call和apply)和原型鏈繼承(prototype)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章