PHP Puzzle(一): 有趣的变量作用域-PHP中global和Javascript中的var关键字

昨天在网上看到几道有意思的PHP题, 下面这道题让我想起了对应的Javascript版本.

function multiply($b) {
$a = 100;
global $a;
return $a * $b;
}
echo multiply(100);

这段代码运行结果是什么呢? 别急着执行这段代码,先想想你的结果.然后再对比一下吧.

我们看先看看global的定义 http://www.php.net/manual/en/language.variables.scope.php 这里也没有太为规范的解释.只是说可以通过global关键字来访问全局变量. 这里还涉及到一个类型转换的问题.

大家都知道PHP脚本是编译为opcode逐语句执行的. 那么现在要一句语句解释就很容易了.

function multiply($b) {
$a = 100; // 定义局部变量$a
global $a; // 访问全局变量$a, $a变量现在的是全局变量了
return $a * $b; // 返回$a和$b的乘积
}
echo multiply(100);

这里可能比较困惑的的是现在变量$a到底是局部变量还是全局变量了.因为global在定义局部变量之后.所以$a变为了全局变量,而在最后输出结果的时候$a并没有值.所以最后在相乘的时候是 NULL * 100; 也就是0了;可能会有人有疑问, 后面只是把$a变为了全局变量, 他的值应该不变的啊. 让我通过下面的例子来看把:

function func($d) {
$a = 100;
global $a;

var_dump(get_defined_vars()); // get_defined_vars()返回当前作用域的所有变量信息
}

func();

array(2) {
["d"]=>
int(0)
["a"]=>
&NULL
}

变量a是NULL的一个引用,因为全局作用域内没有a这个变量. 所以即使在函数前面定义了一个a变量,但是它的值已经指向了全局作用域了.
实际上 global关键字首先从全局符号表中查找变量名叫做a的变量,并把这个变量值设置为当前作用域的符号表中的a变量(更新了当前变量的值). 如果全局作用域内没有这个变量则会在全局作用域内增加这个变量, 实现代码见: $PHP_SRC/Zend/zend_vm_execute.h

static int ZEND_FASTCALL zend_fetch_var_address_helper_SPEC_CONST(int type, ZEND_OPCODE_HANDLER_ARGS) {

// ...
if (zend_hash_find(target_symbol_table, varname->value.str.val, varname->value.str.len+1, (void **) &retval) == FAILURE) {
switch (type) {
case BP_VAR_R:
case BP_VAR_UNSET:
zend_error(E_NOTICE,"Undefined variable: %s", Z_STRVAL_P(varname));
/* break missing intentionally */
case BP_VAR_IS:
retval = &EG(uninitialized_zval_ptr);
break;
case BP_VAR_RW:
zend_error(E_NOTICE,"Undefined variable: %s", Z_STRVAL_P(varname));
/* break missing intentionally */
case BP_VAR_W: {
zval *new_zval = &EG(uninitialized_zval);

Z_ADDREF_P(new_zval);
zend_hash_update(target_symbol_table, varname->value.str.val, varname->value.str.len+1, &new_zval, sizeof(zval *), (void **) &retval);
}
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
}

//...
}

看了这个解释大家可能觉得理所当然.一句一句执行的嘛. 看完了PHP中全局作用域的例子,咱们再看看类似的Javascript中的局部变量的版本吧

var a = 1;
function multiply(b)
{
a = 100;
var a;

return a * b;
}
alert(a);
alert(multiply(100));

那这段代码的输出将会是多少呢?
如果还是同样的思路,结果可能是你的期望完全不一样的结果. 这里的var定义变量和php中global不是一样的东西, php中的global是会在运行时执行的.而Javascript中的var在运行之前就已经"处理"好了.在运行之前的"语法分析"(没有看过Javascript引擎的实现.姑且这么分把)过程中,multiply函数中出现了var a;则把变量a加到函数体内的"局部变量表"中了.在运行过程中并不会执行var a;这一句. 这也是Javascript"怪异"的地方.定义变量的位置并没有关系.所以在函数内定义局部变量最好放在函数体的前面.

所以第一个alert输出的1, 函数的执行并没有改版全局范围内的a变量; 第二就没有什么问题了, 是10000;

实现一个简单在线HTML编辑器

一直没有仔细研究过在线HTML编辑器,以前以为编辑功能很复杂,需要用大量的JavaScript来模拟编辑器的效果,以前都是使用一些开源的HTML编辑器,HTML在各网上随处可见,发表文章,评论。最近自己想做一个类似Things这样的Web版的应用,需要一个想Google  Notebook(可惜的是现在已经停止开发了) 那样的编辑功能,看看现在网上的这些编辑器都庞大了,都是一些自己根本用不到的功能,其实我的需求很简单:简单的编辑既可以,并且需要轻量级一些,因为页面上可能需要开很多个编辑器实例。 所有想自己也来研究一下,看看能不能自己开发一个。

几天前花了一个晚上用firebug看了下Google Docs是怎么做。第一个遇到的问题就是如何让光标停在鼠标点击所在得地方。我刚开始一味都是js模拟出来的,这得有多复杂啊。并且还要兼容各个浏览器,天啊!后来上网一搜发现,浏览器早就想到了我们会有这样的一个需求。其实很简单,两条语句就可以说明HTML编辑器的最为核心的部分:

document.designMode = 'On';
document.contentEditable = true;

参考Mozilla上的这篇文章,介绍了HTML编辑的基本信息,要自己DIY一个常用功能的HTML编辑,这篇文章已经够你用的了。

在你的网页中嵌入这两条语句试试看:),你就会发现你的网站整个得都变的可以编辑了。可以随便乱修改。 不过放心,这样修改并不会破坏你的网站, 当然你也不希望你的整个网站是可以编辑的。例如我们只希望别人发布一条评论,只希望评论输入框可以输入。 要实现这样的效果可以有两种做法:

第一种就是使用直接让某个元素变成可编辑的例如:

index.html

Test TextEditor

Hi, HTML Editor!

Your comment



点击该区域后该区域的内容就变成可以编辑的了,这是我们就是对其进行简单的编辑。你可能会觉得直接编辑的功能太简单了,比如想要插入链接或者图片什么的。就没有办法了。这些功能浏览器并没有帮我们做好,不过实现这些功能也不麻烦, 参考上面Mozilla的文档。浏览器都提供了常用的功能API。

一般的编辑器都会提供一个工具栏之类的按钮来编辑文本内容。比如我使用的WordPress提供的编辑器:

wordpress提供的编辑器

wordpress提供的编辑器

可以对文字内容进行操作,加粗下划线,字体,对齐等等,并且提供可视化以及HTML编辑模式。

这个和FCKeditor 以及tinymce之类的编辑器使用的编辑方式和上面我提到的直接编辑html对象的方法不一样,他们使用的是iframe,使用iframe有很多好处,iframe中的文档和当前文档并不会因为样式或者HTML结构而影响到彼此,所以大部分的编辑器都是使用这种方式。它们基本的方式都是:

  1. 在页面中使用一个不可见的字段比如:“input, textarea”之类表单字段,他们的值就是需要编辑的内容。
  2. 页面初始化好,比如载入编辑器相关的一些脚本,也是就是window.onload好以后。创建一个iframe来显示可编辑区域,iframe初始化好以后读取父窗(相对于这个iframe)口的这个不可见表单值的内容,使之成为iframe中的body的html,也就是把所有需要编辑的内容添加到iframe中
  3. iframe中的内容初始化好以后,在iframe中执行上面提到的:

    document.designMode = 'On';
    document.contentEditable = true;

    把iframe整个窗口变成可以编辑的
  4. 进行编辑,这时候的编辑可能需要一个工具栏,基于同样的原因,一般工具栏也会是一个iframe,显示它们自己的编辑按钮。编辑是就利用浏览器提供的接口来对ifame中的内容进行编辑
  5. 父窗口中提交表单之前或者你需要的地方需要把编辑器中编辑完的内容回写到你的表单字段中去,否则编辑结果没有保存写来就没有意义了。

基本原理就是这样。要做出这样一个东西来,需要的就是一些细活了。要想做出一个FCk这样好用的编辑器也不是那么简单的。但是至少我们知道它是怎么运作的。 这就够了。

花了点时间做了一个简单的编辑器,真正要用的话很多的细节还是需要好好处理的,代码没有怎么清理,是变想边写,不是很完整。

需要的同学可以下来参考参考。
猛击  >> 这里 << 下载代码。