当前位置: 首页 > 编程日记 > 正文

二十分钟教你如何将区块链应用与函数计算相结合

前言

本篇文章适合对区块链应用感兴趣或是想要通过函数计算服务进一步开发区块链应用的新人。本文将结合阿里云区块链服务、阿里云函数计算服务、阿里云日志服务 以及社区应用 Marbles,手把手教大家如何将阿里云区块链服务与阿里云函数计算服务相结合,并进一步提供业务上的结合场景,供大家开拓思路。

本文分为以下几部分:

  • 函数计算与区块链
  • Marbles 区块链应用介绍
  • Marbles 区块链应用结合函数计算进行扩展示例
  • 区块链应用与函数计算在业务上结合的场景与价值探讨

函数计算与区块链

函数计算

函数计算是事件驱动的全托管计算服务。使用函数计算,无需采购与管理服务器等基础设施,只需编写并上传代码。函数计算为用户准备好计算资源,弹性地可靠地运行任务,并提供日志查询、性能监控和报警等功能。借助函数计算,可以快速构建任何类型的应用和服务,并且只需为任务实际消耗的资源付费。

下图为函数计算工作流程:

区块链

区块链可以理解为去中心的分布式记账系统,其是一种 分布式、去中心化的计算与存储架构 。区块链通过某种方式来记录数据,使用户可以信任区块链系统记录的数据。区块链中的记账节点会按照一致性协议记账。记账节点愿意按照一致性协议记账,是因为在一致性协议的设计中,诚实的记账节点会得到相应的奖赏,且诚实的记录比恶意篡改记录的收益更大。

依托于区块链网络的可信度,衍生出了智能合约的概念。什么是智能合约呢?现实生活中,买家与卖家要进行一笔交易,为了保证交易的顺利进行,双方会签订一份合约,合约中会声明双方各自的身份、权利以及义务。当交易出现纠纷时,买家与卖家根据当时签订的合约通过法律的手段解决纠纷。这种方式的不足之处在于解决纠纷的过程需要第三方权威来仲裁以及需要大量时间。那么,假使我们现在有一位可信公正的交易代理人。卖家将商品交给代理人,买家与代理人双方之间一手交钱一手交货。若买家拒绝购买,代理人会将商品归还给买家。买家也不会付了钱拿不到商品。智能合约就可以充当这样的代理人,其为区块链上一个包含合约代码和存储空间的虚拟账户,合约的代码控制智能合约的行为,合约的账户存储合约的状态。

由于有了智能合约,DApp (Decentralized Application 即去中心化应用)也应运而生。DApp 是运行在区块链网络上的应用软件,其上运行的代码我们称之为智能合约。

Marbles 区块链应用介绍

Marbles 区块链应用是一个 资产转移 应用演示。在 Marbles 区块链应用中多个用户可以创建并相互转移弹珠。 ( 即弹珠就是资产转移中的资产 )

上图中:

  • Amy、Alice、Ava 所在的小长方形是她们每个人的账户
  • 小长方形中的圆形弹珠是每个人账户中的资产,弹珠的颜色和大小是资产的属性
  • 点击小长方形中的加号是为某个账户创建弹珠(资产)
  • 将某个小长方形中的弹珠拖拽到右上方的垃圾桶中,是为某个账户删除弹珠(资产)
  • 将某个弹珠从一个小长方形拖拽到到另一个小长方形,是弹珠(资产)的转移
  • 每一步的操作会在下方的 BLOCKS 创建一个新的小正方形,这个小正方形就代表包含交易内容的区块

Marbles 区块链应用代码分成三部分:

  1. 链码 - 区块链网络中,对等节点所运行的代码。链码在此次介绍的 Marbles 应用中的主要作用是处理创建以及转移弹珠的逻辑。
  2. 客户端 - 浏览器中运行的代码,负责 Marbles 应用页面的渲染与交互。
  3. 服务端 - 服务器中运行的代码,充当 Marbles 应用与区块链网络之间的桥梁,其与客户端以及区块链网络中运行着链码的节点进行通信。

在 Marbles 应用中,当客户端发送消息给服务端,服务端与区块链网络通信的时序图大体上如下图所示:( 读者对详细过程感兴趣,可以阅读 Github IBM-Blockchain/marbles )
其中,Peer 节点(对等节点) 存在于区块链网络中,拥有账本并且可安装链码。Orderer 节点负责接收包含签名的交易,对未打包的交易进行排序生成区块,广播给 Peer 节点。

上图中:Client 以及 Server 是上文中所说的客户端以及服务端。

  1. 当用户创建或转移弹珠时,Client 客户端触发相应事件,并向 Server 服务端发起请求。
  2. Server 接收到事件信息后,首先会构建提案(也就是交易),提案是将事件信息进行封装,比如:此次交易的两方以及交易内容是什么。
  3. Server 将构建好的提案发送给区块链网络中的一个 Peer 节点,由 Peer 节点来对提案进行模拟,校验其合法性。为什么要这么做呢?因为链码的作用就是处理交易逻辑,而链码安装在 Peer 节点上,并没有安装在 Server 上。
  4. 如果 Peer 节点模拟提案成功,认为其合法,则会对提案进行背书,并向 Server 返回背书后的提案。背书可以理解为,当我们去购买东西并忘记带现金,付给对方一张 别人 给的支票。对方怀疑支票不可兑现的时候,我们在支票上签字并表明若这张 他人 给的支票不能兑换,则可以来找我们要现金。这个签字的动作就是背书,声明对事物或被认可人的支持。
  5. Server 将背书后的提案发送给 Orderer 节点。
  6. Orderer 节点对提案进行排序并打包进区块,将区块广播给区块链网络中的所有 Peer 节点。

Marbles 区块链应用结合函数计算进行扩展示例

假设说,现在有这么一个业务场景,需要在每次 Marbles 应用有事件发生时,要对事件进行相应处理,且这部分的处理代码会 经常迭代 。那么,在区块链应用更新需要较多时间的情况下,通过函数计算来处理是较为方便的。( 具体缘由在下一节将会继续探讨 )

接下来,笔者将带着大家模拟这个业务场景,扩展 Marbles Server 端代码。在每次事件发生时,将事件信息打包并通过函数计算的 HTTP 触发器,由函数计算来对事件信息做相应处理。

1. 准备工作

开通阿里云日志服务、函数计算服务、区块链服务

2. 在阿里云区块链服务中创建组织、创建联盟

3. 在阿里云区块链服务中创建通道

  • 点击相应组织
  • 点击通道
  • 点击添加通道,填写名称与组织,并创建
  • 点击新创建的通道右侧的待审批链接,同意审批

4. 部署 Marbles 应用以及链码

参考 阿里云区块链服务开发指南
注意事项:nodejs 版本为 v8

5. 下载并配置阿里云函数计算开发工具 fun

  • fun 是一个 Node.js 编写的命令行工具,通过 npm 进行安装:$ npm install @alicloud/fun -g
  • 通过在命令行输入 fun config,根据提示依次配置 Account IDAccess Key IdAccess Key Secret 以及 Default Region Name。可参考:服务地址 、 创建 AccessKey
  • 配置 template.yml

在项目根目录下创建一个 template.yml 文件:

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:marblesFC: # 服务名称Type: 'Aliyun::Serverless::Service'Properties:Description: 'fc test'LogConfig:  # 日志配置Project: test-log-project  # 日志 ProjectLogstore: test-log-store   # 日志 LogStoreprocessEvent: # 函数名Type: 'Aliyun::Serverless::Function'Properties:Handler: httpTrigger.handler # 文件名.方法名Runtime: nodejs8CodeUri: './'Timeout: 60Events:http-test: # 触发器名Type: HTTP # 触发器类型Properties:AuthType: ANONYMOUSMethods: ['GET', 'POST', 'PUT']test-log-project:  #  LogProject 名称Type: 'Aliyun::Serverless::Log'Properties:Description: 'just for test'test-log-store:    # LogStore 名称Type: 'Aliyun::Serverless::Log::Logstore'Properties:TTL: 10ShardCount: 1

上述文件做了如下事项:

  1. 在函数计算中创建了 marblesFC 服务
  2. 为 marblesFC 服务配置了 test-log-project 日志 Project 以及 test-log-store 日志 LogStore
  3. 在 marblesFC 服务中创建了 processEvent 函数,并为其设置入口函数为 httpTrigger.js 文件中的 handler 方法
  4. 为 processEvent 函数配置了 HTTP 触发器,触发器名为 http-test
  5. 创建了 test-log-project 日志 Project 以及 test-log-store 日志 LogStore
  • 在项目根目录下创建 httpTrigger.js 文件

var getRawBody = require('raw-body')
module.exports.handler = function (request, response, context) {// get request infogetRawBody(request, function (err, data) {var params = {path: request.path,queries: request.queries,headers: request.headers,method: request.method,body: data,url: request.url,clientIP: request.clientIP,}// you can deal with your own logic hereconsole.log(JSON.stringify(params.queries))// set responsevar respBody = new Buffer.from(JSON.stringify(params));// var respBody = new Buffer( )response.setStatusCode(200)response.setHeader('content-type', 'application/json')response.send(respBody)})
};
  • 在项目根目录下执行 fun deploy 进行部署
  • 在阿里云函数计算控制台中,进入到 marblesFC 服务下的 processEvent 函数,在代码执行页面记录下调用 HTTP 触发器的地址

6. 修改 Marbles Server 端代码

  1. 打开 Marbles 源代码根目录文件夹下的 app.js 文件
  2. 添加 var https = require('https'); https模块
  3. 修改 setupWebSocket 函数

  function setupWebSocket() {console.log('------------------------------------------ Websocket Up ------------------------------------------');wss = new ws.Server({ server: server }); // start the websocket nowwss.on('connection', function connection(ws) {// -- Process all websocket messages -- //ws.on('message', function incoming(message) {console.log(' ');console.log('-------------------------------- Incoming WS Msg --------------------------------');logger.debug('[ws] received ws msg:', message);var data = null;try {data = JSON.parse(message); // it better be json} catch (e) {logger.debug('[ws] message error', message, e.stack);}// --- [5] Process the ws message  --- //if (data && data.type == 'setup') { // its a setup request, enter the setup codelogger.debug('[ws] setup message', data);startup_lib.setup_ws_steps(data); // <-- open startup_lib.js to view the rest of the start up code} else if (data) { // its a normal marble request, pass it to the lib for processinghttps.get("此处填写触发 HTTP 触发器的 URL 地址?type="+data.type, function(res){console.log('test http trigger');});ws_server.process_msg(ws, data); // <-- the interesting "blockchainy" code is this way (websocket_server_side.js)}});// log web socket errorsws.on('error', function (e) { logger.debug('[ws] error', e); });// log web socket connection disconnects (typically client closed browser)ws.on('close', function () { logger.debug('[ws] closed'); });// whenever someone connects, tell them our app's statews.send(JSON.stringify(ws_server.build_state_msg())); // tell client our app state});// --- Send a message to all connected clients --- //wss.broadcast = function broadcast(data) {var i = 0;wss.clients.forEach(function each(client) { // iter on each connectiontry {logger.debug('[ws] broadcasting to clients. ', (++i), data.msg);client.send(JSON.stringify(data)); // BAM, send the data} catch (e) {logger.debug('[ws] error broadcast ws', e);}});};ws_server.setup(wss, null);}

7. 启动 Marbles

在控制台中进入 Marbles 项目,通过 gulp marbles_baas 启动 Marbles 应用

8. 创建弹珠或转移弹珠

试着在浏览器中创建弹珠或者用鼠标拖拽转移弹珠

9. 查看日志

对 Marbles 应用做了相应操作后,进入阿里云日志服务 test-log-project Project 下的 test-log-store LogStore,查看日志

我们看到当 Marbles Server 接收到事件后,会触发 HTTP 触发器,由函数计算 FC 来对事件做相应处理。( 简单起见,demo 的处理目前仅仅是记录日志,读者们可以自行扩展 )

区块链应用与函数计算在业务上结合的场景与价值探讨

通过制作上面的 demo,相信大家现在对于如何将区块链应用与函数计算相结合有了一定的认识。接下来,就让我们一起探讨下,区块链应用与函数计算在业务上有哪些结合的场景与价值。

下图是最原始的 Fabric SDK 时序图

为了让读者方便理解,我们仍旧使用 Marbles 应用的时序图,读者可以将 Marbles 应用中的 Server 理解为 Fabric SDK 时序图中的 Application,

在笔者看来,函数计算可以与区块链应用相结合的场景主要有以下三点:

1. 处理事件

处理事件的场景刚刚在 demo 中各位读者想必已经体验过了。那么,为什么笔者倡导用函数计算来处理事件呢?

通过函数计算可以将每一次的事件进行相应的处理,处理完成后发送给日志服务。同时,还可以在函数计算中设定定时触发器,在指定时间内,再次统计事件的信息,由此对区块链状态进行一个数据分析。

而关于这种方式的统计逻辑,很有可能是需要经常迭代的。因此,不适合将其逻辑放入区块链应用中,而是更适合放在小巧易迭代的函数计算场景中。

2. 附加业务

考虑一个这样的场景:现在 X 公司推出了一款新的支付 App,为了鼓励用户使用该公司的 App,该公司对外宣传当用户用该公司的 App 成功完成交易后,该公司会送大量优惠券以及积分。若该 App 是一款区块链应用,那么把优惠活动的业务逻辑放在哪里最合适呢?

笔者目前觉得是放在 Orderer 将交易打包成区块并广播的时候最合适,因为优惠活动的业务逻辑是经常会变化的,这类业务逻辑可以统称为附加业务,将附加业务抽象为一个个函数并放在函数计算中,容易更新迭代。

3. 验证交易

Peer 节点在模拟提案时,若是有复杂多变的逻辑,可以放入函数计算中,由 Peer 节点来负责调用。

以上三点就是笔者对于如何将区块链服务与函数计算相结合的思考,有不准确的地方,欢迎大家指出。



本文作者:泽尘

阅读原文

本文为云栖社区原创内容,未经允许不得转载。

相关文章:

【转】推荐!国外程序员整理的Java资源大全

构建 这里搜集了用来构建应用程序的工具。 Apache Maven&#xff1a;Maven使用声明进行构建并进行依赖管理&#xff0c;偏向于使用约定而不是配置进行构建。Maven优于Apache Ant。后者采用了一种过程化的方式进行配置&#xff0c;所以维护起来相当困难。Gradle&#xff1a;Grad…

科研成果汇总:收获的季节

感谢课题组各届成员(2006&#xff5e;2010级)长期不懈的努力与付出&#xff0c;特别感谢目前仍工作在第一线的各位成员的鼎力配合与协作&#xff0c;大家辛苦了&#xff01;同时&#xff0c;还要感谢研究所对我们组的大力支持与帮助&#xff01;谢谢大家&#xff01; BTW&#…

JavaScript 利用location对象实现跨页面传参

需求简述&#xff1a; 两个页面login.html和homepage.html&#xff0c;在login页输入用户名&#xff0c;点击登录会跳转到主页&#xff0c;主页拿到login页输入的用户名 实现思路&#xff1a; 1. 跳转&#xff1a;修改location.html 2. 拿参&#xff1a;处理location.searc…

编译u-boot时候,make distclean 出现rm:无法删除,****是一个目录

今天在编译u-boot的时候&#xff0c;make distclean&#xff0c;出现了rm:无法删除 "include/asm-arm/arch": 是一个目录。经过查看网友的解决办法&#xff0c;已经解决了。 具体解决办法&#xff1a;在uboot顶层有个mkconfig文件&#xff0c;vim打开后&#xff0c;在…

[转载]MaxtoCode对.Net程序加密的原理及解密探讨三(实例解密)

标 题: 【原创】MaxtoCode对.Net程序加密的原理及解密探讨三&#xff08;实例解密&#xff09;作 者: rick时 间: 2006-09-29,23:16:28链 接: http://bbs.pediy.com/showthread.php?t32658上一回我们试验了通过反射的方式获取method的源代码。这次我们就用一个实例来演示dump一…

MyEclipse教程:Web开发——创建Web片段项目

MyEclipse 在线订购年终抄底促销&#xff01;火爆开抢>>MyEclipse最新版下载本教程向用户展示了使用关联的Web项目创建Web片段项目的机制。 用户还可以获得要检查的示例项目。 在本教程中&#xff0c;用户将学习如何&#xff1a;创建Web片段和关联的Web项目部署并测试包含…

JavaScript 实现鼠标移动时实时获取其相对盒子的偏移

实现思路&#xff1a; 1. 获得盒子相对于页面的偏移量 div.offsetTop和div.offsetLeft 2. 获得鼠标相对于页面的偏移量 e.pageY和e.pageX 3. 前两者相减即可获得鼠标相对于盒子上沿和左侧的偏移 4. 使用鼠标移动事件 mousemove 实现效果&#xff1a; 代码 <!DOCTYPE h…

H.264 picture parameter sets成员值含义学习笔记

-picture parameter sets1. pic_parameter_set_idpic_parameter_set_id指明了在切片头中对应的某个psp.pic_parameter_set_id的值应该在0到255,包括0和2552. seq_parameter_set_idset_parameter_set_id表示激活的sps.seq_parameter_set_id应该取值在0到31之间,包括边界值.3. en…

软件开发进度管理

一、什么是软件项目管理  软件项目管理是按需求确定范围、按目标制定项目计划、按计划执行管理的过 程。对软件开发各阶段加强项目管理的根本目的在于增强对软件开发的控 制能力&#xff0c;提升软件开发的质量。软件项目的建设按软件工程的生命周期法可分为项目立项、启动、…

SQLServer之ISO游标使用

什么是游标 结果集&#xff0c;结果集就是select查询之后返回的所有行数据的集合。 游标则是处理结果集的一种机制吧&#xff0c;它可以定位到结果集中的某一行&#xff0c;多数据进行读写&#xff0c;也可以移动游标定位到你所需要的行中进行操作数据。 一般复杂的存储过程&am…

JavaScript实现鼠标拖拽登录框

拖拽的本质是登录框获得光标的偏移来调整自身的偏移。 拖拽发生时&#xff0c;光标相对盒子的坐标不会变化&#xff0c;相对于页面的坐标却在变化&#xff0c;二者的差值就是盒子需要的。 两个注意点&#xff1a; 1. 赋值给盒子的什么属性 outer.style.top e.pageY - y p…

c++ STL容器初探

什么是容器 首先&#xff0c;我们必须理解一下什么是容器&#xff0c;在C 中容器被定义为&#xff1a;在数据存储上&#xff0c;有一种对象类型&#xff0c;它可以持有其它对象或指向其它对像的指针&#xff0c;这种对象类型就叫做容器。很简单&#xff0c;容器就是保存其它对…

flutter开发中常用的dart插件

flutter插件官网地址&#xff1a;https://pub.dartlang.org/packages/ 1. image_picker 一个可以从图库选择图片&#xff0c;并可以用相机拍摄新照片的flutter插件 2. flutter_image 使用NetworkImageWithRetry 代替Image.network 加载网络图片可获得重试能力。 3. barcode_sca…

XML(eXtensible Markup Language)文件的解析

XML与HTML一样&#xff0c;文件中除了根节点以外&#xff0c;整个文件包含一个隐含根“/”,所以我们在解析文件同时一般采用XPath语法进行解析时&#xff0c;一般首先要以反“/”开始。 转载于:https://www.cnblogs.com/hibernate3-example/archive/2010/10/20/2492356.html

JavaScript 立即执行函数的两种写法

(function(str){console.log(str欢迎你~);})(行步至春深);(function(str) {console.log(str欢迎你~);}(行路易知难)); 可以看到&#xff0c;每种写法都比平常多出两个小括号&#xff0c;其中一个可以看作是调用&#xff0c;里面装参数&#xff0c;另一个可以看作防止语法错误。…

【Android动画】之Tween动画 (渐变、缩放、位移、旋转)

Android 平台提供了两类动画。 一类是Tween动画&#xff0c;就是对场景里的对象不断的进行图像变化来产生动画效果&#xff08;旋转、平移、放缩和渐变&#xff09;。 第二类就是 Frame动画&#xff0c;即顺序的播放事先做好的图像&#xff0c;与gif图片原理类似。 下面就讲一下…

Mac下PHP7.1+Nginx安装和配置

https://blog.csdn.net/haiyanggeng/article/details/79186982 PHP&#xff1a;7.1.13Nginx&#xff1a;1.12.2 1. 安装PHP# 添加源brew tap homebrew/dupesbrew tap homebrew/versionsbrew tap homebrew/homebrew-php#更新源brew update#安装brew install php71 --with-imap -…

黑木耳的功效是什么

黑木耳&#xff0c;生长在朽木上&#xff0c;形似人的耳朵&#xff0c;色黑或褐黑&#xff0c;故名黑木耳&#xff0c;又名木菌、树鸡。黑木耳营养极为丰富&#xff0c;据史料记载&#xff0c;它是上古时代帝王独享之佳品&#xff0c;含有大量的碳水化合物、蛋白质、铁、钙、磷…

Javascript 移动的海绵宝宝

效果描述&#xff1a; 做一个简单的动画效果&#xff0c;刚刷新页面时&#xff0c;SpongeBob在页面的左上角位置&#xff0c;随着时间推移&#xff0c;他匀速向右移动&#xff0c;直到右侧抵达页面右侧停下来。 分析&#xff1a; SpongeBob作为一张图片被存放在<img>里…

sqlserver任务导出Excle

--sql语句就用下面的存储过程 /*--数据导出Excel 导出查询中的数据到Excel,包含字段名,文件为真正的Excel文件,如果文件不存在,将自动创建文件,如果表不存在,将自动创建表基于通用性考虑,仅支持导出标准数据类型 使用方法&#xff1a; 直接复制执行创建储存过程-&#xff0d;陈…

Oracle集合操作

Oracle集合操作 UNION&#xff1a;并集&#xff0c;所有的内容都查询&#xff0c;重复的显示一次 UNION ALL&#xff1a;并集&#xff0c;所有的内容都显示&#xff0c;包括重复的 INTERSECT&#xff1a;交集&#xff1a;只显示重复的 MINUS&#xff1a;差集&#xff1a…

Mongodb 4.0+安装

mongodb 4.0&#xff1a;windows 环境选择默认安装路径&#xff1b;存储文件夹自定义&#xff1a; 1.原配置文件删除.mp2.data下新建db文件夹 Mongod -- dbpath D:MongoDB/data3.&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;close windows防火墙&#xff08;…

JavaScript 慢慢移动的海绵宝宝

前情提要&#xff1a;Javascript 移动的海绵宝宝 这个海绵宝宝是匀速运动、突然停下来的&#xff0c;有点不合理。现实中我们跑步都是速度慢慢减小到0。 要实现这个效果&#xff0c;就必须速度逐渐减小&#xff0c;本质上是盒子的步长逐渐减小。 step (targetLocation - nowL…

Linux(CentOS)目录操作命令、文件操作命令、压缩解压缩命令

一、目录操作命令  ls命令    — 功能说明&#xff1a;显示文件和目录列表。    — 命令格式&#xff1a;ls [参数] [<文件或目录> …]    — 常用参数&#xff1a;      -a : 不隐藏任何以“.”字符开始的条目。      -b : 用八进制形式显示非打…

阿里巴巴持续投入,etcd 正式加入 CNCF

2018 年 12 月 11 日&#xff0c;在 KubeCon CloudNativeCon 北美峰会上&#xff0c;etcd 项目正式加入云原生计算基金会&#xff08;CNCF&#xff09;。CNCF 是一个厂商中立的基金会、云原生技术推广和普及的领导者。 etcd 在 2013 年由李响&#xff0c;Brandon Philips, Al…

小机上监控AIX和数据库管理系统的运行情况直到性能优化(SQL语句优化和排除硬件问题)...

AIX下的命令 1)topas 检测操作系统的运行状况 2)nmon(c--cpu,m--memory,d--disk) 检测这3个的情况 ORACLE下的命令 提示&#xff1a;下面这些视图都是实时监控生产机上数据库的情况查询结果每个时刻都随数据库系统当时的情况在变化 &#xff08;1&#xff09; selectopname,…

从前端框架到前端架构参考资料

参考资料 • Wiki - MVC https://zh.wikipedia.org/wiki/MVC • Wiki - MVVM https://zh.wikipedia.org/wiki/MVVM • Mustach https://github.com/janl/mustache.js#usage • Handlebars Introduction | Handlebars • React React – A JavaScript library for building us…

(转)(c#)数据结构与算法分析 --树

树 首先&#xff0c;在win下&#xff0c;进入命令行&#xff0c;输入tree&#xff0c;它会以树的形式返回当前文件夹下的所有子文件夹及文件。如上图&#xff0c;就是一个树。就像一棵被颠倒过来的苹果树&#xff0c;每一个元素称之为节点&#xff0c;如图&#xff0c;A就是这棵…

.vimrc文件

1 set number 2 set shiftwidth4 3 set softtabstop4 4 set tabstop4 5 set expandtab 6 "set hlsearch 7 set noerrorbells 8 set smartindent 9 set autoindent 10 set nobackup 11 syntax on 12 filetype on 13 filetype plugin on 14 filetype indent on转载于:https:…

Javascript中undefined,NaN等特殊比较

以下内容转自&#xff1a;http://blog.csdn.net/hongweigg/article/details/380900931、问题&#xff1a;在Javascript中&#xff0c;typeof(undefined) undefined成立吗&#xff1f; 答案&#xff1a;不成立&#xff0c;全局函数 typeof()返回值类型为字符串类型&#xff0c;…