js和php闭包的使用和区别

匿名函数

如果只是省去函数名,单纯的当作一个函数式方法返回,只能称为匿名函数(闭包需要将匿名函数当作结果返回),比如:

// js
var foo = function(x, y) {
    return x + y ;
};
console.log(foo(1, 2));  // 3
<?php
// php
$foo = function($a, $b) {
    return $a + $b;
}; // 一定要加分号
echo $foo(1, 2); // 3

闭包

闭包通常是用来创建内部变量,使得这些变量不得被外部随意修改,而只能通过指定的函数接口去修改

js闭包

这里举一个阮老师博客里的例子,阮老师博客:学习Javascript闭包(Closure)

js基础

参考链接:深入理解JS中声明提升、作用域(链)和this关键字

  • js比较特殊的一点是:函数内部可以直接读取到全局变量(对于阮老师的这句话不是很能理解,大概是想表达的意思:父作用域的变量可以在子作用域直接访问,而不需要去声明访问真正的全局变量?)
    • 大部分语言,变量都是先声明在使用,而对于js,具有声明提升的特性(不管在哪里声明,都会在代码执行前处理)
    • 函数和变量的声明总是会隐式地被移动到当前作用域的顶部,函数的声明优先级高于变量的声明
    • var 会在当前作用域声明一个变量,而未声明的变量,会隐式地创建一个全局变量
// 声明提升
console.log(a);  // 1, 未报错
var a = 1;
// 上文链接中的例子
function testOrder(arg) {
    console.log(arg); // arg是形参,不会被重新定义
    console.log(a); // 因为函数声明比变量声明优先级高,所以这里a是函数
    var arg = 'hello'; // var arg;变量声明被忽略, arg = 'hello'被执行
    var a = 10; // var a;被忽视; a = 10被执行,a变成number
    function a() {
        console.log('fun');
    } // 被提升到作用域顶部
    console.log(a); // 输出10
    console.log(arg); // 输出hello
}; 
testOrder('hi');
/* 输出:
hi 
function a() {
        console.log('fun');
    }
10 
hello 
*/
// 全局作用域
var foo = 42;
function test() {
    // 局部作用域
    foo = 21;
}
test();
foo; // 21
// 全局作用域
foo = 42;
function test() {
    // 局部作用域
    var foo = 21;
}
test();
foo; // 42
  • js变量的查找是从里往外的,直到最顶层(全局作用域),并且一旦找到,即停止向上查找。所有内部函数可以访问函数外部的变量,反之无效
function foo(a) {
    var b = a * 2;
    function bar(c) {
        console.log(a, b, c);
    }
    bar(b * 3);
}
foo(2);
function foo() {
    var a = 1;
}
console.log(a);  //a is not defined
function foo1() {
    var num = 0;
    addNum = function() {  // 这里未通过var去声明,默认是全局变量
        num += 1;
    };
    function foo2() {
        console.log(num);
    }
    return foo2;
}
var tmp = foo1();
tmp();  // 0

addNum();
tmp(); // 1

这里第二次调用foo2函数,foo1函数的局部变量num并没有被初始化为0,说明打印的是内存中的num。正常函数在每次调用结束后都会销毁局部变量,在重新调用的时候会再次声明变量;而这边没有重新声明的原因是:把foo2函数赋值给了一个全局变量tmp,导致foo2函数一直存在内存中,而foo2函数依赖于foo1函数存在,所以foo1函数也存在内存中,并没有被销毁,所以foo1的局部变量也是存在内存中。

  • this的上下文基于函数调用的情况。和函数在哪定义无关,而和函数怎么调用有关。
    • 在全局上下文(任何函数以外),this指向全局对象(windows)
    • 在函数内部时,this由函数怎么调用来确定
      • 当函数作为对象方法调用时,this指向该对象

下面是阮老师博客里的两个思考题:

var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    return function(){
      return this.name;
    };
  }
};
alert(object.getNameFunc()()); // The Window
var name = "The Window";
var object = {
    name : "My Object",
  getNameFunc : function(){
      var that = this;
    return function(){
        return that.name;
    };
  }
};
alert(object.getNameFunc()());// My Object

this的作用域好像一直是个比较奇怪的东西,对于上面两个例子,我的理解是:第一个例子,是在方法里调用的this,而这个this并没有声明,会隐式地创建一个全局变量,所以调用的全局的name;第二个,调用的that的时候,会向顶级链式查找是否声明that,而这个that有this赋值,这里的this又是通过对象方法调用,则该this指向这个object对象,所有最终调用的是object作用域内的name。不知道这么理解是不是有问题,还望大神指正。

那其实js闭包的主要目的:访问函数内部的局部变量,即延长作用域链
参考链接:js闭包MDN文档

php闭包

php回调函数

mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )
mixed call_user_func_array ( callable $callback , array $param_arr )

这两个函数都是把第一个参数作为回调函数d调用,后面接收参数,区别就是第二个函数第二参数接收数组;在使用上唯一的区别就是,call_user_func函数无法接收引用传递; 个人觉得同样是使用,call_user_func 相比call_user_func_array完全可以被替代,不知道是不是有一些性能上的优势。具体使用样例,请参考官方文档。

<?php
// 引用传递
function foo(&$a, &$b) {
    $a ++;
    $b --;
};
$a = $b = 10;
call_user_func_array('foo', [&$a, &$b]);
echo $a."\n", $b; // 11, 9
基本用法

基本用法,跟js的闭包类似
- 普通调用

<?php
global $tmp = 'hello world';
function foo() {
    var_dump(global $tmp);
}
foo(); // null, 函数内部无法直接调用上级作用域的变量,除非声明为全局变量
<?php
$foo1 = function() {
    $a = 10;
    $foo2 = function() {
        var_dump($a);
    };
    return $foo2;
};
$tmp = $foo1();
$tmp();  // null,原因同上 
  • php想要能够获取上级作用域的变量,需要通过use传递
<?php
$foo1 = function () {
    $a = 10;
    $foo2 = function () use ($a) {
        var_dump($a);
        $a ++;
    };
    $foo2();
    return $foo2;
};
$tmp = $foo1();
$tmp();  // 10, 10,  use并不能实际改变变量的值,只是值传递
<?php
$foo1 = function () {
    $a = 10;
    $foo2 = function () use (&$a) {
        var_dump($a);
        $a ++;
    };
    $foo2();
    return $foo2;
};
$tmp = $foo1();
$tmp();  // 10, 11,  通过值传递改变变量的值
  • 下面两段代码的区别,不是很明白,望大佬指点,为什么后一个值传递就可以获取到已经改变后变量的值。好像都是在调用方法之前,已经执行过变量的递增了吧?
<?php
// 值传递
$foo1 = function () {
    $a = 10;
    $foo2 = function () use ($a) {
        var_dump($a);
    };
    $a ++;
    return $foo2;
};
$tmp = $foo1();
$tmp();  // 10
<?php
// 引用传递
$foo1 = function () {
    $a = 10;
    $foo2 = function () use (&$a) {
        var_dump($a);
    };
    $a ++;
    return $foo2;
};
$tmp = $foo1();
$tmp();  // 11
  • 正确使用
<?php
// 值传递
$foo = function () {
    $a = 10;
    $foo2 = function ($num) use ($a) {
        var_dump($num + $a);
    };
    return $foo2;
};
$tmp = $foo();
$tmp(100); // 110
<?php
// 引用传递
$foo = function () {
    $a = 10;
    $foo2 = function ($num) use (&$a) {
        var_dump($num + $a);
        $a ++;
    };
    return $foo2;
};
$tmp = $foo();
$tmp(100); // 110
$tmp(100); // 111
$tmp(100); // 112  跟js类似,保证变量常驻内存
php Closure 类

共同点

都是为了扩展作用域,获取内部变量

区别

js能够在方法内部直接获取到父级作用域的变量,而php需要通过use声明,并且默认是值传递

应用场景

  • 不是很能理解应用场景,搜索了一下,很多只是写了一个闭包实现的购物车,感觉并不是那么的实用。
  • 如果只是单纯的使用匿名函数,感觉还不如封装成一个私有方法
    >这些只是个人粗鄙的理解,望指正.

JSONP

JSONP是什么

跨域资源共享(Resources Domain Resources Sharing),客户端从不同的域名发送JSON响应时绕过浏览器限制

JSONP原理解析

利用 script标签 的 src属性 进行跨域请求,服务器响应函数调用传参。

2.1 静态方法创建

代码:

<?php
 //echo "a = 1";
?>
//html代码
<script type="text/javascript" src="http://ly.yungou.ws/test/index"></script>
<script type="text/javascript">
    console.log(a);   // 1
</script>

跨域请求成功!

2.2、 动态方法创建

动态创建 script标签的方式,添加到头部,来获取变量参数

<?php
    echo "var a = 1;";
 ?>

//通过 script语句动态创建 script标签进行请求
<script type="text/javascript">
   var script = document.createElement('script');
   script.src = 'http://ly.yungou.ws/test/index';
   var head = document.getElementsByTagName('head')[0];
   head.appendChild(script);
   console.log( a );
</script>

注意:原来动态创建 script 的方式发送请求 是异步的,虽然请求成功了,但是在使用变量时,请求还没有完成,相当于还没有定义变量,然后就报错了。
解决办法:使用callback

<?php
    echo 'callback(123)';
 ?>

<script type="text/javascript">
    var script = document.createElement('script');
    script.src = 'http://ly.yungou.ws/test/index';
    var head = document.getElementsByTagName('head')[0];
    head.appendChild(script);

    function callback(data){
        console.log( data );    //callback(123) 对 callback 调用 输出 123
    }
</script>

那我们就可以用这种动态的方式 很轻松的 拿到后台数据了,只不过前台声明的和 后台 调用的 函数名 需要一样才行,如上面的 然而这样也不太好,每次改动,那都要前后台对接一下。

所以我们可以把回调函数名放在参数中传输。案例如下:

<?php
$callback = $_GET[ 'callback' ];    // get 通过 callback键 得到 函数名
$userInfo = array( 'username' => 'leiyuan', 'password' => '123456' );// 生成数据
$data = json_encode($userInfo);
echo "{$callback}({$data});";// hello( json_encode($arr) )

通过 script语句动态创建 script标签进行请求

    var script = document.createElement('script');
    script.src = 'http://ly.yungou.ws/test/callback?callback=hello';
    var head = document.getElementsByTagName('head')[0];
    head.appendChild(script);
     function hello(data){
        console.log(data);// Object {username: "leiyuan", password: "123456"}        
    }
  </script>

2.3 jsonp原理:

JSONP是一个协议(并且是非正式传输协议)。 该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了.
由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script标签实现跨域请求,然后在服务端输出JSON数据并执行回调函数,从而解决了跨域的数据请求,

2.4 jsonp原理

首先在客户端注册一个callback, 然后把callback的名字传给服务器。
服务器先生成 json 数据。 然后以 javascript 语法的方式,生成一个function , function 名字就是传递上来的参数 jsonp. 最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。
客户端浏览器,解析script标签,并执行返回的 javascript 文档,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里.(动态执行回调函数)

JSONP和JSON的区别:
目前为止最被推崇或者说首选的方案还是用JSON来传数据,靠JSONP来跨域。
  JSON(JavaScript Object Notation)和JSONP(JSON with Padding)
JSON是一种数据交换格式,而JSONP是一种依靠开发人员的聪明才智创造出的一种非官方跨域数据交互协议。一个是描述信息的格式,一个是信息传递双方约定的方法。
  1、ajax和jsonp这两种技术在调用方式上“看起来”很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jquery等框架都把jsonp作为ajax的一种形式进行了封装;
  2、但ajax和jsonp其实本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加
```

JSONP使用注意事项
1、跨系统跨域名,获取基本信息,
ps:后端不能存在任何页面跳转登陆信息,否则会请求失败
2、jsonp的缺陷:不提供错误处理(对方拒绝请求,网络不通,请求地址或者参数不正确等等)如果动态插入的代码正常运行,你可以得到返回,但是如果失败了,那么什么都不会发生。
3、动态请求,都是异步请求。不支持同步请求,async无效。
请求不是通过XHR完成的,而是动态创建script标记,并请求url。跨域请求和dataType: "jsonp" 本身不支持同步操作。
4、若要在fun中使用jsonp请求后的参数,需要注意的是执行顺序,fun需要写在callback中才能成功调用参数。
5、如果没有定义jsonp和jsonpCallback,jsonp默认为"callback",jsonpCallback会是Jquery自动生成的函数名。
6、请求返回的是script tag,首先会调用jsonpCallback函数,不管是否找到该函数,都会调用success函数

参考资料

http://api.jquery.com/jQuery.ajax/

PhantomJS第一篇:安装及抓取网页为图片

编	写:袁	亮
时	间:2015-07-21
说	明:PhantomJS第一篇:安装及抓取网页为图片

一、是什么,解决什么问题
	1、是一个无界面的,webkit内核浏览器,能像一个真正的浏览器一样解析js,dom,css等
	2、应用场景
		抓取js后加载的内容
		将完整页面转为图片
		屏幕补抓
		自动化测试
		网络监控
	
二、下载安装:linux版
	1、2.0.0版本之前的,直接网上下载编译后的二进制文件即可,直接解压就可以运行
		如果找不到好的下载源,直接上249上,有1.4版本和1.9.8版本
		/opt/software/下
	2、源码安装
		2.1 下载源码
			a:直接下载某个版本
				wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.0.0-source.zip
				unzip phantomjs-2.0.0-source.zip
			b:git下载全部
				git clone git://github.com/ariya/phantomjs.git
				cd phantomjs
				git checkout 2.0
			c:下载太慢,直接从249上拷贝
				/opt/ci123/www/html/phantomjs
		2.2 安装依赖的模块
			yum -y install gcc gcc-c++ make flex bison gperf ruby \
			openssl-devel freetype-devel fontconfig-devel libicu-devel sqlite-devel \
			libpng-devel libjpeg-devel
		2.3 执行安装脚本(以小时记,好久好久...)
			./build.sh
	3、参考文档
		http://phantomjs.org/build.html
	4、执行方法
		./bin/phantomjs examples/post.js
			
三、下载安装:windows版本
	1、自行搜索下载
		http://phantomjs.org/download.html
	2、18上有2.0.0版本
		\\192.168.0.18\运维网络硬盘\r软件\phantomjs-2.0.0-windows.zip
	3、解压出来之后,可以直接运行
	4、命令行下执行:
		cd 到相应目录
		./bin/phantomjs.exe examples/post.js
		
四、简单使用,抓取一个网页,并保存为图片
	1、新建fetch.js文件
	2、输入以下代码
		var page = require('webpage').create();
		page.open('http://www.ci123.com/', function() {
			page.render('/opt/ci123/www/html/geekman/ci123.png');
			phantom.exit();
		});
	3、执行,并查看图片是否生成
		./bin/phantomjs fetch.js
		
五、遇到的问题及解决办法
	1、网页中的中文乱码,中文的地方,都是一堆方括号
		yum install bitmap-fonts bitmap-fonts-cjk
		ps:这个解决了乱码问题,但是导致了下面另外一个问题,坑了我大半天
	2、生成的png有一堆的透明效果
		www.ci123.com加使用美图看看导致,其实是正常的
		放在浏览器里看就正常了
	3、生成jpg格式图片,背景黑色
		执行之后,将body背景设置为白色即可
		page.evaluate(function(){
			document.body.bgColor = 'white';
		});
	4、有些内容后加载,导致页面有空白
		延时一定时间之后在渲染成图片
		window.setTimeout(function (){
			page.render("/opt/ci123/www/html/geekman/ci123.png");
			phantom.exit();
		}, 3000);
	5、有些内容,需要触发效应效果才出现,可以在page.evaluate中模拟
		page.evaluate(function(){
			document.body.bgColor = 'white';
			
			window.scrollTo(0,10000);//滚动到底部
			window.document.body.scrollTop = document.body.scrollHeight;
		});
	6、flash播放显示的内容,截取不到
		1.5版本之后就不再支持flash,如果要抓取flash的,需要安装1.4及之前的版本
	7、生成的图片,宽度不够,比如www.ci123.com大概只有960的样子
		设置webkit的宽高,让样式显示正常(可以调整这个的不同值,来抓取不同分辨率下的表现,特别是响应式布局的页面)
		page.viewportSize = {width: 1440,height: 800};
	8、在window下抓取,页面显示正常,但是在linux下抓取,页面排版跟浏览器上显示的有很大的区别
		8.1 版本一致,怀疑是版本的问题,装过1.4,1.5,1.9.2,1.9.8,2.0.0版本,都没用...
		8.2 百度、google了很久,发现别人都没这个问题
		8.3 怀疑是不是webkit内核不一样,所以在我的浏览器里正常,在服务器上的那个不一样,看过使用的webkit内核,是比较旧
			然后又是各种切换版本,测试,发现还是不行
		8.4 后来仔细看截出来的图,发现汉字跟浏览器里不大一样,怀疑是这个的问题
		8.5 将最开始安装的那两个中文字库删除,再截,发现,汉字乱码了,但是排版正常....
			所以问题就确定了,因为使用了不当的字体库,导致页面排版出错
		8.6 重装centos的字体库,又踩了个坑,这个见额外的文档

六、完整代码范例
	var page = require('webpage').create();
	page.viewportSize = {width: 1440,height: 800};
	var url = 'http://www.ci123.com/';
	page.open(url, function (status){
		if (status != "success")    {
			console.log('FAIL to load the address');
			phantom.exit();
		}

		var bb = page.evaluate(function()    {
			document.body.bgColor = 'white';

		// //此函数在目标页面执行的,上下文环境非本phantomjs,所以不能用到这个js中其他变量
			window.scrollTo(0,10000);//滚动到底部
			window.document.body.scrollTop = document.body.scrollHeight;
		});

		window.setTimeout(function (){
			page.render("/opt/ci123/www/html/geekman/ci123.png");
			phantom.exit();
		}, 3000);
	});
		
七、其他
	1、1.5版本之后不支持flash,如果要支持flash的话,请下载1.4以前的版本
	2、常用语法
		http://www.tuicool.com/articles/nieEVv
		http://www.cnblogs.com/justany/p/3279717.html
	3、官方文档
		http://phantomjs.org/documentation/
	4、examples文件夹下,有很多范例,直接参考使用,很好用