[OGRE]基础教程来七发:来谈一谈缓冲绑定
上一章我们处理监听的方案是,每一帧只处理一次。
这一次,当鼠标键盘的事件发生时,我们会立即处理它。
这里只是对缓冲输入的一个简单介绍,而不是完整的如何使用OIS的教程。
若想了解更多内容,请查阅相关的OIS使用教程。
初始的项目源码如下:
- #include "ExampleApplication.h"
- class TutorialFrameListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener
- {
- public:
- TutorialFrameListener(RenderWindow* win, Camera* cam, SceneManager *sceneMgr)
- : ExampleFrameListener(win, cam, true, true)
- {
- }
- bool frameStarted(const FrameEvent &evt)
- {
- if(mMouse)
- mMouse->capture();
- if(mKeyboard)
- mKeyboard->capture();
- return mContinue;
- }
- // MouseListener
- bool mouseMoved(const OIS::MouseEvent &e) { return true; }
- bool mousePressed(const OIS::MouseEvent &e, OIS::MouseButtonID id) { return true; }
- bool mouseReleased(const OIS::MouseEvent &e, OIS::MouseButtonID id) { return true; }
- // KeyListener
- bool keyPressed(const OIS::KeyEvent &e) { return true; }
- bool keyReleased(const OIS::KeyEvent &e) { return true; }
- protected:
- Real mRotate; // The rotate constant
- Real mMove; // The movement constant
- SceneManager *mSceneMgr; // The current SceneManager
- SceneNode *mCamNode; // The SceneNode the camera is currently attached to
- bool mContinue; // Whether to continue rendering or not
- Vector3 mDirection; // Value to move in the correct direction
- };
- class TutorialApplication : public ExampleApplication
- {
- public:
- void createCamera(void)
- {
- // create camera, but leave at default position
- mCamera = mSceneMgr->createCamera("PlayerCam");
- mCamera->setNearClipDistance(5);
- }
- void createScene(void)
- {
- mSceneMgr->setAmbientLight(ColourValue(0.25, 0.25, 0.25));
- // add the ninja
- Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh");
- SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode");
- node->attachObject(ent);
- // create the light
- Light *light = mSceneMgr->createLight("Light1");
- light->setType(Light::LT_POINT);
- light->setPosition(Vector3(250, 150, 250));
- light->setDiffuseColour(ColourValue::White);
- light->setSpecularColour(ColourValue::White);
- // Create the scene node
- node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Vector3(-400, 200, 400));
- // Make it look towards the ninja
- node->yaw(Degree(-45));
- // Create the pitch node
- node = node->createChildSceneNode("PitchNode1");
- node->attachObject(mCamera);
- // create the second camera node/pitch node
- node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode2", Vector3(0, 200, 400));
- node = node->createChildSceneNode("PitchNode2");
- }
- void createFrameListener(void)
- {
- // Create the FrameListener
- mFrameListener = new TutorialFrameListener(mWindow, mCamera, mSceneMgr);
- mRoot->addFrameListener(mFrameListener);
- // Show the frame stats overlay
- mFrameListener->showDebugOverlay(true);
- }
- };
- #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
- #define WIN32_LEAN_AND_MEAN
- #include "windows.h"
- INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
- #else
- int main(int argc, char **argv)
- #endif
- {
- // Create application object
- TutorialApplication app;
- try {
- app.go();
- } catch(Exception& e) {
- #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
- MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
- #else
- fprintf(stderr, "An exception has occurred: %s\n",
- e.getFullDescription().c_str());
- #endif
- }
- return 0;
- }
上一次我们使用的是无缓冲的输入,也就是说,在每一帧里我们查询OIS::Keyboard和OIS::Mouse实例的状态,以判断它们是否被按下。
而缓冲输入使用了一个listener接口,以便在事件发生时通知你的程序。
比如,当一个键被按下时,会触发一个 KeyListener::keyPressed 事件,
而当这个键被释放(不再按下)时,KeyListener::keyReleased 事件被触发给所有已注册的KeyListener类。
这些能用在追踪按键的时间,或判断按键在上一帧中是否没有被按下。
关于OIS的监听系统有一点要注意的是,对于每一个Keyboard,Mouse,Joystick对象只能有一个监听器,这样是为了简单(也为了速度)。
多次调用setEventCallback函数的结果是只有最后一次注册的监听器才得到事件消息。
如果你有多个对象需要获得Key,Mouse事件,你只有自己写一个消息分发。
还有,千万记得在frameStarted方法里调用Keyboard::capture和Mouse::capture。
OIS不会使用线程来确定键盘鼠标的状态,所以你必须指明什么时候去获取输入。
OIS的KeyListener接口提供了两个纯虚函数。
第一个是keyPressed函数,每次按下某个键时调用它,
还一个是keyReleased,每次离开某个键时调用它,
传入这些函数的参数是一个KeyEvent,它包含被按下/释放的按键的键码。
鼠标监听界面MouseListener接口比KeyListener接口要稍微复杂一些。
它包含查看何时鼠标键被按下/释放的函数: MouseListener::mousePressed 和 MouseListener::mouseReleased。
它还包含一个mouseMoved函数,当鼠标移动时调用它。
这些函数都接收一个MouseEvent对象,在state变量里保存着当前鼠标的状态。
需要注意的是,MouseState对象即包含了鼠标移动的相对XY坐标(即,从上一次调用MouseListener::mouseMoved开始,它所移动的距离),
还包含了绝对XY坐标(即,屏幕上的准确位置)。
在我们开始修改TutorialFrameListener之前,请注意先对TutorialFrameListener类做两处大的改变:
- class TutorialFrameListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener
我们继承了OIS的MouseListener和KeyListener类,这样我们才能从它们那里接收事件。
同样,调用ExampleFrameListener构造器也有变化:
- TutorialFrameListener( RenderWindow* win, Camera* cam, SceneManager *sceneMgr ):ExampleFrameListener(win, cam, true, true)
将后面两个参数改为true
指明了我们将要使用带缓冲的键盘鼠标输入。
再对类中的变量稍作修改:
- protected:
- Real mRotate; // 旋转常量
- Real mMove; // 运动常量
- SceneManager *mSceneMgr; // 当前的场景管理器
- SceneNode *mCamNode; // 当前摄像机附着的场景节点
- bool mContinue; // 是否要继续渲染
- Vector3 mDirection; // 指向正确的移动方向
mContinue变量是frameStarted方法的返回值。
当mContinue为false的时候,程序退出。
mDirection变量指定了在每一个帧里我们如何移动摄像机节点。
在构造器里,我们像在上次那样初始化一些变量,并把mContinue设成true。添加如下代码到TutorialFrameListener的构造器里:
- TutorialFrameListener( RenderWindow* win, Camera* cam, SceneManager *sceneMgr )
- :ExampleFrameListener(win, cam, true, true)
- {
- // Populate the camera and scene manager containers
- mCamNode = cam->getParentSceneNode();
- mSceneMgr = sceneMgr;
- // 设置旋转和移动速度
- mRotate = 0.13;
- mMove = 250;
- // 继续渲染
- mContinue = true;
- }
在ExampleFrameListener的构造器里已经取得了OIS的mMouse和mKeyboard对象。
我们调用这些输入对象的setEventCallback方法,把TutorialFrameListener注册成一个监听器。
- //注册监听器
- mMouse->setEventCallback(this);
- mKeyboard->setEventCallback(this);
最后,我们还要把mDirection初始化成零向量(因为我们最开始不需要它动):
- TutorialFrameListener( RenderWindow* win, Camera* cam, SceneManager *sceneMgr )
- :ExampleFrameListener(win, cam, true, true)
- {
- // Populate the camera and scene manager containers
- mCamNode = cam->getParentSceneNode();
- mSceneMgr = sceneMgr;
- // 设置旋转和移动速度
- mRotate = 0.13;
- mMove = 250;
- // 继续渲染
- mContinue = true;
- //注册监听器
- mMouse->setEventCallback(this);
- mKeyboard->setEventCallback(this);
- //零向量
- mDirection = Vector3::ZERO;
- }
在我们深入之前,我们应该设置
Escape
键用来退出程序。
找到TutorialFrameListener::keyPressed方法,每当键盘上一个键被按下时,都会调用这个方法并传入一个KeyEvent对象。
我们能够通过这个对象的key变量来获取按键的键码(KC_*)。基于这个值,我们构造一个switch,为绑定所有程序里用到的按钮。
我们需要在switch语句里为其它按钮做绑定。首先我们要让用户按1、2键进行视口的切换。
代码基本上与上次相同:
- case OIS::KC_1:
- mCamera->getParentSceneNode()->detachObject(mCamera);
- mCamNode = mSceneMgr->getSceneNode("CamNode1");
- mCamNode->attachObject(mCamera);
- break;
- case OIS::KC_2:
- mCamera->getParentSceneNode()->detachObject(mCamera);
- mCamNode = mSceneMgr->getSceneNode("CamNode2");
- mCamNode->attachObject(mCamera);
- break;
接下来我们要添加键盘移动。每次用户按下移动按键,我们都要朝正确的方向加上或者减去mMove:
- case OIS::KC_UP:
- case OIS::KC_W:
- mDirection.z -= mMove;
- break;
- case OIS::KC_DOWN:
- case OIS::KC_S:
- mDirection.z += mMove;
- break;
- case OIS::KC_LEFT:
- case OIS::KC_A:
- mDirection.x -= mMove;
- break;
- case OIS::KC_RIGHT:
- case OIS::KC_D:
- mDirection.x += mMove;
- break;
- case OIS::KC_PGDOWN:
- case OIS::KC_E:
- mDirection.y -= mMove;
- break;
- case OIS::KC_PGUP:
- case OIS::KC_Q:
- mDirection.y += mMove;
- break;
当按键被释放时,我们要立即取消mDirection向量上的移动。找到keyReleased方法,添加如下代码:
- switch (e.key)
- {
- case OIS::KC_UP:
- case OIS::KC_W:
- mDirection.z += mMove;
- break;
- case OIS::KC_DOWN:
- case OIS::KC_S:
- mDirection.z -= mMove;
- break;
- case OIS::KC_LEFT:
- case OIS::KC_A:
- mDirection.x += mMove;
- break;
- case OIS::KC_RIGHT:
- case OIS::KC_D:
- mDirection.x -= mMove;
- break;
- case OIS::KC_PGDOWN:
- case OIS::KC_E:
- mDirection.y += mMove;
- break;
- case OIS::KC_PGUP:
- case OIS::KC_Q:
- mDirection.y -= mMove;
- break;
- } // switch
- return true;
好了,我们能根据按键输入对mDirection进行更新了。下面的代码与上次是一样的,添加到frameStarted函数里:
- mCamNode->translate(mDirection * evt.timeSinceLastFrame, Node::TS_LOCAL);
接下来轮到鼠标了。我们从点击鼠标左键来控制灯的开关开始。
找到mousePressed函数并看看它的参数。用OIS,我们可以访问MouseEvent和MouseButtonID。
我们用MouseButtonID作为switch条件,来确定按下的是哪个按钮。用下面的代码替换掉mousePressed函数里的:
- Light *light = mSceneMgr->getLight("Light1");
- switch (id)
- {
- case OIS::MB_Left:
- light->setVisible(! light->isVisible());
- break;
- }
- return true;
剩下来的事情就是绑定鼠标右键来进入鼠标观察模式。
每当鼠标移动时我们都检查右键是否按下。如果是,我们基于相对运动来转动摄像机。
通过传入函数的MouseEvent对象,我们能获取相对运动。它包含一个switch变量,里面有鼠标的状态(是关于鼠标的详细信息)。
MouseState::buttonDown告诉我们是否一个特定的按钮被按下,而“X”和“Y”变量告诉我们鼠标的相对运动。找到mouseMoved方法,用以下代码替换掉原来的:
- if (e.state.buttonDown(OIS::MB_Right))
- {
- mCamNode->yaw(Degree(-mRotate * e.state.X.rel), Node::TS_WORLD);
- mCamNode->pitch(Degree(-mRotate * e.state.Y.rel), Node::TS_LOCAL);
- }
- return true;
至此,完整的代码如下:
- #include "ExampleApplication.h"
- class TutorialFrameListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener
- {
- public:
- TutorialFrameListener(RenderWindow* win, Camera* cam, SceneManager *sceneMgr)
- : ExampleFrameListener(win, cam, true, true)
- {
- // Populate the camera and scene manager containers
- mCamNode = cam->getParentSceneNode();
- mSceneMgr = sceneMgr;
- // set the rotation and move speed
- mRotate = 0.13;
- mMove = 250;
- // continue rendering
- mContinue = true;
- mMouse->setEventCallback(this);
- mKeyboard->setEventCallback(this);
- mDirection = Vector3::ZERO;
- }
- bool frameStarted(const FrameEvent &evt)
- {
- if(mMouse)
- mMouse->capture();
- if(mKeyboard)
- mKeyboard->capture();
- mCamNode->translate(mDirection * evt.timeSinceLastFrame, Node::TS_LOCAL);
- return mContinue;
- }
- // MouseListener
- bool mouseMoved(const OIS::MouseEvent &e)
- {
- if (e.state.buttonDown(OIS::MB_Right))
- {
- mCamNode->yaw(Degree(-mRotate * e.state.X.rel), Node::TS_WORLD);
- mCamNode->pitch(Degree(-mRotate * e.state.Y.rel), Node::TS_LOCAL);
- }
- return true;
- }
- bool mousePressed(const OIS::MouseEvent &e, OIS::MouseButtonID id)
- {
- Light *light = mSceneMgr->getLight("Light1");
- switch (id)
- {
- case OIS::MB_Left:
- light->setVisible(! light->isVisible());
- break;
- }
- return true;
- }
- bool mouseReleased(const OIS::MouseEvent &e, OIS::MouseButtonID id) { return true; }
- // KeyListener
- bool keyPressed(const OIS::KeyEvent &e)
- {
- switch (e.key)
- {
- case OIS::KC_ESCAPE:
- mContinue = false;
- break;
- case OIS::KC_1:
- mCamera->getParentSceneNode()->detachObject(mCamera);
- mCamNode = mSceneMgr->getSceneNode("CamNode1");
- mCamNode->attachObject(mCamera);
- break;
- case OIS::KC_2:
- mCamera->getParentSceneNode()->detachObject(mCamera);
- mCamNode = mSceneMgr->getSceneNode("CamNode2");
- mCamNode->attachObject(mCamera);
- break;
- case OIS::KC_UP:
- case OIS::KC_W:
- mDirection.z -= mMove;
- break;
- case OIS::KC_DOWN:
- case OIS::KC_S:
- mDirection.z += mMove;
- break;
- case OIS::KC_LEFT:
- case OIS::KC_A:
- mDirection.x -= mMove;
- break;
- case OIS::KC_RIGHT:
- case OIS::KC_D:
- mDirection.x += mMove;
- break;
- case OIS::KC_PGDOWN:
- case OIS::KC_E:
- mDirection.y -= mMove;
- break;
- case OIS::KC_PGUP:
- case OIS::KC_Q:
- mDirection.y += mMove;
- break;
- }
- return true;
- }
- bool keyReleased(const OIS::KeyEvent &e)
- {
- switch (e.key)
- {
- case OIS::KC_UP:
- case OIS::KC_W:
- mDirection.z += mMove;
- break;
- case OIS::KC_DOWN:
- case OIS::KC_S:
- mDirection.z -= mMove;
- break;
- case OIS::KC_LEFT:
- case OIS::KC_A:
- mDirection.x += mMove;
- break;
- case OIS::KC_RIGHT:
- case OIS::KC_D:
- mDirection.x -= mMove;
- break;
- case OIS::KC_PGDOWN:
- case OIS::KC_E:
- mDirection.y += mMove;
- break;
- case OIS::KC_PGUP:
- case OIS::KC_Q:
- mDirection.y -= mMove;
- break;
- } // switch
- return true;
- }
- protected:
- Real mRotate; // The rotate constant
- Real mMove; // The movement constant
- SceneManager *mSceneMgr; // The current SceneManager
- SceneNode *mCamNode; // The SceneNode the camera is currently attached to
- bool mContinue; // Whether to continue rendering or not
- Vector3 mDirection; // Value to move in the correct direction
- };
- class TutorialApplication : public ExampleApplication
- {
- public:
- void createCamera(void)
- {
- // create camera, but leave at default position
- mCamera = mSceneMgr->createCamera("PlayerCam");
- mCamera->setNearClipDistance(5);
- }
- void createScene(void)
- {
- mSceneMgr->setAmbientLight(ColourValue(0.25, 0.25, 0.25));
- // add the ninja
- Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh");
- SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode");
- node->attachObject(ent);
- // create the light
- Light *light = mSceneMgr->createLight("Light1");
- light->setType(Light::LT_POINT);
- light->setPosition(Vector3(250, 150, 250));
- light->setDiffuseColour(ColourValue::White);
- light->setSpecularColour(ColourValue::White);
- // Create the scene node
- node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Vector3(-400, 200, 400));
- node->yaw(Degree(-45));
- node->attachObject(mCamera);
- // create the second camera node/pitch node
- node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode2", Vector3(0, 200, 400));
- }
- void createFrameListener(void)
- {
- // Create the FrameListener
- mFrameListener = new TutorialFrameListener(mWindow, mCamera, mSceneMgr);
- mRoot->addFrameListener(mFrameListener);
- // Show the frame stats overlay
- mFrameListener->showDebugOverlay(true);
- }
- };
- #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
- #define WIN32_LEAN_AND_MEAN
- #include "windows.h"
- INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
- #else
- int main(int argc, char **argv)
- #endif
- {
- // Create application object
- TutorialApplication app;
- try {
- app.go();
- } catch(Exception& e) {
- #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
- MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
- #else
- fprintf(stderr, "An exception has occurred: %s\n",
- e.getFullDescription().c_str());
- #endif
- }
- return 0;
- }
运行效果如图:
[OGRE]基础教程来七发:来谈一谈缓冲绑定的更多相关文章
- Objective-C 基础教程第七章,深入理解Xcode
目录 Object-C 基础教程第七章,深入理解Xcode 0x00 前言 0x01 创建工程界面 0x02 主程序界面 ①顶部 Top Test(测试) Profile(动态分析) Analyze( ...
- salesforce零基础学习(七十五)浅谈SOSL(Salesforce Object Search Language)
在工作中,我们更多操作的是一个表的对象,所以我们对SOQL的使用很多.但是有时候,我们需要对几个表进行查询操作,类似salesforce的全局搜索功能,这时,使用SOQL没法满足功能了,我们就需要使用 ...
- python基础教程(七)
本章介绍如何将语句组织成函数,这样,可以告诉计算机如何做事. 下面编写一小段代码计算婓波那契数列(前两个数的和是第三个数) fibs = [0,1] # 定义一个列表,初始内容是0,1 for i ...
- 《手把手教你》系列基础篇(七十五)-java+ selenium自动化测试-框架设计基础-TestNG实现DDT - 中篇(详解教程)
1.简介 上一篇中介绍了DataProvider如何传递参数,以及和一些其他方法结合传递参数,今天宏哥接着把剩下的一些常用的也做一下简单的介绍和分享. 2.项目实战1 @DataProvider + ...
- 《手把手教你》系列基础篇(七十六)-java+ selenium自动化测试-框架设计基础-TestNG实现DDT - 下篇(详解教程)
1.简介 今天这一篇宏哥主要是结合实际工作中将遇到的测试场景和前边两篇学习的知识结合起来给大家讲解和分享一下,希望以后大家在以后遇到其他的测试场景也可以将自己的所学的知识应用到测试场景中. 2.测试场 ...
- 《手把手教你》系列基础篇(七十七)-java+ selenium自动化测试-框架设计基础-TestNG依赖测试- 上篇(详解教程)
1.简介 今天主要是讲解和分享:TestNG中一个类中有多个测试方法的时候,多个测试方法的执行顺序或者依赖关系的问题.如果不用dependsOnMethods,testNG会自动根据@Test方法名称 ...
- 《手把手教你》系列基础篇(七十八)-java+ selenium自动化测试-框架设计基础-TestNG依赖测试- 中篇(详解教程)
1.简介 上一篇讲解了依赖测试的各种方法,今天继续讲解依赖测试的方法,这一篇主要是讲解和分享通过xml文件配置组名依赖方法( 主要是测试组的用法).废话不说,直接上干货. 2.实例 测试组:一个组可包 ...
- SpringCloud2.0 Hystrix Feign 基于Feign实现断路器 基础教程(七)
1.启动[服务中心]集群,工程名:springcloud-eureka-server 参考 SpringCloud2.0 Eureka Server 服务中心 基础教程(二) 2.启动[服务提供者]集 ...
- (转)Windows驱动编程基础教程
版权声明 本书是免费电子书. 作者保留一切权利.但在保证本书完整性(包括版权声明.前言.正文内容.后记.以及作者的信息),并不增删.改变其中任何文字内容的前提下,欢迎任何读者 以任何形式(包括 ...
随机推荐
- android 在activity中改变标题栏的标题 tabActivity的标题改变
在activity中改变标题栏的标题是调用setTitle()方法,参数为标题名称. 而tabActivity跟Activity是一样的,因此在onCheckedChanged()方法中要动态改变标题 ...
- 如何用AndroidStudio导入github项目
最近一直在研究AndroidStudio,但是总会有这样那样的问题,特别是在github上看到一个很好地开源项目,想clone下来用用,就会出现很多蛋疼的问题,今天摸索着,结合一些大牛们的建议,轻轻松 ...
- Linux内存初始化
start_kernel -> setup_arch 在这个函数中我们主要看这几个函数. machine_specific_memory_setup max_low_pfn = setup_me ...
- Linux 线程优先级
http://www.cnblogs.com/imapla/p/4234258.html http://blog.csdn.net/lanseshenhua/article/details/55247 ...
- IIS UrlWriter配置(asp.net)
前提在建虚拟目录或网站时注意以下设置第一步:下载URLRewriter 添加URLRewriter和ActionlessForm(不添加只能在VS实现,IIS下会找不到页面). 第二步:配置web.c ...
- MapReduce架构设计
MapReduce采用Master/Slave的架构,其架构图如下: 它主要有以下4个部分组成: 1)Client 2)JobTracker JobTracke负责资源监控和作业调度.JobTrack ...
- iconv装换文件编码格式
最近在mac上编译xml文本文件的时候用vim打开文件汉字总是显示乱码,修改.vimrc,修改iterm编码格式各种方法都使用遍了.最后通过iconv工具将原来的文件编码格式直接转为UTF-8解决掉. ...
- MySQL sql_slave_skip_counter
因为mysql的主从复制是逻辑复制,所以slave在apply relay log的过程中,经常会遇到错误,而参数sql_slave_skip_counter可以设置跳过多少个event,让slave ...
- Mac OS X 10.10(yosemite)更新后,cocoapods出错, 及cocoapods更新至0.34后, 错误情况整理
1:Mac升级10.10后,执行pod install出错如下 QTX-Will-mini:NewHishop willbin$ pod install [!] Unable to load a sp ...
- ES6/ES2015核心内容
ECMAScript定义了: JS语言语法 – 语法解析规则.关键字.语句.声明.运算符等. 类型 – 布尔型.数字.字符串.对象等. 原型和继承 内建对象和函数的标准库 – JSON.Math.数组 ...