首頁 web前端 js教程 JavaScript實現區塊鏈

JavaScript實現區塊鏈

May 30, 2018 pm 02:26 PM
javascript js 實現

很多朋友都聽過比特幣和以太幣這樣的加密貨幣,但是只有極少數人懂得隱藏在它們背後的技術,接下來透過本文給大家介紹用JavaScript來創建一個簡單的區塊鏈來示範它們的內部究竟是如何運作的,有興趣的朋友一起看看吧

幾乎每個人都聽說過像比特幣和以太幣這樣的加密貨幣,但是只有極少數人懂得隱藏在它們背後的技術。在這篇文中,我將會用JavaScript來創建一個簡單的區塊鏈來展示它們的內部究竟是如何運作的。我將會稱之為SavjeeCoin!

全文分為三個部分:

  1. part1:實作一個基本的區塊鏈

  2. part2:實作POW

  3. part3:交易與挖礦獎勵

#Part1:實作一個基本的區塊鏈

區塊鏈

區塊鏈是由一個任何人都可以存取的區塊所構成的公共資料庫。這好像沒什麼特別的,不過它們有一個有趣的屬性:它們是不可變的。一旦一個區塊被加入到區塊鏈中,除非讓剩餘的其餘區塊失效,否則它是不會再被改變的。

這就是為什麼加密貨幣是基於區塊鏈的原因。你肯定不希望人們在交易完成後再變更交易!

創造一個區塊

區塊鏈是由許許多多的區塊連結在一起的(這聽起來好像沒毛病..)。鏈上的區塊透過某種方式允許我們檢測到是否有人操縱了之前的任何區塊。

那我們要如何確保資料的完整性呢?每個區塊都包含一個基於其內容計算出來的hash。同時也包含了前一個區塊的hash。

下面是一個區塊類別用JavaScript寫出來大致的樣子:

const SHA256 = require("crypto-js/sha256");
class Block {
 constructor(index, timestamp, data, previousHash = '') {
 this.index = index;
 this.previousHash = previousHash;
 this.timestamp = timestamp;
 this.data = data;
 this.hash = this.calculateHash();
 }
 calculateHash() {
 return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data)).toString();
 }
}
登入後複製

因為JavaScript中並不支援sha256所以我引進了crypto-js 函式庫。然後我定義了一個建構函式來初始化我區塊的屬性。每一個區塊上都被賦予了 index 屬性來告知我們這個區塊在整個鏈上的位置。我們同時也產生了一個時間戳,以及一些需要在區塊裡儲存的資料。最後是前一個區塊的hash。

創造一個鏈

現在我們可以在Blockchain類別中將區塊連結起來了!以下是用JavaScript實作的程式碼:

class Blockchain{
 constructor() {
 this.chain = [this.createGenesisBlock()];
 }
 createGenesisBlock() {
 return new Block(0, "01/01/2017", "Genesis block", "0");
 }
 getLatestBlock() {
 return this.chain[this.chain.length - 1];
 }
 addBlock(newBlock) {
 newBlock.previousHash = this.getLatestBlock().hash;
 newBlock.hash = newBlock.calculateHash();
 this.chain.push(newBlock);
 }
 isChainValid() {
 for (let i = 1; i < this.chain.length; i++){
  const currentBlock = this.chain[i];
  const previousBlock = this.chain[i - 1];
  if (currentBlock.hash !== currentBlock.calculateHash()) {
  return false;
  }
  if (currentBlock.previousHash !== previousBlock.hash) {
  return false;
  }
 }
 return true;
 }
}
登入後複製

在建構函式裡,我透過建立一個包含創世區塊的陣列來初始化整個鏈。第一個區塊是特殊的,因為它不能指向前一個區塊。我還添加了下面兩個方法:

  • getLatestBlock() 返回我們區塊鏈上最新的區塊。

  • addBlock() 負責將新的區塊加入我們的鏈。為此,我們將前一個區塊的hash添加到我們新的區塊中。這樣我們就可以保持整個鏈的完整性。因為只要我們變更了最新區塊的內容,我們就需要重新計算它的hash。當計算完成後,我將把這個區塊推進鏈裡(一個陣列)。

最後,我創建一個 isChainValid() 來確保沒有人篡改過區塊鏈。它會遍歷所有的區塊來檢查每個區塊的hash是否正確。它會透過比較 previousHash 來檢查每個區塊是否指向正確的上一個區塊。如果一切都沒有問題它會回傳 true 否則會回傳 false 。

使用區塊鏈

我們的區塊鏈類別已經寫完啦,可以真正的開始使用它了!

let savjeeCoin = new Blockchain();
savjeeCoin.addBlock(new Block(1, "20/07/2017", { amount: 4 }));
savjeeCoin.addBlock(new Block(2, "20/07/2017", { amount: 8 }));
登入後複製

在這裡我只是創建了一個區塊鏈的實例,並且將它命名為SavjeeCoin!之後我在鏈上添加了一些區塊。區塊裡可以包含任何你想要放的數據,不過在上面的程式碼裡,我選擇加入了一個有 amount 屬性的物件。

試著操作吧!

在介紹裡我曾說過區塊鏈是不可變的。一旦添加,區塊就不可能再變更了。讓我們試試看!

// 检查是否有效(将会返回true)
console.log(&#39;Blockchain valid? &#39; + savjeeCoin.isChainValid());
// 现在尝试操作变更数据
savjeeCoin.chain[1].data = { amount: 100 };
// 再次检查是否有效 (将会返回false)
console.log("Blockchain valid? " + savjeeCoin.isChainValid());
登入後複製

我會在一開始透過執行 isChainValid() 來驗證整個鏈的完整性。我們操作過任何區塊,所以它會回傳true。

之後我將鏈上的第一個(索引為1)區塊的資料進行了變更。之後我再次檢查整個鏈的完整性,發現它回傳了false。我們的整個鏈不再有效了。

結論

這個小栗子還遠遠沒有達到完成的程度。它還沒有實現POW(工作量證明機制)或P2P網路來與其它礦工進行交流。

但他確實證明了區塊鏈的工作原理。許多人認為原理會非常複雜,但這篇文章證明了區塊鏈的基本概念是非常容易理解和實現的。

Part2:實作POW(proof-of-work:工作量證明)

在part1中我们用JavaScript创建了一个简单的区块链来演示区块链的工作原理。不过这个实现并不完整,很多人发现依旧可以篡改该系统。没错!我们的区块链需要另一种机制来抵御攻击。那么让我们来看看我们该如何做到这一点!

问题

现在我们可以很快的创造区块然后非常迅速的将它们添加进我们的区块链中。不过这导致了三个问题:

  • 第一:人们可以快速创建区块然后在我们的链里塞满垃圾。大量的区块会导致我们区块链过载并让其无法使用。

  • 第二:因为创建一个有效的区块太容易了,人们可以篡改链中的某一个区块,然后重新计算所有区块的hash。即使它们已经篡改了区块,他们仍然可以以有效的区块来作为结束。

  • 第三:你可以通过结合上述两个破绽来有效控制区块链。区块链由p2p网络驱动,其中节点会将区块添加到可用的最长链中。所以你可以篡改区块,然后计算所有其他的区块,最后添加多任意你想要添加的区块。你最后会得到一个最长的链,所有的其它节点都会接受它然后往上添加自己的区块。

显然我们需要一个方案来解决这些问题:POW。

什么是POW

POW是在第一个区块链被创造之前就已经存在的一种机制。这是一项简单的技术,通过一定数量的计算来防止滥用。工作量是防止垃圾填充和篡改的关键。如果它需要大量的算力,那么填充垃圾就不再值得。

比特币通过要求hash以特定0的数目来实现POW。这也被称之为 难度

不过等一下!一个区块的hash怎么可以改变呢?在比特币的场景下,一个区块包含有各种金融交易信息。我们肯定不希望为了获取正确的hash而混淆了那些数据。

为了解决这个问题,区块链添加了一个 nonce 值。Nonce是用来查找一个有效Hash的次数。而且,因为无法预测hash函数的输出,因此在获得满足难度条件的hash之前,只能大量组合尝试。寻找到一个有效的hash(创建一个新的区块)在圈内称之为挖矿。

在比特币的场景下,POW确保每10分钟只能添加一个区块。你可以想象垃圾填充者需要多大的算力来创造一个新区块,他们很难欺骗网络,更不要说篡改整个链。

实现POW

我们该如何实现呢?我们先来修改我们区块类并在其构造函数中添加Nonce变量。我会初始化它并将其值设置为0。

constructor(index, timestamp, data, previousHash = &#39;&#39;) {
 this.index = index;
 this.previousHash = previousHash;
 this.timestamp = timestamp;
 this.data = data;
 this.hash = this.calculateHash();
 this.nonce = 0;
}
登入後複製


我们还需要一个新的方法来增加Nonce,直到我们获得一个有效hash。强调一下,这是由难度决定的。所以我们会收到作为参数的难度。

mineBlock(difficulty) {
 while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
  this.nonce++;
  this.hash = this.calculateHash();
 }
 console.log("BLOCK MINED: " + this.hash);
}
登入後複製


最后,我们还需要更改一下 calculateHash() 函数。因为目前他还没有使用Nonce来计算hash。

calculateHash() {
 return SHA256(this.index +
 this.previousHash +
 this.timestamp +
 JSON.stringify(this.data) +
 this.nonce
 ).toString();
}
登入後複製

将它们结合在一起,你会得到如下所示的区块类:

class Block {
 constructor(index, timestamp, data, previousHash = &#39;&#39;) {
 this.index = index;
 this.previousHash = previousHash;
 this.timestamp = timestamp;
 this.data = data;
 this.hash = this.calculateHash();
 this.nonce = 0;
 }
 calculateHash() {
 return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data) + this.nonce).toString();
 }
 mineBlock(difficulty) {
 while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
  this.nonce++;
  this.hash = this.calculateHash();
 }
 console.log("BLOCK MINED: " + this.hash);
 }
}
登入後複製

修改区块链

现在,我们的区块已经拥有Nonce并且可以被开采了,我们还需要确保我们的区块链支持这种新的行为。让我们先在区块链中添加一个新的属性来跟踪整条链的难度。我会将它设置为2(这意味着区块的hash必须以2个0开头)。

constructor() {
 this.chain = [this.createGenesisBlock()];
 this.difficulty = 2;
}
登入後複製


现在剩下要做的就是改变 addBlock() 方法,以便在将其添加到链中之前确保实际挖到该区块。下面我们将难度传给区块。

addBlock(newBlock) {
 newBlock.previousHash = this.getLatestBlock().hash;
 newBlock.mineBlock(this.difficulty);
 this.chain.push(newBlock);
}
登入後複製


大功告成!我们的区块链现在拥有了POW来抵御攻击了。

测试

现在让我们来测试一下我们的区块链,看看在POW下添加一个新区块会有什么效果。我将会使用之前的代码。我们将创建一个新的区块链实例然后往里添加2个区块。

let savjeeCoin = new Blockchain();
console.log(&#39;Mining block 1&#39;);
savjeeCoin.addBlock(new Block(1, "20/07/2017", { amount: 4 }));
console.log(&#39;Mining block 2&#39;);
savjeeCoin.addBlock(new Block(2, "20/07/2017", { amount: 8 }));
登入後複製

如果你运行了上面的代码,你会发现添加新区块依旧非常快。这是因为目前的难度只有2(或者你的电脑性能非常好)。

如果你创建了一个难度为5的区块链实例,你会发现你的电脑会花费大概十秒钟来挖矿。随着难度的提升,你的防御攻击的保护程度越高。

免责声明

就像之前说的:这绝不是一个完整的区块链。它仍然缺少很多功能(像P2P网路)。这只是为了说明区块链的工作原理。

并且:由于单线程的原因,用JavaScript来挖矿并不快。

Part3:交易与挖矿奖励

在前面两部分我们创建了一个简单的区块链,并且加入了POW来抵御攻击。然而我们在途中也偷了懒:我们的区块链只能在一个区块中存储一笔交易,而且矿工没有奖励。现在,让我们解决这个问题!

重构区块类

现在一个区块拥有 index , previousHash , timestamp , data , hash 和 nonce 属性。这个 index 属性并不是很有用,事实上我甚至不知道为什么开始我要将它添加进去。所以我把它移除了,同时将 data 改名为 transactions 来更语义化。

class Block{
 constructor(timestamp, transactions, previousHash = &#39;&#39;) {
 this.previousHash = previousHash;
 this.timestamp = timestamp;
 this.transactions = transactions;
 this.hash = this.calculateHash();
 this.nonce = 0;
 }
}
登入後複製


当我们改变区块类时,我们也必须更改 calculateHash() 函数。现在它还在使用老旧的 index 和 data 属性。

calculateHash() {
 return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.transactions) + this.nonce).toString();
}
登入後複製

交易类

在区块内,我们将可以存储多笔交易。因此我们还需要定义一个交易类,一边我们可以锁定交易应当具有的属性:

class Transaction{
 constructor(fromAddress, toAddress, amount){
 this.fromAddress = fromAddress;
 this.toAddress = toAddress;
 this.amount = amount;
 }
}
登入後複製

这个交易例子非常的简单,仅仅包含了发起方( fromAddress )和接受方( toAddress )以及数量。如果有需求,你也可以在里面加入更多字段,不过这个只是为了最小实现。

调整我们的区块链

当前的最大任务:调整我们的区块链来适应这些新变化。我们需要做的第一件事就是存储待处理交易的地方。

正如你所知道的,由于POW,区块链可以稳定的创建区块。在比特币的场景下,难度被设置成大约每10分钟创建一个新区块。但是,是可以在创造两个区块之间提交新的交易。

为了做到这一点,首先需要改变我们区块链的构造函数,以便他可以存储待处理的交易。我们还将创造一个新的属性,用于定义矿工获得多少钱作为奖励:

class Blockchain{
 constructor() {
  this.chain = [this.createGenesisBlock()];
  this.difficulty = 5;
  // 在区块产生之间存储交易的地方
  this.pendingTransactions = [];
  // 挖矿回报
  this.miningReward = 100;
 }
}
登入後複製


下一步,我们将调整我们的 addBlock() 方法。不过我的调整是指删掉并重写它!我们将不再允许人们直接为链上添加区块。相反,他们必须将交易添加至下一个区块中。而且我们将 addBlock() 更名为 createTransaction() ,这看起来更语义化:

createTransaction(transaction) {
 // 这里应该有一些校验!
 // 推入待处理交易数组
 this.pendingTransactions.push(transaction);
}
登入後複製


挖矿

人们现在可以将新的交易添加到待处理交易的列表中。但无论如何,我们需要将他们清理掉并移入实际的区块中。为此,我们来创建一个 minePendingTransactions() 方法。这个方法不仅会挖掘所有待交易的新区块,而且还会向采矿者发送奖励。

minePendingTransactions(miningRewardAddress) {
 // 用所有待交易来创建新的区块并且开挖..
 let block = new Block(Date.now(), this.pendingTransactions);
 block.mineBlock(this.difficulty);
 // 将新挖的看矿加入到链上
 this.chain.push(block);
 // 重置待处理交易列表并且发送奖励
 this.pendingTransactions = [
   new Transaction(null, miningRewardAddress, this.miningReward)
 ];
}
登入後複製


请注意,该方法采用了参数 miningRewardAddress 。如果你开始挖矿,你可以将你的钱包地址传递给此方法。一旦成功挖到矿,系统将创建一个新的交易来给你挖矿奖励(在这个栗子里是100枚币)。

有一点需要注意的是,在这个栗子中,我们将所有待处理交易一并添加到一个区块中。但实际上,由于区块的大小是有限制的,所以这是行不通的。在比特币里,一个区块的大小大概是2Mb。如果有更多的交易能够挤进一个区块,那么矿工可以选择哪些交易达成哪些交易不达成(通常情况下费用更高的交易容易获胜)。

地址的余额

在测试我们的代码钱让我们再做一件事!如果能够检查我们区块链上地址的余额将会更好。

getBalanceOfAddress(address){
 let balance = 0; // you start at zero!
 // 遍历每个区块以及每个区块内的交易
 for(const block of this.chain){
  for(const trans of block.transactions){
   // 如果地址是发起方 -> 减少余额
   if(trans.fromAddress === address){
    balance -= trans.amount;
   }
   // 如果地址是接收方 -> 增加余额
   if(trans.toAddress === address){
    balance += trans.amount;
   }
  }
 }
 return balance;
}
登入後複製

测试

好吧,我们已经完成并可以最终一切是否可以正常工作!为此,我们创建了一些交易:

let savjeeCoin = new Blockchain();
console.log(&#39;Creating some transactions...&#39;);
savjeeCoin.createTransaction(new Transaction(&#39;address1&#39;, &#39;address2&#39;, 100));
savjeeCoin.createTransaction(new Transaction(&#39;address2&#39;, &#39;address1&#39;, 50));
登入後複製

这些交易目前都处于等待状态,为了让他们得到证实,我们必须开始挖矿:

console.log(&#39;Starting the miner...&#39;);
savjeeCoin.minePendingTransactions(&#39;xaviers-address&#39;);
登入後複製

当我们开始挖矿,我们也会传递一个我们想要获得挖矿奖励的地址。在这种情况下,我的地址是 xaviers-address (非常复杂!)。

之后,让我们检查一下 xaviers-address 的账户余额:

console.log(&#39;Balance of Xaviers address is&#39;, savjeeCoin.getBalanceOfAddress(&#39;xaviers-address&#39;));
// 输出: 0
登入後複製


我的账户输出竟然是0?!等等,为什么?难道我不应该得到我的挖矿奖励么?那么,如果你仔细观察代码,你会看到系统会创建一个交易,然后将您的挖矿奖励添加为新的待处理交易。这笔交易将会包含在下一个区块中。所以如果我们再次开始挖矿,我们将收到我们的100枚硬币奖励!

console.log(&#39;Starting the miner again!&#39;);
savjeeCoin.minePendingTransactions("xaviers-address");
console.log(&#39;Balance of Xaviers address is&#39;, savjeeCoin.getBalanceOfAddress(&#39;xaviers-address&#39;));
// 输出: 100
登入後複製

局限性与结论

现在我们的区块链已经可以在一个区块上存储多笔交易,并且可以为矿工带来回报。

不過,還是有些不足:發送貨幣是,我們不檢查發起人是否有足夠的餘額來實際進行交易。然而,這其實是一件容易解決的事情。我們也沒有創建一個新的錢包和簽名交易(傳統上用公鑰/私鑰加密完成)。

上面是我整理給大家的,希望今後對大家有幫助。

相關文章:

vue-cli建立的專案,配置多頁面的實作方法

web3.js增加eth.getRawTransactionByHash(txhash)方法步驟

#nodejs簡單存取及操作mysql資料庫的方法範例

#

以上是JavaScript實現區塊鏈的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

華為手機如何實現雙微信登入? 華為手機如何實現雙微信登入? Mar 24, 2024 am 11:27 AM

華為手機如何實現雙微信登入?隨著社群媒體的興起,微信已成為人們日常生活中不可或缺的溝通工具之一。然而,許多人可能會遇到一個問題:在同一部手機上同時登入多個微信帳號。對於華為手機用戶來說,實現雙微信登入並不困難,本文將介紹華為手機如何實現雙微信登入的方法。首先,華為手機自帶的EMUI系統提供了一個很方便的功能-應用程式雙開。透過應用程式雙開功能,用戶可以在手機上同

建議:優秀JS開源人臉偵測辨識項目 建議:優秀JS開源人臉偵測辨識項目 Apr 03, 2024 am 11:55 AM

人臉偵測辨識技術已經是一個比較成熟且應用廣泛的技術。而目前最廣泛的網路應用語言非JS莫屬,在Web前端實現人臉偵測辨識相比後端的人臉辨識有優勢也有弱勢。優點包括減少網路互動、即時識別,大大縮短了使用者等待時間,提高了使用者體驗;弱勢是:受到模型大小限制,其中準確率也有限。如何在web端使用js實現人臉偵測呢?為了實現Web端人臉識別,需要熟悉相關的程式語言和技術,如JavaScript、HTML、CSS、WebRTC等。同時也需要掌握相關的電腦視覺和人工智慧技術。值得注意的是,由於Web端的計

PHP程式設計指南:實作斐波那契數列的方法 PHP程式設計指南:實作斐波那契數列的方法 Mar 20, 2024 pm 04:54 PM

程式語言PHP是一種用於Web開發的強大工具,能夠支援多種不同的程式設計邏輯和演算法。其中,實作斐波那契數列是一個常見且經典的程式設計問題。在這篇文章中,將介紹如何使用PHP程式語言來實作斐波那契數列的方法,並附上具體的程式碼範例。斐波那契數列是一個數學上的序列,其定義如下:數列的第一個和第二個元素為1,從第三個元素開始,每個元素的值等於前兩個元素的和。數列的前幾元

如何在華為手機上實現微信分身功能 如何在華為手機上實現微信分身功能 Mar 24, 2024 pm 06:03 PM

如何在華為手機上實現微信分身功能隨著社群軟體的普及和人們對隱私安全的日益重視,微信分身功能逐漸成為人們關注的焦點。微信分身功能可以幫助使用者在同一台手機上同時登入多個微信帳號,方便管理和使用。在華為手機上實現微信分身功能並不困難,只需要按照以下步驟操作即可。第一步:確保手機系統版本和微信版本符合要求首先,確保你的華為手機系統版本已更新至最新版本,以及微信App

掌握Golang如何實現遊戲開發的可能性 掌握Golang如何實現遊戲開發的可能性 Mar 16, 2024 pm 12:57 PM

在現今的軟體開發領域中,Golang(Go語言)作為一種高效、簡潔、並發性強的程式語言,越來越受到開發者的青睞。其豐富的標準庫和高效的並發特性使它成為遊戲開發領域的一個備受關注的選擇。本文將探討如何利用Golang來實現遊戲開發,並透過具體的程式碼範例來展示其強大的可能性。 1.Golang在遊戲開發中的優勢作為靜態類型語言,Golang正在建構大型遊戲系統

如何在Golang中實現精確除法運算 如何在Golang中實現精確除法運算 Feb 20, 2024 pm 10:51 PM

在Golang中實現精確除法運算是一個常見的需求,特別是在涉及金融計算或其它需要高精度計算的場景中。 Golang的內建的除法運算子「/」是針對浮點數計算的,並且有時會出現精度遺失的問題。為了解決這個問題,我們可以藉助第三方函式庫或自訂函數來實現精確除法運算。一種常見的方法是使用math/big套件中的Rat類型,它提供了分數的表示形式,可以用來實現精確的除法運算

PHP遊戲需求實作指南 PHP遊戲需求實作指南 Mar 11, 2024 am 08:45 AM

PHP遊戲需求實現指南隨著網路的普及和發展,網頁遊戲的市場也越來越火爆。許多開發者希望利用PHP語言來開發自己的網頁遊戲,而實現遊戲需求是其中一個關鍵步驟。本文將介紹如何利用PHP語言來實現常見的遊戲需求,並提供具體的程式碼範例。 1.創造遊戲角色在網頁遊戲中,遊戲角色是非常重要的元素。我們需要定義遊戲角色的屬性,例如姓名、等級、經驗值等,並提供方法來操作這些

js和vue的關係 js和vue的關係 Mar 11, 2024 pm 05:21 PM

js和vue的關係:1、JS作為Web開發基石;2、Vue.js作為前端框架的崛起;3、JS與Vue的互補關係;4、JS與Vue的實踐應用。

See all articles