2020年10月19日 星期一

《面試官別再問》JavaScript 動態建立變數的方法,怎麼動態生成js變數

JavaScript 動態建立變數:低調務實的選擇與眉角

各位同鄉、各位朋友,大家好!今天想跟大家聊聊一個在寫 JavaScript 時,有時候會遇到的情境:「欸,我這個變數的名稱,好像不能一開始就決定耶,得等到程式跑起來才知道。那這種時候,我該怎麼辦呢?」這就是我們今天要談的「動態建立變數」。

講到動態建立變數,有些人可能馬上會想到一些比較「刺激」的方法,但咱們台灣人嘛,做事情講求的是效率、穩定,還有最重要的「不要出包」。所以,今天就來跟大家分享幾招,有推薦的,也有那種…嗯,建議您多想兩次的。


最推薦:把變數當成物件的「屬性」

這招可以說是動態建立變數的「模範生」,安全又好用,建議您直接學起來。

其實很簡單,就是我們不要直接去「宣告」一個新的變數,而是把這些「動態變數」當作某個物件裡面的東西

假設我們有個情境,要根據使用者輸入的「產品名稱」來儲存一些資料:

JavaScript
// 建立一個空物件,專門用來放我們的動態變數
const productData = {};

// 假設這是從使用者那邊拿到的資料
const userProductName = "智慧型手機";
const userProductPrice = 12900;
const userProductStock = 50;

// 現在,我們把這些資料「掛」到 productData 這個物件下面
// productData.智慧型手機 = 12900; 這樣寫也行,但如果是變數名稱,就要用中括號
productData[userProductName] = {
  price: userProductPrice,
  stock: userProductStock
};

console.log(productData["智慧型手機"]);
// 輸出: { price: 12900, stock: 50 }

// 如果要再多加一個產品呢?照做就好!
const anotherProductName = "無線耳機";
productData[anotherProductName] = {
  price: 3500,
  stock: 200
};

console.log(productData["無線耳機"]);
// 輸出: { price: 3500, stock: 200 }

這樣做的好處非常多:

  • 乾淨俐落:所有的動態資料都集中在 productData 這個物件裡面,一目瞭然,不會讓您的程式碼變得亂七八糟。

  • 管理方便:想知道有哪些動態變數?直接看 productData 就好。

  • 安全無虞:跟某些危險的作法比起來,這種方式非常安全,不用擔心被惡意程式碼搞破壞。

  • 彈性十足:搭配 JavaScript 的物件操作,您想怎麼增刪改查,都輕輕鬆鬆。


次選項:window 物件 (只在瀏覽器裡面用得到)

如果您的 JavaScript 程式碼是在瀏覽器裡面跑,而且您真的、真的很想讓某些變數變成「全域變數」(就是整個網頁都抓得到的變數),那可以考慮用 window 物件。

在瀏覽器環境下,您平常宣告的 var 變數,其實都會偷偷地掛到 window 這個全域物件底下。所以,您可以這樣做:

JavaScript
// 注意:這段程式碼只在瀏覽器環境下有效喔!
if (typeof window !== 'undefined') {
  const variableName = "myGlobalData";
  const variableValue = "我是全域資料!";

  window[variableName] = variableValue;

  console.log(window.myGlobalData); // 輸出: 我是全域資料!
  console.log(myGlobalData);      // 輸出: 我是全域資料! (因為它變成全域變數了)
}

不過,這種方法有幾個小缺點,還是要提醒一下:

  • 只限定瀏覽器:您在 Node.js 或其他伺服器環境下寫程式,這招就用不上了。

  • 容易「弄髒」全域環境:全域變數太多,就像家裡東西堆得到處都是,很容易亂,也可能跟別人寫的程式「撞名」,造成一些意想不到的錯誤。除非必要,不然盡量少用。


千萬要小心:eval() 函數 (不建議使用!)

最後,我們要來聊聊一個很常被提到,但我們台灣人講求務實,會強烈建議您不要隨便使用的方法:eval() 函數。

eval() 可以把一個字串當成 JavaScript 程式碼來執行。所以,您當然可以這樣動態建立變數:

JavaScript
const myDynamicVarName = "dynamicMessage";
const myDynamicVarValue = "這段文字是動態生成的!";

// 千萬小心!這行程式碼非常危險!
eval(`var ${myDynamicVarName} = "${myDynamicVarValue}";`);

console.log(dynamicMessage); // 輸出: 這段文字是動態生成的!

為什麼要強調「千萬小心」呢?理由很簡單:

  • 資安大破口:如果這個字串是從外面(例如使用者輸入、網路傳輸)來的,裡面只要夾帶一點惡意的程式碼,eval() 就會照單全收執行下去,等於您把您的網站或系統,直接開了一個大門給駭客。這就是資安上常說的「XSS 攻擊」其中一種手法。

  • 效能打折扣eval() 在執行的時候,還要先解析、編譯您給的字串,這會比直接執行程式碼慢很多。

  • 除錯很痛苦:程式碼出錯時,您很難追蹤到底哪一行是 eval() 產生的問題。

  • 作用域問題eval() 執行的程式碼會「繼承」它被呼叫時的環境,這有時候會產生一些您意想不到的行為。

所以,除非您非常、非常、非常確定您知道自己在做什麼,而且沒有任何其他替代方案,否則請務必避免使用 eval()


結語:務實選擇,安全第一

總結來說,JavaScript 動態建立變數,最務實、最安全的做法,就是把變數當成物件的屬性來操作。這種方法不僅程式碼好管理,也能避免掉許多潛在的問題。

至於 window 物件,雖然可以用,但請考慮是否真的有「全域」的需求,並留意可能造成的全域污染。而 eval(),除非您是資安專家,否則真的建議您高抬貴手,別碰它為妙。

希望這篇小小的分享,能幫助大家在寫 JavaScript 的時候,能更順手、更安全。有任何問題或想法,都歡迎在下面留言交流喔!

2020年7月24日 星期五

《面試官別再問》提高 Laravel 框架性能 PHP7 + Swoole + Stone

PHP每次的每次請求結束, 都會釋放掉執行中建立的所有資源。這樣有一個很大的好處:PHP程序員基本不用費力去考慮資源釋放的問題,諸如內存,IO句柄,數據庫連接等,請求結束時PHP將全部釋放。PHP程序員幾乎不用關心內存釋放的問題,也很難寫出內存泄露的程序。這讓PHP變得更加簡單容易上手, 直抒心意。但是也帶來了一個壞處:PHP很難在請求間復用資源, 類似PHP框架這種耗時的工作, 每次請求都需要反復做——即使每次都在做同樣的事情。也正因為如此,在PHP發展過程中,關於是否使用框架的爭論也從未停止過。

Stone主要優化的就是這個問題。 在框架資源初始化結束后再開啟一個FastCGI服務,這樣, 新的請求過來是直接從資源初始化結束后的狀態開始,避免每次請求去做資源初始化的事情。所以, 本質上, Stone運行時是常駐內存的,它和PHP-FPM一樣,是一個FastCGI的實現,不同的是, FPM每次執行請求都需要重新初始化框架, Stone直接使用初始化的結果。

同樣,事情總是有好有壞。壞處是:PHP編程變得更難了, 你需要考慮內存的釋放,需要關心PHP如何使用內存。甚至, 你需要了解使用的框架,以免『不小心』寫出讓人『驚喜』的效果。同時, PHP的調試變得更難, 因為每次修改程序后需要重啟進程才能看到效果。事實上開發Stone時針對這方面做了不少工作。好處是:程序的性能得到極大的提高。 當然, 客觀上的一些利好因素是: PHP的內存回收已經相當穩定和高效, Swoole穩定性已經在相當多的項目中得到驗證,Laravel代碼質量相當高。

按照Laravel官方文檔安裝laravel,如果已經安裝可以跳過

composer安裝stone內核

composer require stone/kernel:dev-master
編輯config / app.php,添加Provider

 'providers' => [

    /*
     * Laravel Framework Service Providers...
     */
    Illuminate\Auth\AuthServiceProvider::class,
    // 省略很多行

    /*
     * Package Service Providers...
     */
    Stone\StoneServiceProvider::class,

    /*
     * Application Service Providers...
     */
    App\Providers\AppServiceProvider::class,
],
編輯app / Console / Kernel.php,添加新命令

protected $commands = [
    \Stone\Console\Commands\StoneServer::class,
];
Stone的安裝已經完成,正常情況下stone:server的命令應該可以正常執行了

php ./artisan stone:server --help

Usage:
    stone:server [options]

Options:
      --debug
      --start
      --reload
      --stop
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
      --env[=ENV]       The environment the command should run under
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Help:
  A FastCGI server bases on swoole and laravel
下面我們繼續,讓服務器運行起來,首先需要設置一些服務器的參數,我們需要新建一個config / stone.php

<?php
return [
    'server' => [
        'user' => 'www-data', //運行用戶,一般和php-fpm運行用戶相同
        'group' => 'www-data', //運行組,同上
        'domain' => '/var/run/stone-server-fpm.sock', // Unix域套接字地址,使用與nginx進程通信,推薦保持可用,如果系統不支持也可以是ip地址
        'port' => 9101, // 端口
        'handler' => 'App\Servers\Handler', //請求處理器,一個類名
        'pid' => '/var/run/stone.pid', //進程文件
    ]
];
建立Handler,新建app / Servers / Handler.php

<?php namespace App\Servers;

use Stone\Contracts\RequestHandler;
use Response;

class Handler implements RequestHandler
{
    public function process()
    {
        return Response::make('hello, stone server!');
    }

    public function onWorkerStart()
    {   
    }

    public function handleException($e)
    {   
    }
}
啟動服務器,正常情況下會顯示一個ok

php artisan swoole:http stop
php artisan swoole:http start

sudo php artisan stone:server --stop
sudo php artisan stone:server --start

當工程師的價值不再只是寫 Code,公司還看得見你的貢獻嗎?

 最近跟 AI、軟體開發流程轉變相關的討論越來越多。 很多人關注的是: AI 會不會取代工程師? 未來還需不需要寫 Code? 工程師應該學什麼新技能? 但有一個更現實的問題,可能很多工程師每天都正在面對: 當你的工作價值不再只是「寫多少 Code」,公司是...