由于強(qiáng)大的機(jī)器學(xué)習(xí)模型(特別是大型語言模型,如 Claude、Meta 的 LLama 2 等)被托管平臺/服務(wù)作為 API 調(diào)用公開,生成式 AI 開發(fā)已經(jīng)民主化。這使開發(fā)人員擺脫了對基礎(chǔ)設(shè)施的擔(dān)憂,并讓他們專注于核心業(yè)務(wù)問題。這也意味著開發(fā)人員可以自由使用最適合其解決方案的編程語言。Python 通常是 AI/ML 解決方案的首選語言,但該領(lǐng)域具有更大的靈活性。
在這篇文章中,您將看到如何利用 Go 編程語言將向量數(shù)據(jù)庫和諸如檢索增強(qiáng)生成 (RAG) 等技術(shù)與 langchaingo 一起使用。如果您是想要如何構(gòu)建和學(xué)習(xí)生成式 AI 應(yīng)用程序的 Go 開發(fā)人員,那么您來對地方了!
如果你正在尋找有關(guān)使用 Go for AI/ML 的介紹性內(nèi)容,請隨時查看我以前的博客和這個領(lǐng)域的開源項目。
首先,讓我們退后一步,在深入了解本文的動手部分之前了解一些背景信息。
大型語言模型 (LLM) 和其他基礎(chǔ)模型已經(jīng)在大量數(shù)據(jù)語料庫上進(jìn)行了訓(xùn)練,使它們能夠在許多自然語言處理 (NLP) 任務(wù)中表現(xiàn)出色。但最重要的限制之一是,大多數(shù)基礎(chǔ)模型和 LLM 使用靜態(tài)數(shù)據(jù)集,該數(shù)據(jù)集通常具有特定的知識截止點(例如,2022 年 1 月)。
例如,如果你要問一個發(fā)生在截止時間之后的事件,它要么無法回答(這很好),要么更糟,自信地回答不正確的回答——這通常被稱為幻覺。
我們需要考慮這樣一個事實,即 LLM 只根據(jù)他們接受訓(xùn)練的數(shù)據(jù)做出回應(yīng)——這限制了他們準(zhǔn)確回答有關(guān)專業(yè)或?qū)S兄黝}的問題的能力。例如,如果我要問一個關(guān)于特定AWS服務(wù)的問題,LLM可能會(也可能不會)給出準(zhǔn)確的答案。如果 LLM 可以使用官方的 AWS 服務(wù)文檔作為參考,那不是很好嗎?
它通過在響應(yīng)生成過程中動態(tài)檢索外部信息來增強(qiáng) LLM,從而將模型的知識庫擴(kuò)展到其原始訓(xùn)練數(shù)據(jù)之外。基于RAG的解決方案包含一個向量存儲,可以對其進(jìn)行索引和查詢以檢索最新和相關(guān)的信息,從而將LLM的知識擴(kuò)展到其訓(xùn)練截止點之外。當(dāng)配備 RAG 的 LLM 需要生成響應(yīng)時,它首先查詢向量存儲以查找與查詢相關(guān)的相關(guān)最新信息。這個過程確保模型的輸出不僅基于其預(yù)先存在的知識,而且用最新信息進(jìn)行擴(kuò)充,從而提高其響應(yīng)的準(zhǔn)確性和相關(guān)性。
雖然這篇文章只關(guān)注 RAG,但還有其他方法可以解決這個問題,每種方法都有其優(yōu)點和缺點:
我在上一段中多次提到向量存儲。這些只不過是存儲和索引向量嵌入的數(shù)據(jù)庫,這些向量嵌入是文本、圖像或?qū)嶓w等數(shù)據(jù)的數(shù)值表示。嵌入幫助我們超越基本搜索,因為它們代表了源數(shù)據(jù)的語義含義——因此有了語義搜索這個詞,它是一種理解單詞的含義和上下文以提高搜索準(zhǔn)確性和相關(guān)性的技術(shù)。矢量數(shù)據(jù)庫還可以存儲元數(shù)據(jù),包括對嵌入的原始數(shù)據(jù)源(例如,Web 文檔的 URL)的引用。
得益于生成式人工智能技術(shù),矢量數(shù)據(jù)庫也出現(xiàn)了爆炸式增長。其中包括已建立的 SQL 和 NoSQL 數(shù)據(jù)庫,您可能已經(jīng)在體系結(jié)構(gòu)的其他部分(例如 PostgreSQL、Redis、MongoDB 和 OpenSearch)中使用這些數(shù)據(jù)庫。但也有為矢量存儲定制的數(shù)據(jù)庫。其中一些包括 Pinecone、Milvus、Weaviate 等。
好吧,讓我們回到 RAG...
概括地說,基于 RAG 的解決方案具有以下工作流。這些通常作為有凝聚力的管道執(zhí)行:
歸根結(jié)底,這是作為更大應(yīng)用程序的一部分的集成,其中上下文數(shù)據(jù)(語義搜索結(jié)果)提供給 LLM(以及提示)。
每個工作流步驟都可以使用不同的組件執(zhí)行。博客中使用的包括:
您將了解這些單獨的部分是如何工作的。我們將在隨后的博客中介紹此體系結(jié)構(gòu)的其他變體。
確保您擁有:
我們可以使用一個 Docker 鏡像!
docker run --name pgvector --rm -it -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres ankane/pgvector
通過從其他終端登錄 PostgreSQL(使用 )來激活擴(kuò)展:pgvectorpsql
# enter postgres when prompted for password
psql -h localhost -U postgres -W
CREATE EXTENSION IF NOT EXISTS vector;
克隆項目存儲庫:
git clone https://github.com/build-on-aws/rag-golang-postgresql-langchain
cd rag-golang-postgresql-langchain
此時,我假設(shè)您的本地計算機(jī)已配置為使用 Amazon Bedrock
我們要做的第一件事是將數(shù)據(jù)加載到 PostgreSQL 中。在這種情況下,我們將使用現(xiàn)有的網(wǎng)頁作為信息來源。我已經(jīng)使用過這個開發(fā)人員指南——但請隨意使用你自己的!請確保在后續(xù)步驟中相應(yīng)地更改搜索查詢。
export PG_HOST=localhost
export PG_USER=postgres
export PG_PASSWORD=postgres
export PG_DB=postgres
go run *.go -action=load -source=https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html
應(yīng)獲得以下輸出:
loading data from https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html
vector store ready - postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable
no. of documents to be loaded 23
給它幾秒鐘。最后,如果一切順利,您應(yīng)該會看到以下輸出:
data successfully loaded into vector store
要進(jìn)行驗證,請返回終端并檢查表:psql
\d
您應(yīng)該會看到幾個表 — 和 .這些是創(chuàng)建的,因為我們沒有明確指定它們(沒關(guān)系,方便入門! 包含集合名稱,同時存儲實際的嵌入。langchain_pg_collectionlangchain_pg_embeddinglangchaingolangchain_pg_collectionlangchain_pg_embedding
| Schema | Name | Type | Owner |
|--------|-------------------------|-------|----------|
| public | langchain_pg_collection | table | postgres |
| public | langchain_pg_embedding | table | postgres |
您可以自省這些表:
select * from langchain_pg_collection;
select count(*) from langchain_pg_embedding;
select collection_id, document, uuid from langchain_pg_embedding LIMIT 1;
您將在langchain_pg_embedding表中看到 23 行,因為這是我們的網(wǎng)頁源被拆分為 langchain 文檔的數(shù)量(當(dāng)您加載數(shù)據(jù)時,請參閱上面的應(yīng)用程序日志)
快速繞道了解其工作原理......
數(shù)據(jù)加載實現(xiàn)在 load.go 中,但讓我們看看我們?nèi)绾卧L問向量存儲實例(在 common.go 中):
brc :=bedrockruntime.NewFromConfig(cfg)
embeddingModel, err :=bedrock.NewBedrock(bedrock.WithClient(brc), bedrock.WithModel(bedrock.ModelTitanEmbedG1))
//...
store, err=pgvector.New(
context.Background(),
pgvector.WithConnectionURL(pgConnURL),
pgvector.WithEmbedder(embeddingModel),
)
回到 load.go 中的加載過程。為此,我們首先使用內(nèi)置的 HTML 加載器以 ( 函數(shù)) 切片的形式獲取數(shù)據(jù)。schema.DocumentgetDocslangchaingo
docs, err :=documentloaders.NewHTML(resp.Body).LoadAndSplit(context.Background(), textsplitter.NewRecursiveCharacter())
然后,我們將其加載到 PostgreSQL 中。與其自己寫所有東西,不如使用向量存儲抽象,使用高級函數(shù):langchaingoAddDocuments
_, err=store.AddDocuments(context.Background(), docs)
偉大。我們設(shè)置了一個簡單的管道來獲取數(shù)據(jù)并將其攝取到 PostgreSQL 中。讓我們好好利用吧!
讓我們問一個問題。我將使用“我可以使用哪些工具來設(shè)計 dynamodb 數(shù)據(jù)模型?”,這與我用作數(shù)據(jù)源的文檔相關(guān) - 請根據(jù)您的場景隨意調(diào)整它。
export PG_HOST=localhost
export PG_USER=postgres
export PG_PASSWORD=postgres
export PG_DB=postgres
go run *.go -action=semantic_search -query="what tools can I use to design dynamodb data models?" -maxResults=3
您應(yīng)該會看到類似的輸出 — 請注意,我們選擇最多輸出三個結(jié)果(您可以更改它):
vector store ready==============similarity search results==============similarity search info - can build new data models from, or design models based on, existing data models that satisfy
your application's data access patterns. You can also import and export the designed data
model at the end of the process. For more information, see Building data models with NoSQL Workbench
similarity search score - 0.3141409============================similarity search info - NoSQL Workbench for DynamoDB is a cross-platform, client-side GUI
application that you can use for modern database development and operations. It's available
for Windows, macOS, and Linux. NoSQL Workbench is a visual development tool that provides
data modeling, data visualization, sample data generation, and query development features to
help you design, create, query, and manage DynamoDB tables. With NoSQL Workbench for DynamoDB, you
similarity search score - 0.3186116============================similarity search info - key-value pairs or document storage. When you switch from a relational database management
system to a NoSQL database system like DynamoDB, it's important to understand the key differences
and specific design approaches.TopicsDifferences between relational data
design and NoSQLTwo key concepts for NoSQL designApproaching NoSQL designNoSQL Workbench for DynamoDB
Differences between relational data
design and NoSQL
similarity search score - 0.3275382============================
現(xiàn)在你在這里看到的是前三個結(jié)果(感謝)。-maxResults=3
請注意,這不是我們問題的答案。這些是來自我們的向量存儲的結(jié)果,它們在語義上接近查詢——這里的關(guān)鍵字是語義。多虧了 中的向量存儲抽象,我們能夠輕松地將源數(shù)據(jù)攝取到 PostgreSQL 中,并使用該函數(shù)獲得與我們的查詢相對應(yīng)的頂級結(jié)果(參見 query.go 中的函數(shù)):langchaingoSimilaritySearchNsemanticSearch
請注意,(在撰寫本文時)langchaingo 中的 pgvector 實現(xiàn)使用余弦距離向量運算,但 pgvector 也支持 L2 和內(nèi)積 - 有關(guān)詳細(xì)信息,請參閱 pgvector 文檔。
好的,到目前為止,我們有:
這是 RAG(檢索增強(qiáng)生成)的墊腳石 - 讓我們看看它的實際效果!
為了執(zhí)行基于 RAG 的搜索,我們運行與上面相同的命令(幾乎),只是 () 略有變化:actionrag_search
export PG_HOST=localhost
export PG_USER=postgres
export PG_PASSWORD=postgres
export PG_DB=postgres
go run *.go -action=rag_search -query="what tools can I use to design dynamodb data models?" -maxResults=3
這是我得到的輸出(在您的情況下可能略有不同):
Based on the context provided, the NoSQL Workbench for DynamoDB is a tool that can be used to design DynamoDB data models. Some key points about NoSQL Workbench for DynamoDB:
- It is a cross-platform GUI application available for Windows, macOS, and Linux.
- It provides data modeling capabilities to help design and create DynamoDB tables.
- It allows you to build new data models or design models based on existing data models.
- It provides features like data visualization, sample data generation, and query development to manage DynamoDB tables.
- It helps in understanding the key differences and design approaches when moving from a relational database to a NoSQL database like DynamoDB.
因此,總而言之,NoSQL Workbench for DynamoDB 似乎是一個專門為建模和使用 DynamoDB 數(shù)據(jù)模型而設(shè)計的有用工具。
正如你所看到的,結(jié)果不僅僅是“這是你的查詢的前 X 個響應(yīng)”。相反,這是對這個問題的精心設(shè)計的回答。讓我們再次窺探幕后花絮,看看它是如何工作的。
與攝取和語義搜索不同,基于 RAG 的搜索不會直接由向量存儲實現(xiàn)公開。為此,我們使用一條鏈來處理以下事項:langchaingolangchaingo
這是鏈的樣子(參考 query.go 中的函數(shù)):ragSearch
result, err :=chains.Run(
context.Background(),
chains.NewRetrievalQAFromLLM(
llm,
vectorstores.ToRetriever(store, numOfResults),
),
question,
chains.WithMaxTokens(2048),
)
這只是一個例子。我嘗試了一個不同的問題,并將 maxResults 增加到 10,這意味著將使用向量數(shù)據(jù)庫中的前 10 個結(jié)果來制定答案。
go run *.go -action=rag_search -query="how is NoSQL different from SQL?" -maxResults=10
結(jié)果(同樣,對你來說可能不同):
Based on the provided context, there are a few key differences between NoSQL databases like DynamoDB and relational database management systems (RDBMS):
1. Data Modeling:
- In RDBMS, data modeling is focused on flexibility and normalization without worrying much about performance implications. Query optimization doesn't significantly affect schema design.
- In NoSQL, data modeling is driven by the specific queries and access patterns required by the application. The data schema is designed to optimize the most common and important queries for speed and scalability.
2. Data Organization:
- RDBMS organizes data into tables with rows and columns, allowing flexible querying.
- NoSQL databases like DynamoDB use key-value pairs or document storage, where data is organized in a way that matches the queried data shape, improving query performance.
3. Query Patterns:
- In RDBMS, data can be queried flexibly, but queries can be relatively expensive and don't scale well for high-traffic situations.
- In NoSQL, data can be queried efficiently in a limited number of ways defined by the data model, while other queries may be expensive and slow.
4. Data Distribution:
- NoSQL databases like DynamoDB distribute data across partitions to scale horizontally, and the data keys are designed to evenly distribute the traffic across partitions, avoiding hot spots.
- The concept of "locality of reference," keeping related data together, is crucial for improving performance and reducing costs in NoSQL databases.
In summary, NoSQL databases prioritize specific query patterns and scalability over flexible querying, and the data modeling is tailored to these requirements, in contrast with RDBMS where data modeling focuses on normalization and flexibility.
邊做邊學(xué)是一個很好的方法。如果您到目前為止已經(jīng)遵循并執(zhí)行了該應(yīng)用程序,那就太好了!
我建議您嘗試以下方法:
這是一個簡單示例,可讓您更好地了解構(gòu)建基于 RAG 的解決方案的各個步驟。這些可能會根據(jù)實現(xiàn)而略有變化,但高級想法保持不變。
我用作框架。但這并不總是意味著你必須使用一個。如果您需要在應(yīng)用程序中進(jìn)行精細(xì)控制或框架不符合您的要求,您還可以刪除抽象并直接調(diào)用 LLM 平臺 API。與大多數(shù)生成式 AI 一樣,這個領(lǐng)域正在迅速發(fā)展,我對 Go 開發(fā)人員有更多選擇來構(gòu)建生成式 AI 解決方案持樂觀態(tài)度。langchaingo
如果您有任何反饋或問題,或者您希望我圍繞此主題介紹其他內(nèi)容,請隨時在下面發(fā)表評論!
祝您建造愉快!
原文標(biāo)題:How To Use Retrieval Augmented Generation (RAG) for Go Applications
原文鏈接:https://dzone.com/articles/how-to-use-retrieval-augmented-generation-rag-for
作者:Abhishek Gupta
編譯:LCR
ready()類似于 onLoad()事件
ready()可以寫多個,按順序執(zhí)行
$(document).ready(function(){})等價于$(function(){})
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ready事件</title>
<script src="js/jquery-3.4.1.js" type="text/javascript"></script>
<script type="text/javascript">
// 文檔載入完便觸發(fā)ready方法
$(document).ready(function(){
$("div").html("ready go...");
})
// $(document).ready(function(){})==$(function(){})
$(function(){
$("p").click( function () {
$(this).hide();
});
});
$(function(){
$("#btntest").bind("click",function(){
$("div").html("剁吧...");
});
});
</script>
</head>
<body>
<h3>頁面載入時觸發(fā)ready()事件</h3>
<div></div>
<input id="btntest" type="button" value="剁手" />
<p>aaa</p>
<p>bbbb</p>
<p>ccc</p>
<p>dddd</p>
</body>
</html>
為被選元素添加一個或多個事件處理程序,并規(guī)定事件發(fā)生時運行的函數(shù)。
$(selector).bind( eventType [, eventData], handler(eventObject));
eventType :是一個字符串類型的事件類型,就是你所需要綁定的事件。
這類類型可以包括如下:
blur, focus, focusin, focusout, load, resize, scroll, unload, click, dblclick
mousedown, mouseup, mousemove, mouseover, mouseout, mouseenter
mouseleave,change, select, submit, keydown, keypress, keyup, error
[, eventData]:傳遞的參數(shù),格式:{名:值,名2:值2}
handler(eventObject):該事件觸發(fā)執(zhí)行的函數(shù)
<script type="text/javascript">
$(function(){
/*$("#test").bind("click",function(){
alert("世界會向那些有目標(biāo)和遠(yuǎn)見的人讓路!!");
});*/
/*
* js的事件綁定
ele.onclick=function(){};
* */
// 等同于上面的放方法
$("#test").click(function(){
alert("世界會向那些有目標(biāo)和遠(yuǎn)見的人讓路!!");
});
/*
1.確定為哪些元素綁定事件
獲取元素
2.綁定什么事件(事件類型)
第一個參數(shù):事件的類型
3.相應(yīng)事件觸發(fā)的,執(zhí)行的操作
第二個參數(shù):函數(shù)
* */
$("#btntest").bind('click',function(){
// $(this).attr('disabled',true);
$(this).prop("disabled",true);
})
});
</script>
<body>
<h3>bind()方簡單的綁定事件</h3>
<div id="test" style="cursor:pointer">點擊查看名言</div>
<input id="btntest" type="button" value="點擊就不可用了" />
</body>
<script type="text/javascript">
$(function(){
// 綁定click 和 mouseout事件
/*$("h3").bind('click mouseout',function(){
console.log("綁多個事件");
});*/
// 鏈?zhǔn)骄幊?
$("h3").bind('click',function(){
alert("鏈?zhǔn)骄幊?");
}).bind('mouseout',function(){
$("#slowDiv").show("slow");//讓slowDiv顯示
});
/*$("#test").click(function(){
console.log("點擊鼠標(biāo)了....");
}).mouseout(function () {
console.log("移出鼠標(biāo)了...");
});*/
$("#test").bind({
click:function(){
alert("鏈?zhǔn)骄幊?");
},
mouseout:function(){
$("#slowDiv").show("slow");
}
});
});
</script>
<body>
<h3>bind()方法綁多個事件</h3>
<div id="test" style="cursor:pointer">點擊查看名言</div>
<div id="slowDiv"style=" width:200px; height:200px; display:none; ">
人之所以能,是相信能
</div>
</body>
本文是全套java入門到架構(gòu)師教程之前端開發(fā)部分-jQuery的教學(xué)文檔,如需全套java入門道架構(gòu)師教程請留言或私信
一)回顧
break:結(jié)束循環(huán)、流程不再繼續(xù);
continue:結(jié)束這一次,然后再繼續(xù);
注意:雖然 continue可以繼續(xù),但是continue后面的內(nèi)容不會被執(zhí)行!
數(shù)據(jù)類型:1.簡單數(shù)據(jù)類型 2.復(fù)雜數(shù)據(jù)類型
簡單數(shù)據(jù)類型存儲單值:內(nèi)存的棧—客棧;
復(fù)雜數(shù)據(jù)類型存儲復(fù)雜值:內(nèi)存的堆—一堆;
運行方法 string number null undefined Boolean
簡單類型:var a=1;全部放在棧內(nèi);運算速度快!— 藍(lán)屏— 內(nèi)存被完全占滿了— 重啟
復(fù)雜數(shù)據(jù)類型:數(shù)組 Object var arr –棧 值 – 堆
很多學(xué)生的信息,就不能用很多變量來存儲!
(二)數(shù)組字面量
VAR ARR=[];
通過遍歷給arr 賦值,當(dāng)i不連續(xù)的時候,會變成稀疏數(shù)組
For(var i=0;i <=10;i++){
}
(三)函數(shù)
函數(shù)就是方法,可以理解成一個功能!
1.函數(shù)的聲明 function 方法名(){
功能實現(xiàn);
}
2.函數(shù)的調(diào)用 方法名();
(四)函數(shù)的參數(shù)
參數(shù)分為形式參數(shù)、實際參數(shù)
Function fn(形式參數(shù)){
Console.log(形式參數(shù));
}
fn(實際參數(shù));
參數(shù)詳解:
1.實參比形參多,多余的實參會被忽略;
2.實參比形參少,undefind; NaN 實參比形參少的時候,不會報錯,但是不會參與運算;
3.形參與實參參數(shù)名一樣的時候,他們也沒有任何關(guān)系;
(五)函數(shù)的返回值
在函數(shù)中運算完成之后,把結(jié)果返回出函數(shù)體,以便后續(xù)使用;
Function fn(形式參數(shù)){
Console.log(形式參數(shù));
Return 結(jié)果;
}
計算完成之后,不在方法體中進(jìn)行運算。而是計算完成之后通過return返回之后,再運算;
Var result=fn(實際參數(shù));
返回值詳解:
1.Return后面的內(nèi)容不會再執(zhí)行;
2.如果函數(shù)沒有返回值,調(diào)用會返回undefined;
3.一般運算之后,都會把結(jié)果返回出方法體,有利于程序的再次調(diào)用
(六)函數(shù)的相互調(diào)用
Function fn2(){
Console.log(“函數(shù)fn2的調(diào)用的過程”);
fn1();
}
Fn2();
Function fn1(){
Console.log(“函數(shù)fn2”);
}
比較三個數(shù)的大小?
(七)重載?
在其他語言里,函數(shù)名相同,但參數(shù)不同的情況稱之為重載;
在JS中,函數(shù)名相同,參數(shù)不同也被認(rèn)為是同一函數(shù),JS中沒有重載;
(八)函數(shù)的兩種聲明方式
8.1 函數(shù)聲明
Function fn(){
}
Fn();
8.2 函數(shù)表達(dá)式
var fn2=function(){
};
注意:
1.函數(shù)表達(dá)式后面要加;
2.Fn()會函數(shù)聲明內(nèi)部提升的過程;
3.函數(shù)表達(dá)式不會提升,會提示fn2 is not function;
(九)匿名函數(shù)
函數(shù)沒有函數(shù)名的情況,通過變量名來調(diào)用函數(shù),這個函數(shù)不需要名字;
(十)變量的作用域
全局變量 局部變量
全局變量:聲明在最外圍,在整個script中都能使用;
在函數(shù)內(nèi)部聲明,但是沒有var也是全局變量——但是不要這樣寫!
var a=10;
function fn(){
console.log(a);
}
局部變量:在函數(shù)內(nèi)部聲明的變量,只能在內(nèi)部使用;
var b=20;
function fn(){
var d=10;
}
(十一)遞歸
遞歸就是自己調(diào)用自己的方法
function fn (){
fn();
}
Fn();
注意:遞歸如果沒有退出條件就是死循環(huán)
var i=1;
function fn(){
i++;
if(i<10){
fn();
}
}
fn();
(十二)回調(diào)函數(shù)
把函數(shù)當(dāng)做參數(shù)進(jìn)行傳遞的就是回調(diào)函數(shù)!
function fn1(){
retrun 100;
}
function fn2(fn){
console.log( fn() + 100);
}
Fn2(fn1);
(十三)對象的方法
給對象設(shè)置的匿名函數(shù)與調(diào)用
var person=new Object();
person.name=“tom”;
person.age=18;
person.eat=function(){
console.log(“吃飯在呢!”);
}
Person.eat();
(十四)對象的字面量
var stu={};
stu.name=“tom”;
stu.age=18;
stu.sayHi=function (){
console.log(“HI”);
}
var stu2={
name:tom,
age:18,
sayHi=function(){
console.log(“HI”);
}
}
www.Codecombat.cn
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。