bakey的小世界
日历
网志分类
· 所有网志 (181)
· 胡思乱想 (3)
· 学习生活 (10)
· 新思路新人生 (0)
· 未分类 (168)
最新的评论
站内搜索
友情链接
· 歪酷博客
· 我的歪酷 非非共享界
· polly
· 高性能网络编程maillist

订阅 RSS

0045556

歪酷博客

bakey @ 2010-07-06 10:43

spiderMonkey实际上是一个类似JVM之类的虚拟机,它把js源代码解析成为中间码bytecode,然后执行这些中间码。首先我们会看看spiderMonkey是如何工作的,下一篇我们会看看js代码是如何与C函数互相调用的。
spiderMonkey解析器是单个,路径非常深的一个函数,这个函数每次会执行一个bytecode指令,使用switch语句,或者依靠编译器来跳转到当前指令指向的下一个代码段。JS-to-JS函数调用会进行javascript压栈,而不会增加C的栈。因为现在JS-C-JS的调用模式越来越普遍,解析器会不断地重入。
一些spidermonkey bytecode有很多特别的case,因为他们的参数不同。
编译器由以下组件组成:一个随机的逻辑扫描器,一个递归下降的parser,生成一个AST,还有一个进行树遍历的code generator
spidermonkey代码阅读笔记:
先忽略一些初始化过程。在通过js 的public API获得js global object和context后,外部只需要调用一下JS_EvaluateUCScriptForPrincipals或者类似的evaluate接口就可以运行js的源代码了。
而evaluate系列的函数流程为:
js_CompileScript -> js_Execute  -> JS_DestroyScript,
首先来分析编译过程:
js_CompileScript(cx, obj, principals, TCF_COMPILE_N_GO,
                              chars, length, NULL, filename, lineno);
在编译的时候,首先初始化parser的context:
js_InitParseContext.
    js_InitParseContext,首先第一步是初始化JSParseContext中的JSTokenStream这个数据结构。如:
        ts->userbuf.base = (jschar *)base;
        ts->userbuf.limit = (jschar *)base + length;
        ts->userbuf.ptr = (jschar *)base;
     userbuf中存储的数据为js源代码,length为源代码长度,所以userbuf.limit为数据的末尾。
    另外,初始化了tokenbuf这个数据结构,
      ts->tokenbuf.grow = GrowTokenBuf;
      ts->tokenbuf.data = cx;
     tokenbuf.grow为函数指针,指向分配内存空间的具体函数。data为JSContext
      //还有各种listener
     ts->listener = cx->debugHooks->sourceHandler;
     ts->listenerData = cx->debugHooks->sourceHandlerData;
    
然后调用
MaybeSetupFrame(cx, obj, fp, &frame);
这个函数的作用是初始化一下JSStackFrame frame这个数据结构,并且复制给cx->fp
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
js_PeekToken(JSContext *cx, JSTokenStream *ts)
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

js_InitCodeGenerator


  for (;;) {
        pc.tokenStream.flags |= TSF_OPERAND;
        tt = js_PeekToken(cx, &pc.tokenStream);
        pc.tokenStream.flags &= ~TSF_OPERAND;
        if (tt <= TOK_EOF) {
            if (tt == TOK_EOF)
                break;
            JS_ASSERT(tt == TOK_ERROR);
            script = NULL;
            goto out;
        }

        pn = Statement(cx, &pc.tokenStream, &cg.treeContext);
        if (!pn) {
            script = NULL;
            goto out;
        }

        /*
         * FIXME bug 346749: let declarations at the top level in a script are
         * turned into var declarations and do not introduce block nodes.
         */
        JS_ASSERT(!cg.treeContext.blockNode);

        if (!js_FoldConstants(cx, pn, &cg.treeContext) ||
            !js_EmitTree(cx, &cg, pn)) {
            script = NULL;
            goto out;
        }
        RecycleTree(pn, &cg.treeContext);
    }


 
bakey @ 2010-04-14 15:00

原文在此:http://www.matrix67.com/blog/archives/55

 看黑书介绍NP的时候有一个“不可解问题”,非常不可思议,费劲周折在网上查到了些英文资料,搞明白了,非常有意思,在这里说一下。
    不可解问题(Undecidable Decision Problem)指的是这样一种问题:他无论如何也不可能有一个正确的算法来解决。虽然不可思议,但这种问题被证明确实是存在的。图灵在1936年(那时还没电脑,我们的父亲是在没有设备支持的纯理论基础上提出来的,致敬)提出了第一个不可解问题的实例:The Halting Problem。
    The Halting Problem是问,输入一段程序代码和一个针对此程序的输入,能否编程判断运行这个程序后程序是否会终止。
    这个问题的答案是否定的。也就是说,不可能有一种算法可以正确判断一个指定的程序运行后,给予指定的输入,程序最后出不出得来。换句话说,The Halting Problem是一个不可解问题。
    虽然这感觉似乎不可能,但在严格的证明下谁也无法发言反对。
    证明过程非常简单,假设The Halting Problem是有解的,并且已经用程序实现了,那么我们只需要再编写一个程序Program Bug,就会发现存在矛盾。
    反证:既然解决The Halting Problem的算法已经实现了,那么我们一定能定义一个函数
Function Halting(a,b:input_type):boolean;
    其中,a是读入的程序源码,b是输入数据。这个函数的功能就是返回对于指定的程序源码和输入数据,程序是否能顺利退出。
    下面编写一个程序:
Program Bug;
var
    code:input_type;
begin
   get(code);   //读入code
   if halting(code,code) then repeat until false
      else halt;
end.

    好,现在运行Bug这个程序,并且输入Bug这个程序本身的代码。这样,halting(code,code)其实质就是在判断这个Bug程序本身了。如果The Halting Problem认为Bug程序会正常退出,那么就让程序进入一个死循环,否则立即退出程序。矛盾产生。
    //简直是在挑战表达力极限
    //做人要厚道,转帖请注明出处



 
bakey @ 2010-01-24 23:03

在mozilla/xpcom/sample下提供了一个C++写的sample 组件,但是用C++来撰写组件比较麻烦,在对代码的效率要求不高的情况下用javascript来写组件的话,会更加高效,而且更少错误。现在javascript的引擎也越来越高效,特别是mozilla的traceMonkey出来之后,所以看好以后js的前景。下面来说一下在XPCOM体系下如何用javascript来写一个组件。
原文见:https://developer.mozilla.org/en/How_to_Build_an_XPCOM_Component_in_Javascript

首先写一个idl文件:
#include "nsISupports.idl"

[scriptable, uuid(c840f5e7-e6a1-4a22-af5e-cb76c1532ccd)]
interface nsIHelloWorld : nsISupports
{
      string hello();
}; 
mozilla/dist/bin/xpidl -m typelib -w -v -I mozilla/dist/sdk/idl -e HelloWorld.xpt HelloWorld.idl
用以上这个命令生成xpt文件,如果你想生成.h文件的话,改一下参数,xpidl有help可以参考,这里不再赘述

然后就是.js文件:最简单的一个js文件,还有其他的一些信息可以通过这个js文件提供,详细的文件描述可以参见原文
//这一行是mozillafirefox3.0以后才有的feature,利用XPCOMUtils.jsm来自动帮我们生成一个modules

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

function HelloWorld() {
//如果你只是想让javascript访问这个组件,把下面这一样注释去掉
//this.wrappedJSObject = this;
}

HelloWorld.prototype = {
  classDescription: "My Hello World Javascript XPCOM Component",
  classID:          Components.ID("{51608599-e5d5-437c-8a2e-dfe9404d2b16}"),
  contractID:       "@dietrich.ganx4.com/helloworld;1",
  QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIHelloWorld]),
  hello: function() { return "Hello World!"; }
};
//给出你组件的名字
var components = [HelloWorld];
function NSGetModule(compMgr, fileSpec) {
  return XPCOMUtils.generateModule(components);
}
完成之后把js文件和xpt文件拷贝到mozilla/dist/bin/components下面。然后重启你的firefox或者其他application就可以使用你的组件了。c++使用方式如下:
nsCOMPtr<nsIHelloWorld> helloWorld = do_CreateInstance( NS_HELLOWORLD_CONTRACTID );
 if ( helloWorld )
 {
  char *ho =  new char[20];
  helloWorld->Hello( &ho );
  printf("%s\n" , ho );
 }
这种方式比用C++方式建立组件要简单,而且js编程比C++也爽很多。写到这里,不得不佩服一下XPCOM的设计,可以通过XPConnect来粘合C++和js,使得可以在C++中嵌入js脚本语言。记得云风现在在用lua嵌入C++,不知相比xpcom如何



 
bakey @ 2010-01-20 18:31

closure被翻译成“闭包”,感觉这东西被包装的太学术化。下面参考书本和网上资源简单探讨一下(理解不当之处务请留意)。
1、什么是闭包
官方的回答:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
看了上面的定义,如果你不是高手,我坚信你会和我一样愤怒的质问:这tmd是人话吗?
要理解闭包,还是代码最有说服力啊,上代码:


function funcTest()
{
  
var tmpNum=100//私有变量

  
//在函数funcTest内定义另外的函数作为funcTest的方法函数
  function innerFuncTest(
  {
       alert(tmpNum); 
//引用外层函数funcTest的临时变量tmpNum
  }

  
return innerFuncTest; //返回内部函数
}

//调用函数
var myFuncTest=funcTest(); 
myFuncTest();
//弹出100

上面代码中,注释已经写的清清楚楚。现在我们可以这么理解“闭包”:在函数体内定义另外的函数作为目标对象的方法函数(示例中就是在函数funcTest内定义另外的函数innerFuncTest作为funcTest的方法函数),而这个对象的方法函数反过来引用外层函数体中的临时变量(闭包是一种间接保持变量值的机制。示例中就是内部函数innerFuncTest引用外层函数funcTest的临时变量tmpNum,这里必须注意,临时变量可以包括外部函数中声明的所有局部变量参数和声明的其他内部函数)。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包(示例中,调用函数的时候,myFuncTest实际调用的是innerFuncTest函数,也就是说funcTest的一个内部函数innerFuncTest在funcTest之外被调用,这时就创建了一个闭包)。
2、两个利用闭包的例子
下面举两个例子,一个是因为闭包导致了问题,而另一个则利用闭包巧妙地通过函数的作用域绑定参数。
这两个例子相关的HTML标记片断如下:
<a href="#" id="closureTest0">利用闭包的例子(1秒后会看到提示)</a><br />
<a href="#" id="closureTest1">由于闭包导致问题的例子1</a><br />
<a href="#" id="closureTest2">由于闭包导致问题的例子2</a><br />
<a href="#" id="closureTest3">由于闭包导致问题的例子3</a><br />
(1)、因闭包而导致问题
上面的HTML标记片断中有4个<a>元素,现在要给后三个指定事件处理程序,使它们在用户单击时报告自己在页面中的顺序,比如:当用户单击第2个链接时,报告“您单击的是第1个链接”。为此,如果编写下列为后三个链接添加事件处理程序的函数:


function badClosureExample(){
    
for (var i = 1; i <4; i++) {
        
var element = document.getElementById('closureTest' + i);
        element .onclick 
= function(){
            alert(
'您单击的是第' + i + '个链接');
        }
    }
}

然后,在页面载入完成后(不然可能会报错)调用该函数:
window.onload = function(){
    badClosureExample();
}
看一下运行结果,此时单击后3个链接,会看到警告框中显示什么信息呢?——全都是“您单击的是第4个链接”。是不是令你感到十分意外?为什么?
分析:因为在badClosureExample()函数中指定给element.onclick的事件处理程序,也就是onclick那个匿名函数是在 badClosureExample()函数运行完成后(用户单击链接时)才被调用的。而调用时,需要对变量i求值,解析程序首先会在事件处理程序内部查 找,但i没有定义。然后,又到 badClosureExample()函数中查找,此时有定义,但i的值是4(只有i大于4才会停止执行for循环)。因此,就会取得该值——这正是闭 包(匿名函数)要使用其外部函(badClosureExample)作用域中变量的结果。而且,这也是由于匿名函数本身无法传递参数(故而无法维护自己 的作用域)造成的。
那么这个例子的问题怎么解决呢?其实方法有很多(自己不妨写一下看看),我认为比较简单直接的代码:


function popNum(oNum){
    
return function(){
                    alert(
'您单击的是第'+oNum+'个链接');
   }
}
function badClosureExample(){
    
for (var i = 1; i <4; i++) {
        
var element = document.getElementById('closureTest' + i);
        element .onclick 
=new popNum(i);
        }
}

(2)、巧妙利用闭包绑定参数
 还是上面的HTML片段,我们要在用户单击第一个链接时延时弹出一个警告框,怎么实现?答案是使用setTimeout()函数,这个函数会在指定的毫秒数之后调用一个函数,如:
setTimeout(someFunc,1000);
但问题是,无法给其中的someFunc函数传递参数。而使用闭包则可以轻松解决这个问题:

function goodClosureExample(oMsg){
    
return function(){
        alert(oMsg);
    };
}

函数goodClosureExample用来返回一个匿名函数(闭包)。而我们可以通过为它传递参数来使返回的匿名函数绑定该参数,如:
var good = goodClosureExample('这个参数是通过闭包绑定的');
而此时,就可以将绑定了参数的good函数传递给setTimeout()实现延时警告了:
setTimeout(good,1000) //此时good中已经绑定了参数
最后,测试通过的完整代码:


window.onload = function(){
    
var element = document.getElementById('closureTest0');
    
if (element) {
        
var good = goodClosureExample('这个参数是由闭包绑定的');
        element.onclick 
= function(){
            setTimeout(good, 
1000); //延迟1秒弹出提示
        }
    }
}

3、javascript的垃圾回收原理
(1)、在javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收;
(2)、如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。
在js中使用闭包,往往会给javascript的垃圾回收器制造难题。尤其是遇到对象间复杂的循环引用时,垃圾回收的判断逻辑非常复杂,搞不好就有内存泄漏的危险,所以,慎用闭包。ms貌似已经不建议使用闭包了。



 
bakey @ 2010-01-20 17:58


关 于js的作用域链,早有耳闻,也曾看过几篇介绍性的博文,但一直都理解的模棱两可。近日又精心翻看了一下《悟透Javascript》这本书,觉得写得太 深刻,在“代码的时空”一节里有一段介绍作用域链的地方寥寥数语,回味无穷(其实还是理解的模棱两可^_^)。现在整理下自己的读书笔记,顺便借鉴网上资 源,写下来。
一、从一个简单的问题说起
下面的js代码在页面中运行显示什么结果:

var arg = 1;
function fucTest(arg) {
    alert(arg);
    
var arg = 2;
    
//alert(arg);
}
fucTest(10
);
您的答案是什么?没错,就是弹出10。我的理解是这样的,funTest函数有一个形参arg,funTest函数传入实参10,alert方法把10弹出就是了,囧。
好,问题又来了:
var arg = 1;
function funcTest() {
    alert(arg);
    
var arg = 2;
}
arg 
= 10;
funcTest();
答案是什么?如果是5年前的我,肯定不会再往下想了,还是10!这么简单的问题还用想什么呀?我的理解是这样的:funTest函数是一个无参数的函数, 函数内部通过alert方法,调用外部(全局)的变量arg,在函数执行前,arg赋值为10,弹出arg值后改变arg值为2,所以弹出值为10。
真的是10吗?是还是不是?
测试的结果:弹出“undefined”,瀑布汗.
二、理解作用域链,从javascript运行机制说起
1、js的运行顺序
如果一个文档流中包含多个script代码段(用script标签分隔的js代码或引入的js文件),它们的运行顺序是:
步骤1. 读入第一个代码段(js执行引擎并非一行一行地执行程序,而是一段一段地分析执行的)
步骤2. 做语法分析,有错则报语法错误(比如括号不匹配等),并跳转到步骤5
步骤3. 对var变量和function定义做“预解析”(永远不会报错的,因为只解析正确的声明)
步骤4. 执行代码段,有错则报错(比如变量未定义)
步骤5. 如果还有下一个代码段,则读入下一个代码段,重复步骤2
步骤6. 结束
上面的分析已经足够清楚,步骤二、三和步骤四里的红色字体可能是我们新手理解上的一个盲点,尤其是步骤三的“预解析”,如果不清楚什么叫预解析,总觉得不踏实。而步骤四的“有错则报错”也是经常碰到的。举例来说:
function funcTest() {
    alert(arg);
    
var arg = 2;
}

funcTest();
上面这段代码执行时,弹出“undefined”,也就是说arg没有定义,js的变量不是不用定义也可以吗?
2、语法分析和“预解析”
(1)、从解释型语言的编译过程说起
众所周知,javascript是解释型语言,它不同于c#和java等编译型语言。对于传统编译型语言来说,编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成;但对于解释型语言来说,通过词法分析语法分析得到语法树后,就可以开始解释执行了。
a、词法分析
简单地说,词法分析是将字符流(char stream)转换为记号流(token stream)。
但是这个转换过程并不是可以用一句话就可以概括的那么简单,我们可以试着用伪代码理解一段简单的程序:

代码var result=x-y;的转换大致可以表示如下:

NAME "result"
EQUALS
NAME "x"
MINUS
NAME "y"
SEMICOLON

b、语法分析
简单地说,语法分析就是为了构造合法的语法分析树,而语法分析树可以直观地表示出推导的过程。
那么什么是语法分析树?简单地说,就是程序推导过程的描述。但是到底什么是语法树,请参考专业文章,本篇略过。
c、其他
通过语法分析,构造出语法分析树后,接下来还可能需要进一步的语义检查。对于传统强类型语言来说,语义检查的主要部分是类型检查,比如函数的实参和形参类型是否匹配等等。
结论:通过上面的分析可以看出,对于javascript引擎来说,肯定有词法分析和语法分析,之后可能还有语义检查、代码优化等步骤,等这些编译步骤完成之后(任何语言都有编译过程,只是解释型语言没有编译成二进制代码),才会开始执行代码。
(2)、执行过程
a、javascript的作用域机制
通过编译,javascript代码已经翻译成了语法树,然后会立刻按照语法树执行。
进一步的执行过程,需要理解javascript的作用域机制:词法作用域(lexcical scope)。通俗地讲,就是javascript变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,编译器通过静态分析就能确定,因此词法作用域也叫做静态作用域(static scope)。但需要注意,with和eval的语义无法仅通过静态技术实现,所以只能说javascript的作用域机制非常接近词法作用域(lexical scope).
javascript引擎在执行每个函数实例时,都会创建一个执行环境(execution context)。执行环境中包含一个调用对象(call object), 调用对象是一个scriptObject结构(scriptObject是 与函数相关的一套静态系统,与函数实例的生命周期保持一致),用来保存内部变量表varDecls、内嵌函数表funDecls、父级引用列表 upvalue等语法分析结构(注意varDecls和funDecls等信息是在语法分析阶段就已经得到,并保存在语法树中。函数实例执行时,会将这些 信息从语法树复制到scriptObject上)。
b、javascript作用域机制的实现方法
词法作用域(lexical scope)是javascript的作用域机制,还需要理解它的实现方法,就是作用域链(scope chain)。作用域链是一个name lookup机制,首先在当前执行环境的scriptObject中寻找,没找到,则顺着upvalue到父scriptObject中寻找,一直lookup到全局调用对象(global object)。
现在回过头来分析第二个问题:

var arg = 1;
function funcTest() {
    alert(arg);
    
var arg = 2;
}
arg 
= 10;
funcTest();

在执行funcTest函数时,也即进入了funcTest对应的作用域,js引擎在执行时,当遇到对变量名或者函数名的使用时,会首先在当前作用 域(也即funcTest对应的作用域)查找变量或者函数(显然,arg变量在funcTest对应的作用域里被定义为var arg=2 所以alert方法的参数采用的是当前作用域的arg,但是因为arg被定义在alert方法后,所以arg变量默认值为undefined)。当然,如 果没有找到就到上层作用域查找,依此类推(作用域范围可以持续到javascript运行环境的根:window对象)。
最后,让你看的更清楚,上面的代码其实可以等价于:

var arg = 1;
function funcTest() {
    var arg; //默认值undefined
    alert(arg);
   
 arg = 2;
}
arg 
= 10;
funcTest();


c、闭包(closure)
当一个函数实例执行时,会创建或关联到一个闭包。 (关于闭包,打算另写一篇学习笔记)
scriptObject用来静态保存与函数相关的变量表,闭包则在执行期动态保存这些变量表及其运行值;
闭包的生命周期有可能比函数实例长。函数实例在活动引用为空后会自动销毁;
闭包则要等要数据引用为空后,由javascript引擎回收(有些情况下不会自动回收,就导致了内存泄漏)。
ps:关于“执行过程”这一段比较拗口,名词很多,不过别被它们吓住,一旦理解了执行环境(execution context)、调用对象(call object)、词法作用域(lexical scope)、作用域链(scope chain)、闭包(closure)等这些概念,javascript的很多现象都能迎刃而解。
三、结语
通过第二段的分析,对照第一段笔者曾经做出的判断(你是不是也觉得笔者曾经的分析和结论很幼稚(哪怕有时结果碰巧也对!)?!不是一般的肤浅啊,^_^),你会发现原来javascript还有这么多“玄机”,而要真正理解精通又谈何容易?先“悟透”再说吧。



 
bakey @ 2010-01-11 14:01

现在河蟹太疯狂,到处咬。担心这些小博客网站随时挂掉,连博客大巴都挂了。是不是应该自己搞一个虚拟主机放博客呢?思考ing....


 
bakey @ 2010-01-11 12:29

很多时候,我们都需要接手一个新的系统,并且对其进行部分的改进。但是往往并没有充足的时间让我们去对这个存在的系统的代码行行细读,并理解其每一行的含义,并且了解每个函数在系统运行中所起到的作用。
这样往往会导致我们在修改代码的过程中欠缺考虑。拿我现在在做的一个事情来说。我需要从一个对图片进行解码的模块中在其解码之前抽取出图片的源数据,并保存到一个文件里面去。这个系统总共好几百万行,而且我并不熟悉图片解码的原理和算法。所以我只能凭借在阅读代码时对其函数的命名来猜测其作用,并且添加我需要的功能。结果改完出来一运行,提取出的图片大小总比原来少了一点,然后就是疯狂的尝试,猜想应该在哪个函数中添加,怎么添加等等。由此花费了一个整上午的时间在这件事情上面。
其实,事情本身不可谓不重要,花费的时间也不算是浪费。但是想到要花费这么多时间去改进一个小feature,是否值得呢?其中是否存在可优化的空间?由此提出一点小的看法:
1:最优的解决方案,在做改动之前花费更多的时间去熟悉系统的源代码,并且理解其原理和代码的实现,对于全盘的函数调用都有一个了解。这样在作出改动的时候相信可以只花费比原来更少的时间和更少的尝试次数便可以解决问题。但是,很可惜的是,这是一种很理想的状态,并且很多时候我们并没有这么充裕的事前时间去学习熟悉我们需要接手的系统。
2:次优解方案,首先自己拥有程序开发的更好的经验,充分利用各种脚本,程序等自动化技术来帮助debug,在尝试的时候对于出现的问题能够很快地解决。这样的解决方案的效率,其实更多的取决于个人的经验和对目标系统,操作系统的熟悉程度。我觉得在这个解决方案中,能够帮助我们更快解决问题的焦点在于:是否拥有更多的自动化debug,测试的敏感度,尽量将人工参与的程度减少到最低。
但是这个解决方案最大的问题在于和目标系统脱离了,能否快速解决问题,其实取决于你的运气,如果你在的方向不对,也许花上更多的时间也解决不了。
总的来说,这个问题更像是一个np问题。只能无限逼近,而不能完美解决。最好的方法也许就是随机尝试吧。在这个时候,花最小的事件能获得多大的收益是更重要的问题。


 
bakey @ 2009-08-19 16:20

现在每天都要登陆那个系统几次,就为了看一看当前的状态,就怕那个状态忽然有一天变成“未通过”,那就真是欲哭无泪了。想起来,已经好久没这么强烈地去尝试一件事情了,这种心态虽然给了我很大的斗志,但是对心理的折磨还真是挺大。希望以后自己能变强点,再也不需要这么折磨自己的神经了,呵呵