SpringBoot2.0整合Disconf配置中心实战

Disconf简介

配置中心使用开源 Distributed Configuration Management Platform(分布式配置管理平台) 统一管理配置
配置中心
包括 百度、滴滴打车、银联、网易、拉勾网 等知名互联网公司正在使用!

主要目标

  • 部署简单:同一个上线包,无须改动配置,即可在 多个环境中(dev/qa/pre/prod) 上线
  • 部署动态化:更改配置,无需重新打包或重启,即可 实时生效
  • 统一管理:提供web平台,统一管理 多个环境(dev/qa/pre/prod)、多个产品 的所有配置
  • 支持微服务架构

配置中心选型

  • 关于配置中心选型资料详见 《开源配置中心对比》

对接注意事项

  • httpclient 4.2.1以下的版本会报错, 推荐使用4.5.9 GA版本
  • 配置文件请保证配置正确性,如:配置项前后存在空格,配置格式,特殊字符,配置项编码等
  • spring5原client需重写ReloadingPropertyPlaceholderConfigurer类,super.parseStringValue 方法

SpringBoot对接

以下SpringBoot2 DisconfConfig 代码注入Bean配置对接方式可有效解决集成需要启动两次配置才生效的问题

1.  新建Disconf配置类DisconfConfig

import java.io.IOException;
import java.util.List;

import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.baidu.disconf.client.DisconfMgrBean;
import com.baidu.disconf.client.DisconfMgrBeanSecond;
import com.baidu.disconf.client.addons.properties.ReloadablePropertiesFactoryBean;
import com.baidu.disconf.client.config.inner.DisInnerConfigAnnotation;
import com.baidu.disconf.client.support.DisconfAutowareConfig;
import com.google.common.collect.Lists;

/**
 * Disconf配置中心
 * @author ByWei.Cn
 * @date 2019/08/28
 */
@Configuration
public class DisconfConfig {

    @DisInnerConfigAnnotation(name = "disconf.scanpackage")
    public String scanpackage;
    
    @DisInnerConfigAnnotation(name = "disconf.locations")
    public String locations;
    
    public DisconfConfig() {
        super();
        try {
            DisconfAutowareConfig.autowareConfig(this, "disconf.properties");
        } catch (Exception e) {
        }
    }

    @Bean(destroyMethod = "destroy")
    public DisconfMgrBean disconfMgrBean() {
        DisconfMgrBean mgr = new DisconfMgrBean();
        mgr.setScanPackage(scanpackage);
        return mgr;
    }

    @Bean(initMethod = "init", destroyMethod = "destroy")
    public DisconfMgrBeanSecond disconfMgrBeanSecond() {
        return new DisconfMgrBeanSecond();
    }
    
    @Bean
    public ReloadablePropertiesFactoryBean reloadablePropertiesFactoryBean() {
        ReloadablePropertiesFactoryBean reloadProp = new ReloadablePropertiesFactoryBean();
        String[] props = locations.split(",");
        List propsList = Lists.newArrayList();
        for (String prop : props) {
            propsList.add("classpath:"+prop);
        }
        reloadProp.setLocations(propsList);
        return reloadProp;
    }

    @Bean
    public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer(ReloadablePropertiesFactoryBean reloadablePropertiesFactoryBean) {
        PropertyPlaceholderConfigurer prop = new PropertyPlaceholderConfigurer();
        prop.setIgnoreResourceNotFound(true);
        prop.setIgnoreUnresolvablePlaceholders(true);
        try {
            prop.setPropertiesArray(reloadablePropertiesFactoryBean.getObject());
        } catch (IOException e) {
             e.printStackTrace();
        }
        return prop;
    }
}

2.  在disconf.properties文件中新增自定义扩展配置

# scan package
disconf.scanpackage=cn.bywei.demo
# properties locations','split
disconf.locations=bywei-conf.properties

 

Disconf对接小技巧

  • 获取disconf配置项:DisClientConfig.getInstance().ENV
  • 多配置可通过自定义启动变量:
    xml获取- #{environment[‘project.channel’]} ;
    注解JAVA代码获取-DisconfConfig implements EnvironmentAware

HTTP Basic Authorization身份认证模式,抢先认证HttpClient 4.5.3 实例

Basic认证

关于HTTP BASIC认证

Basic 认证是服务器与客户端之间进行认证的一种方式,最初是在HTTP 1.0 规范(RFC 1945)中定义,后续的有关安全的信息可以在HTTP 1.1规范(RFC 2616)和HTTP认证规范(RFC 2617)中找到。HTTP Basic Authentication认证的服务,需要提供用户名和密码,否则服务器就会返回401。

HTTP BASIC认证流程原理

HTTP使用BASIC认证的原理是在HTTP协议进行通信的过程中,HTTP协议定义了基本认证过程以允许HTTP服务器对WEB浏览器进行用户身份认证的方法。
实现方法:当一个客户端向HTTP服务器进行数据请求时,如果客户端未被认证,则HTTP服务器将通过基本认证过程对客户端的用户名及密码进行验证,以决定用户是否合法。客户端在接收到HTTP服务器的身份认证要求后,会提示用户输入用户名及密码, 用户输入后,客户端将用户名和密码中间用“:”分隔合并,并将合并后的字符串用BASE64编码,在每次请求数据 时,将密文附加于请求头(Request Header)Authorization: Basic XXXXXXX中。HTTP服务器在每次收到请求包后,根据协议取得客户端附加的用户信息(BASE64编码的用户名和密码),解开请求包,对用户名及密码进行验证,如果用 户名及密码正确,则根据客户端请求,返回客户端所需要的数据;否则,返回错误代码或重新要求客户端提供用户名及密码。

JAVA HttpClient 4.5.3 抢先认证实例

原HttpClient 4.1.1 实例方法DefaultHttpClient等已过时废弃, 如下测试实例中,实现了两种方法:
1)通过HttpClient提供的抢先认证方式,直接跳过验证是否登录,把Basic Authorization信息发送给服务器
2)直接添加header的方式,HttpClient抢先认证方式原理同理

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Base64;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
public class HttpsClientTest {

	@Test
	public void test_auth_basic_should_success() throws Exception {
		String url ="http://192.168.1.100:8080/bywei/api/authtest";
		HttpHost targetHost = new HttpHost("192.168.1.100", 8080, "http");
		HttpGet get = new HttpGet(url);
		UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("test-auth", "=^ad.*sa:_2_RXa*23");
		
		// 使用 AuthCache 方式将使用RestClient 中的 MainClientExec方法,在发送请求时直接携带Authorization header信息
    	       AuthCache authCache = new BasicAuthCache();
    	       // Generate BASIC scheme object and add it to the local auth cache
    	      BasicScheme basicAuth = new BasicScheme();
    	      authCache.put(targetHost, basicAuth);
    	      // Add AuthCache to the execution context
    	      BasicHttpContext localcontext = new BasicHttpContext();
    	      localcontext.setAttribute(HttpClientContext.AUTH_CACHE, authCache);
		
		String content = doExecute(get, targetHost, credentials, localcontext);
		System.out.println("Authorization Basic认证authCache请求响应:"+content);
	}
	
	@Test
	public void test_auth_header_should_success() throws Exception {
		String url ="http://192.168.1.100:8080/bywei/api/authtest";
		HttpHost targetHost = new HttpHost("192.168.1.100", 8080, "http");
		HttpGet get = new HttpGet(url);
		String token = "test-auth:=^ad.*sa:_2_RXa*23";
		byte[] auth = Base64.getEncoder().encode(token.getBytes("UTF-8"));
		get.addHeader("Authorization", "Basic " + new String(auth));
		HttpClientContext context = HttpClientContext.create();
		String content = doExecute(get, targetHost, null, context);
		System.out.println("Authorization Basic认证header请求响应:"+content);
	}
	
	private CloseableHttpClient getHttpClient(UsernamePasswordCredentials credentials){
	    RequestConfig requestConf = RequestConfig.custom().setAuthenticationEnabled(false).build();
	    HttpClientBuilder httpClientBuilder = HttpClients.custom();
	    if(credentials != null) {
	    	CredentialsProvider provider = new BasicCredentialsProvider();
	    	provider.setCredentials(AuthScope.ANY, credentials);
	    	httpClientBuilder.setDefaultCredentialsProvider(provider);
	    }
	    CloseableHttpClient client = httpClientBuilder.setDefaultRequestConfig(requestConf).build();
	    return  client;
	}
	
	private String doExecute(HttpGet get, HttpHost targetHost, UsernamePasswordCredentials credentials, HttpContext contenxt) throws Exception {
		CloseableHttpResponse response = null;
		String content = "";
        try {
        	
        	//指定Client使用HttpContext,包含属性:http.auth.auth-cache
            response = getHttpClient(credentials).execute(get, contenxt);
            HttpEntity entity = response.getEntity();
            if(entity!=null) {
                Charset charset = ContentType.getOrDefault(entity).getCharset();
                content = EntityUtils.toString(entity, charset);
                EntityUtils.consume(entity);
                return content;
            }
        } catch(Exception ie) {
            ie.printStackTrace();
            throw ie;
        } finally {
            try {
                if(response!=null) {
                    response.close();
                }
            } catch(IOException e) {
            }
            if(get!=null) {
            	get.releaseConnection();
            } 
        }
        return content;
	}
}

基于lucene实时分布式搜索引擎ElasticSearch

ElasticSearch是一个基于Lucene构建的开源,分布式,RESTful搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。支持通过HTTP使用JSON进行数据索引。

下载地址: http://www.elasticsearch.org/download/

 

一、elasticSearch安装方法:

1.设置jdk环境变量,至少jdk1.6.

2.设置环境变量 ES_HOME,配置path后重启
a. ES_HOME  : ElasticSearch安装路径

b. ES_MAX_MEM : 设置 ElasticSearch 的最大使用内存

c. EX_MIN_MEM :  设置ElasticSearch 的最小使用内存

d.  ES_HEAP_SIZE: 环境变量,保证 JVM 使用的最大和最小内存用量相同。

例子linux

export ES_HOME="/home/weblogic/elasticsearch-0.90.0";export PATH=$ES_HOME/bin:$PATH;export ES_MAX_MEM="25g";export ES_MIN_MEM="1024M";

 

3.windows直接运行bin\elasticsearch.bat  ; linux 运行elasticsearch.sh

4.推荐学习资料:http://www.searchtech.pro/

 

一、插件安装方法:

服务器管理插件:  https://github.com/mobz/elasticsearch-head 

1.进入elasticsearchbin目录 cd D:\elasticsearch-0.20.5

2. 执行bin\plugin.bat  如:bin\plugin -install mobz/elasticsearch-head  

3.es服务开启访问http://localhost:9200/_plugin/head/

 

服务器启动运行工具:https://github.com/elasticsearch/elasticsearch-servicewrapper

1.直接解压到bin目录(bin\service)。service目录下有个elasticsearch.conf配置文件,主要是设置一些java运行环境参数

参数:

#eshome路径,不用用默认值就可以

set.default.ES_HOME=<Path to ElasticSearch Home>

#分配给es的最小内存

set.default.ES_MIN_MEM=256

#分配给es的最大内存

set.default.ES_MAX_MEM=1024

启动等待超时时间(以秒为单位)

wrapper.startup.timeout=300

关闭等待超时时间(以秒为单位)

wrapper.shutdown.timeout=300

# ping超时时间(以秒为单位)

wrapper.ping.timeout=300

2.运行命令:

bin/service/elasticsearch +

console 在前台运行es

start 在后台运行es

stop 停止es

install 使es作为服务在服务器启动时自动启动

remove 取消启动时自动启动

 

集群监控工具 https://github.com/lukas-vlcek/bigdesk

bigdeskelasticsearch的一个集群监控工具;

1.插件安装运行:bin\plugin -install lukas-vlcek/bigdesk

2.运行es

3.打开http://localhost:9200/_plugin/bigdesk/

客服端开发工具 Jest

Jest:https://github.com/searchbox-io/Jest

它是ESjava开源客户端,基于http restful方式。

 

ES操作工具 CURL

CURL:http://curl.haxx.se/download.html

curl是利用URL语法在命令行方式下工作的文件传输工具。

创建索引 curl -XPUT ‘http://localhost:9200/music_reviews/’

 

中文分词插件(ik,mmseg) ES默认的分词器为standard,自定义的analyzer配置为:index.analysis.analyzer.default.type:ik  

1.安装:plugin -install medcl/elasticsearch-analysis-ik/1.1.0  

2.配置词典config目录

cd config

wget http://github.com/downloads/medcl/elasticsearch-analysis-ik/ik.zip –no-check-certificate

unzip ik.zip

rm ik.zip

3.ik分词配置elasticsearch.yml文件

index.analysis.analyzer.ik.type : “ik”

或者
index:

analysis:

analyzer:

ik:

alias: [ik_analyzer]

type: org.elasticsearch.index.analysis.IkAnalyzerProvider

网页加载速度之js/css压缩

Minify压缩JS和CSS

Minify把CSS和JS压缩和削减(Minify:去掉空格回车符等),以及把多个CSS,JS文件整合到一个文件里。不要以为你的大带宽没有 必要进行这类优化。使用它的理由更重要的是文件合并,而不是压缩,而是文件整合,这样可以减少浏览器端不断发出新的连接请求,就像FTP服务器一样,多个 小文件和一个大文件耗时是不一样的。

Minify是用PHP写的,项目地址 https://github.com/mrclay/minify

安 装

1. 下载最新的Minify然后解压缩到minify目录。
2. 复制”min”目录到你的DOCUMENT_ROOT.

基本用法

假设你有 http://localhost/a.js,http://localhost/b.js 两个文件。那么现在,你可以使用
http://localhost/min/?f=a.js,b.js,看看浏览器返回结果,是不是minify的两个js文件的内容?

附译min目录下的README.txt
该目录中的文件包含默认Minify设置,旨在简化整合您的网站。Minify将合并削减JavaScript或CSS文件,并进行HTTP压缩和缓存头。

推 荐

建议修改config.php中设置$min_cachePath到一个PHP可写目录。这将提高性能。

GETTING STARTED

最快的开始Minify的方法是使用Minify Builder应用程序的URI
访问您的网站:http://example.com/min/builder/

压缩单个文件

比方说,你要服务于这个文件:
http://example.com/wp-content/themes/default/default.css
下面是“Minify网址”该文件:
http://example.com/min/?f=wp-content/themes/default/default.css

换句话说,“f”参数设置为从WEB根路径下的目标文件(不需要路径/)”。由于CSS文件可能包含相对URI,Minify会自动通过重写机制找到它们。

合并多个文件到一个文件下载

用’,’分隔f参数的每一个文件名。
比如,有如下CSS文件:
http://example.com/scripts/jquery-1.8.js
http://example.com/scripts/site.js
您可以通过Minify结合起来:
http://example.com/min/?f=scripts/jquery-1.8.js,scripts/site.js

简化基本路径

如果你合并的文件共享同一父目录,你可以使用b参数设置的f参数的基本目录(同样不包括前导或者后缀/字符)。
例如,以下两种写法效果相同:
http://example.com/min/?f=scripts/jquery-1.8.js,scripts/site.js,scripts/home.js
http://example.com/min/?b=scripts&f=jquery-1.8.js,site.js,home.js

在Html中使用MINIFY
在(X)HTML文件,不要忘记将&替换为&

指定允许的目录

默认情况下,Minify不会有任何DOCUMENT_ROOT范围内的*.css/*.js文件。如果你希望限制Minify存取某些目录,在config.php中设置
$min_serveOptions [‘minApp’] [‘allowDirs’]数组。例如:限制到/js和/themes/default目录,使用:
Php代码

$min_serveOptions['minApp']['allowDirs'] = array('//js', '//themes/default');

“组”:更快的性能和更好的网址

为了获得最佳性能,编辑groupsConfig.php中的预指定文件组,下面是一个例子配置:
Php代码

    return array(
	'js' => array('//js/Class.js', '//js/email.js')
    );

以上预指定js将结果是合并了如下文件:
http://example.com/js/Class.js
http://example.com/js/email.js
现在,您可以如此简化URL:
http://example.com/min/?g=js

组:指定document_root目录以外的文件

groupsConfig.php阵中,//是指向DOCUMENT_ROOT,但你也可以指定从系统的绝对目录路径或相对于document_root的相对目录:
Php代码

return array(
	'js' => array(
	'//js/file.js' // file within DOC_ROOT
	,'//../file.js' // file in parent directory of DOC_ROOT
	,'C:/Users/Steve/file.js' // file anywhere on filesystem
	)
);

 

未来过期HTTP头

Minify可以发送未来(一年)过期HTTP头。要启用该功能,您必须添加一个数字到URIs(例如/min/?g=js&1234 or /min/f=file.js&1234),每当源文件修改时改变该数字。如果你使用SVN/CVS,你可以考虑使用修订版号作为该数字。

如果使用”组”来合并压缩你的文件,可以使用工具函数Minify_groupUri()来得到一个“版本”的URI。例如:
Php代码

 // 之前确保min/lib目录设置到include_path
 // add /min/lib to your include_path first!
 require $_SERVER['DOCUMENT_ROOT'] . '/min/utils.php';
 $jsUri = Minify_groupUri('js');
 echo ""; ";

 

调试模式

在调试模式下,Minify不压缩文件,而是发送合并后的带有行号的文件。要启用该模式,在config.php中设置为$min_allowDebugFlag为true,并增加”&debug=1″ 到你的URIs.
例如:/min/?f=script1.js,script2.js&debug=1

注:对于该模式,注释风格的字符串正则表达式可能会导致问题。
更多问题请访问https://github.com/mrclay/minify

Javascript 常见陷阱及特殊用法

对象使用和属性

JavaScript 中所有变量都是对象,除了两个例外 null 和 undefined。

false.toString() // 'false'
[1, 2, 3].toString(); // '1,2,3'

function Foo(){}
Foo.bar = 1;
Foo.bar; // 1

一个常见的误解是数字的字面值(literal)不是对象。这是因为 JavaScript 解析器的一个错误, 它试图将点操作符解析为浮点数字面值的一部分。

2.toString(); // 出错:SyntaxError

有很多变通方法可以让数字的字面值看起来像对象。

  ..toString(); // 第二个点号可以正常解析
	2 .toString(); // 注意点号前面的空格
	(2).toString(); // 2 先被计算

 

对象作为数据类型

对象作为数据类型

JavaScript 的对象可以作为哈希表使用,主要用来保存命名的键与值的对应关系。

使用对象的字面语法 – {} – 可以创建一个简单对象。这个新创建的对象从 Object.prototype 继承下面,没有任何自定义属性。

var foo = {}; // 一个空对象

// 一个新对象,拥有一个值为 12 的自定义属性'test'
var bar = {test: 12};

 

访问对象属性

访问对象属性

有两种方式来访问对象的属性,点操作符或者中括号操作符。

var foo = {name: 'Kitten'}
foo.name; // kitten
foo['name']; // kitten

var get = 'name';
foo[get]; // kitten

foo.1234; // SyntaxError
foo['1234']; // works

 

两种语法是等价的,但是中括号操作符在下面两种情况下依然有效 – 动态设置属性 – 属性名不是一个有效的变量名(译者注:比如属性名中包含空格,或者属性名是 JS 的关键词)

删除对象属性

删除对象属性

删除属性的唯一方法是使用 delete 操作符;设置属性为 undefined 或者 null 并不能真正的删除属性, 而仅仅是移除了属性和值的关联。

01 var obj = {
02 bar: 1,
03 foo: 2,
04 baz: 3
05 };
06 obj.bar = undefined;
07 obj.foo = null;
08 delete obj.baz;
09
10 for(var i in obj) {
11 if (obj.hasOwnProperty(i)) {
12 console.log(i, '' + obj[i]);
13 }
14 }

 

上面的输出结果有 bar undefined 和 foo null – 只有 baz 被真正的删除了,所以从输出结果中消失。

对象属性名的语法

对象属性名的语法

1 var test = {
2 'case': 'I am a keyword so I must be notated as a string',
3 delete: 'I am a keyword too so me' // 出错:SyntaxError
4 };

对象的属性名可以使用字符串或者普通字符声明。但是由于 JavaScript 解析器的另一个错误设计, 上面的第二种声明方式在 ECMAScript 5 之前会抛出 SyntaxError 的错误。

这个错误的原因是 delete 是 JavaScript 语言的一个关键词;因此为了在更低版本的 JavaScript 引擎下也能正常运行, 必须使用字符串字面值声明方式。

原型

原型

JavaScript 不包含传统的类继承模型,而是使用prototypal原型模型。

虽然这经常被当作是 JavaScript 的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大。 实现传统的类继承模型是很简单,但是实现 JavaScript 中的原型继承则要困难的多。 (It is for example fairly trivial to build a classic model on top of it, while the other way around is a far more difficult task.)

由于 JavaScript 是唯一一个被广泛使用的基于原型继承的语言,所以理解两种继承模式的差异是需要一定时间的。

第一个不同之处在于 JavaScript 使用原型链的继承方式。

注意:简单的使用 Bar.prototype = Foo.prototype 将会导致两个对象共享相同的原型。 因此,改变任意一个对象的原型都会影响到另一个对象的原型,在大多数情况下这不是希望的结果。

01 function Foo() {
02 this.value = 42;
03 }
04 Foo.prototype = {
05 method: function() {}
06 };
07
08 function Bar() {}
09
10 // 设置Bar的prototype属性为Foo的实例对象
11 Bar.prototype = new Foo();
12 Bar.prototype.foo = 'Hello World';
13
14 // 修正Bar.prototype.constructor为Bar本身
15 Bar.prototype.constructor = Bar;
16
17 var test = new Bar() // 创建Bar的一个新实例
18
19 // 原型链
20 test [Bar的实例]
21 Bar.prototype [Foo的实例]
22 { foo: 'Hello World' }
23 Foo.prototype
24 {method: ...};
25 Object.prototype
26 {toString: ... /* etc. */};

 

上面的例子中,test 对象从 Bar.prototype 和 Foo.prototype 继承下来;因此, 它能访问 Foo 的原型方法 method。同时,它也能够访问那个定义在原型上的 Foo 实例属性 value。 需要注意的是 new Bar() 不会创造出一个新的 Foo 实例,而是 重复使用它原型上的那个实例;因此,所有的 Bar 实例都会共享相同的 value 属性。

注意:不要使用 Bar.prototype = Foo,因为这不会执行 Foo 的原型,而是指向函数 Foo。 因此原型链将会回溯到 Function.prototype 而不是 Foo.prototype,因此 method 将不会在 Bar 的原型链上。

属性查找

属性查找

当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止。

到查找到达原型链的顶部 – 也就是 Object.prototype – 但是仍然没有找到指定的属性,就会返回 undefined。

原型属性

当原型属性用来创建原型链时,可以把任何类型的值赋给它(prototype)。 然而将原子类型赋给 prototype 的操作将会被忽略。

1 function Foo() {}
2 Foo.prototype = 1; // 无效

而将对象赋值给 prototype,正如上面的例子所示,将会动态的创建原型链。

性能

如果一个属性在原型链的上端,则对于查找时间将带来不利影响。特别的,试图获取一个不存在的属性将会遍历整个原型链。

并且,当使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问。

扩展内置类型的原型

一个错误特性被经常使用,那就是扩展 Object.prototype 或者其他内置类型的原型对象。

这种技术被称之为 monkey patching 并且会破坏封装。虽然它被广泛的应用到一些 JavaScript 类库中比如 Prototype, 但是我仍然不认为为内置类型添加一些非标准的函数是个好主意。

扩展内置类型的唯一理由是为了和新的 JavaScript 保持一致,比如 Array.forEach。

译者注:这是编程领域常用的一种方式,称之为 Backport,也就是将新的补丁添加到老版本中。

hasOwnProperty函数

hasOwnProperty 函数

为了判断一个对象是否包含自定义属性而不是原型链上的属性, 我们需要使用继承自 Object.prototype 的 hasOwnProperty 方法。

注意:通过判断一个属性是否 undefined 是不够的。 因为一个属性可能确实存在,只不过它的值被设置为 undefined。

hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。

1 // 修改Object.prototype
2 Object.prototype.bar = 1;
3 var foo = {goo: undefined};
4
5 foo.bar; // 1
6 'bar' in foo; // true
7
8 foo.hasOwnProperty('bar'); // false
9 foo.hasOwnProperty('goo'); // true

 

只有 hasOwnProperty 可以给出正确和期望的结果,这在遍历对象的属性时会很有用。没有其它方法可以用来排除原型链上的属性,而不是定义在对象自身上的属性。

hasOwnProperty 作为属性

JavaScript不会保护 hasOwnProperty 被非法占用,因此如果一个对象碰巧存在这个属性, 就需要使用外部的 hasOwnProperty 函数来获取正确的结果。

01 var foo = {
02 hasOwnProperty: function() {
03 return false;
04 },
05 bar: 'Here be dragons'
06 };
07
08 foo.hasOwnProperty('bar'); // 总是返回 false
09
10 // 使用其它对象的 hasOwnProperty,并将其上下为设置为foo
11 {}.hasOwnProperty.call(foo, 'bar'); // true

 

结论

当检查对象上某个属性是否存在时,hasOwnProperty 是唯一可用的方法。 同时在使用 for in loop 遍历对象时,推荐总是使用 hasOwnProperty 方法, 这将会避免原型对象扩展带来的干扰。

forin循环

for in 循环

和 in 操作符一样,for in 循环同样在查找对象属性时遍历原型链上的所有属性。

注意:for in 循环不会遍历那些 enumerable 设置为 false 的属性;比如数组的 length 属性。

1 // 修改 Object.prototype
2 Object.prototype.bar = 1;
3
4 var foo = {moo: 2};
5 for(var i in foo) {
6 console.log(i); // 输出两个属性:bar 和 moo
7 }

 

由于不可能改变 for in 自身的行为,因此有必要过滤出那些不希望出现在循环体中的属性, 这可以通过 Object.prototype 原型上的 hasOwnProperty 函数来完成。

注意:由于 for in 总是要遍历整个原型链,因此如果一个对象的继承层次太深的话会影响性能。

使用 hasOwnProperty 过滤

1 // foo 变量是上例中的
2 for(var i in foo) {
3 if (foo.hasOwnProperty(i)) {
4 console.log(i);
5 }
6 }

这个版本的代码是唯一正确的写法。由于我们使用了 hasOwnProperty,所以这次只输出 moo。 如果不使用 hasOwnProperty,则这段代码在原生对象原型(比如 Object.prototype)被扩展时可能会出错。

一个广泛使用的类库 Prototype 就扩展了原生的 JavaScript 对象。 因此,但这个类库被包含在页面中时,不使用 hasOwnProperty 过滤的 for in 循环难免会出问题。

总结

推荐总是使用 hasOwnProperty。不要对代码运行的环境做任何假设,不要假设原生对象是否已经被扩展了。

函数声明与表达式

函数声明与表达式

函数是JavaScript中的一等对象,这意味着可以把函数像其它值一样传递。 一个常见的用法是把匿名函数作为回调函数传递对异步函数中。

函数声明

01 function foo() {}
02 //上面的方法会在执行前被 解析(hoisted),因此它存在于当前上下文的任意一个地方, 即使在函数定义体//的上面被调用也是对的。
03
04 foo(); // 正常运行,因为foo在代码运行前已经被创建
05 function foo() {}//函数赋值表达式
06
07 var foo = function() {};//这个例子把一个匿名的函数赋值给变量 foo。
08
09 foo; // 'undefined'
10 foo(); // 出错:TypeError
11 var foo = function() {};

 

由于 var 定义了一个声明语句,对变量 foo 的解析是在代码运行之前,因此 foo 变量在代码运行时已经被定义过了。

但是由于赋值语句只在运行时执行,因此在相应代码执行之前, foo 的值缺省为 undefined。

命名函数的赋值表达式

另外一个特殊的情况是将命名函数赋值给一个变量。

1 var foo = function bar() {
2 bar(); // 正常运行
3 }
4 bar(); // 出错:ReferenceError

bar 函数声明外是不可见的,这是因为我们已经把函数赋值给了 foo; 然而在 bar 内部依然可见。这是由于 JavaScript 的 命名处理 所致, 函数名在函数内总是可见的。

this的工作原理

this 的工作原理

JavaScript 有一套完全不同于其它语言的对 this 的处理机制。 在五种不同的情况下 ,this 指向的各不相同。

全局范围内

this;
当在全部范围内使用 this,它将会指向全局对象。

译者注:浏览器中运行的 JavaScript 脚本,这个全局对象是 window。

函数调用

foo();
这里 this 也会指向全局对象。

ES5 注意:在严格模式下(strict mode),不存在全局变量。 这种情况下 this 将会是 undefined。

方法调用

test.foo();
这个例子中,this 指向 test 对象。

调用构造函数

new foo();
如果函数倾向于和 new 关键词一块使用,则我们称这个函数是 构造函数。 在函数内部,this 指向新创建的对象。

显式的设置 this

1 function foo(a, b, c) {}
2
3 var bar = {};
4 foo.apply(bar, [1, 2, 3]); // 数组将会被扩展,如下所示
5 foo.call(bar, 1, 2, 3); // 传递到foo的参数是:a = 1, b = 2, c = 3

 

当使用 Function.prototype 上的 call 或者 apply 方法时,函数内的 this 将会被显式设置为函数调用的第一个参数。

因此函数调用的规则在上例中已经不适用了,在foo 函数内 this 被设置成了 bar。

注意:在对象的字面声明语法中,this 不能用来指向对象本身。 因此 var obj = {me: this} 中的 me 不会指向 obj,因为 this 只可能出现在上述的五种情况中。译者注:这个例子中,如果是在浏览器中运行,obj.me 等于 window 对象。

常见误解

尽管大部分的情况都说的过去,不过第一个规则(译者注:这里指的应该是第二个规则,也就是直接调用函数时,this 指向全局对象) 被认为是JavaScript语言另一个错误设计的地方,因为它从来就没有实际的用途。

1 Foo.method = function() {
2 function test() {
3 // this 将会被设置为全局对象(译者注:浏览器环境中也就是 window 对象)
4 }
5 test();
6 }

一个常见的误解是 test 中的 this 将会指向 Foo 对象,实际上不是这样子的。

为了在 test 中获取对 Foo 对象的引用,我们需要在 method 函数内部创建一个局部变量指向 Foo 对象。

1 Foo.method = function() {
2 var that = this;
3 function test() {
4 // 使用 that 来指向 Foo 对象
5 }
6 test();
7 }

that 只是我们随意起的名字,不过这个名字被广泛的用来指向外部的 this 对象。 在 闭包 一节,我们可以看到 that 可以作为参数传递。

方法的赋值表达式

另一个看起来奇怪的地方是函数别名,也就是将一个方法赋值给一个变量。

1 var test = someObject.methodTest;
2 test();

上例中,test 就像一个普通的函数被调用;因此,函数内的 this 将不再被指向到 someObject 对象。

虽然 this 的晚绑定特性似乎并不友好,但是这确实基于原型继承赖以生存的土壤。

1 function Foo() {}
2 Foo.prototype.method = function() {};
3
4 function Bar() {}
5 Bar.prototype = Foo.prototype;
6
7 new Bar().method();

 

当 method 被调用时,this 将会指向 Bar 的实例对象。

闭包和引用

闭包和引用

闭包是 JavaScript 一个非常重要的特性,这意味着当前作用域总是能够访问外部作用域中的变量。 因为 函数 是 JavaScript 中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数。

模拟私有变量

01 function Counter(start) {
02 var count = start;
03 return {
04 increment: function() {
05 count++;
06 },
07
08 get: function() {
09 return count;
10 }
11 }
12 }
13
14 var foo = Counter(4);
15 foo.increment();
16 foo.get(); // 5

 

这里,Counter 函数返回两个闭包,函数 increment 和函数 get。 这两个函数都维持着 对外部作用域 Counter 的引用,因此总可以访问此作用域内定义的变量 count.

为什么不可以在外部访问私有变量

因为 JavaScript 中不可以对作用域进行引用或赋值,因此没有办法在外部访问 count 变量。 唯一的途径就是通过那两个闭包。

1 var foo = new Counter(4);
2 foo.hack = function() {
3 count = 1337;
4 };

上面的代码不会改变定义在 Counter 作用域中的 count 变量的值,因为 foo.hack 没有 定义在那个作用域内。它将会创建或者覆盖全局变量 count。

循环中的闭包

一个常见的错误出现在循环中使用闭包,假设我们需要在每次循环中调用循环序号

1 for(var i = 0; i < 10; i++) {
2 setTimeout(function() {
3 console.log(i);
4 }, 1000);
5 }

上面的代码不会输出数字 0 到 9,而是会输出数字 10 十次。

当 console.log 被调用的时候,匿名函数保持对外部变量 i 的引用,此时 for循环已经结束, i 的值被修改成了 10.

为了得到想要的结果,需要在每次循环中创建变量 i 的拷贝。

避免引用错误

为了正确的获得循环序号,最好使用 匿名包裹器(译者注:其实就是我们通常说的自执行匿名函数)。

1 for(var i = 0; i < 10; i++) {
2 (function(e) {
3 setTimeout(function() {
4 console.log(e);
5 }, 1000);
6 })(i);
7 }

外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。

当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。

有另一个方法完成同样的工作;那就是从匿名包装器中返回一个函数。这和上面的代码效果一样。

1 for(var i = 0; i < 10; i++) {
2 setTimeout((function(e) {
3 return function() {
4 console.log(e);
5 }
6 })(i), 1000)
7 }

 

arguments对象

arguments 对象

JavaScript 中每个函数内都能访问一个特别变量 arguments。这个变量维护着所有传递到这个函数中的参数列表。

注意:由于 arguments 已经被定义为函数内的一个变量。 因此通过 var 关键字定义 arguments 或者将 arguments 声明为一个形式参数, 都将导致原生的 arguments 不会被创建。

arguments 变量不是一个数组(Array)。 尽管在语法上它有数组相关的属性 length,但它不从 Array.prototype 继承,实际上它是一个对象(Object)。

因此,无法对 arguments 变量使用标准的数组方法,比如 push, pop 或者 slice。 虽然使用 for 循环遍历也是可以的,但是为了更好的使用数组方法,最好把它转化为一个真正的数组。

转化为数组

下面的代码将会创建一个新的数组,包含所有 arguments 对象中的元素。

Array.prototype.slice.call(arguments);
这个转化比较慢,在性能不好的代码中不推荐这种做法。

传递参数

下面将参数从一个函数传递到另一个函数,是推荐的做法。

1 function foo() {
2 bar.apply(null, arguments);
3 }
4 function bar(a, b, c) {
5 // do stuff here
6 }

另一个技巧是同时使用 call 和 apply,创建一个快速的解绑定包装器。

01 function Foo() {}
02
03 Foo.prototype.method = function(a, b, c) {
04 console.log(this, a, b, c);
05 };
06
07 // Create an unbound version of "method"
08 // 输入参数为: this, arg1, arg2...argN
09 Foo.method = function() {
10
11 // 结果: Foo.prototype.method.call(this, arg1, arg2... argN)
12 Function.call.apply(Foo.prototype.method, arguments);
13 };

 

译者注:上面的 Foo.method 函数和下面代码的效果是一样的:

1 Foo.method = function() {
2 var args = Array.prototype.slice.call(arguments);
3 Foo.prototype.method.apply(args[0], args.slice(1));
4 };

 

自动更新

arguments 对象为其内部属性以及函数形式参数创建getter和setter方法。

因此,改变形参的值会影响到 arguments 对象的值,反之亦然。

01 function foo(a, b, c) {
02 arguments[0] = 2;
03 a; // 2
04
05 b = 4;
06 arguments[1]; // 4
07
08 var d = c;
09 d = 9;
10 c; // 3
11 }
12 foo(1, 2, 3);

 

性能真相

arguments 对象总会被创建,除了两个特殊情况 – 作为局部变量声明和作为形式参数。 而不管它是否有被使用。

arguments 的getters和setters方法总会被创佳;因此使用 arguments 对性能不会有什么影响。 除非是需要对 arguments 对象的属性进行多次访问。

ES5 提示:这些getters和setters在严格模式下(strict mode)不会被创建。

译者注:在 MDC 中对 strict mode 模式下 arguments 的描述有助于我们的理解,请看下面代码:

1 // 阐述在 ES5 的严格模式下 `arguments` 的特性
2 function f(a) {
3 "use strict";
4 a = 42;
5 return [a, arguments[0]];
6 }
7 var pair = f(17);
8 assert(pair[0] === 42);
9 assert(pair[1] === 17);

然而,的确有一种情况会显著的影响现代 JavaScript 引擎的性能。这就是使用 arguments.callee。

01 function foo() {
02 arguments.callee; // do something with this function object
03 arguments.callee.caller; // and the calling function object
04 }
05
06 function bigLoop() {
07 for(var i = 0; i < 100000; i++) {
08 foo(); // Would normally be inlined...
09 }
10 }

 

上面代码中,foo 不再是一个单纯的内联函数 inlining(译者注:这里指的是解析器可以做内联处理), 因为它需要知道它自己和它的调用者。 这不仅抵消了内联函数带来的性能提升,而且破坏了封装,因此现在函数可能要依赖于特定的上下文。

因此强烈建议大家不要使用 arguments.callee 和它的属性。

ES5 提示:在严格模式下,arguments.callee 会报错 TypeError,因为它已经被废除了。

构造函数

构造函数

JavaScript 中的构造函数和其它语言中的构造函数是不同的。 通过 new 关键字方式调用的函数都被认为是构造函数。

在构造函数内部 – 也就是被调用的函数内 – this 指向新创建的对象 Object。 这个新创建的对象的 prototype 被指向到构造函数的 prototype。

如果被调用的函数没有显式的 return 表达式,则隐式的会返回 this 对象 – 也就是新创建的对象。

1 function Foo() {
2 this.bla = 1;
3 }
4
5 Foo.prototype.test = function() {
6 console.log(this.bla);
7 };
8
9 var test = new Foo();

 

上面代码把 Foo 作为构造函数调用,并设置新创建对象的 prototype 为 Foo.prototype。

显式的 return 表达式将会影响返回结果,但仅限于返回的是一个对象。

01 function Bar() {
02 return 2;
03 }
04 new Bar(); // 返回新创建的对象
05
06 function Test() {
07 this.value = 2;
08
09 return {
10 foo: 1
11 };
12 }
13 new Test(); // 返回的对象

 

译者注:new Bar() 返回的是新创建的对象,而不是数字的字面值 2。 因此 new Bar().constructor === Bar,但是如果返回的是数字对象,结果就不同了,如下所示

1 function Bar() {
2 return new Number(2);
3 }
4 new Bar().constructor === Number

译者注:这里得到的 new Test()是函数返回的对象,而不是通过new关键字新创建的对象,因此:

1 (new Test()).value === undefined
2 (new Test()).foo === 1
3 如果 new 被遗漏了,则函数不会返回新创建的对象。
4
5 function Foo() {
6 this.bla = 1; // 获取设置全局参数
7 }
8 Foo(); // undefined

 

虽然上例在有些情况下也能正常运行,但是由于 JavaScript 中 this 的工作原理, 这里的 this 指向全局对象。

工厂模式

为了不使用 new 关键字,构造函数必须显式的返回一个值。

01 function Bar() {
02 var value = 1;
03 return {
04 method: function() {
05 return value;
06 }
07 }
08 }
09 Bar.prototype = {
10 foo: function() {}
11 };
12
13 new Bar();
14 Bar();

 

上面两种对 Bar 函数的调用返回的值完全相同,一个新创建的拥有 method 属性的对象被返回, 其实这里创建了一个闭包。

还需要注意, new Bar() 并不会改变返回对象的原型(译者注:也就是返回对象的原型不会指向 Bar.prototype)。 因为构造函数的原型会被指向到刚刚创建的新对象,而这里的 Bar 没有把这个新对象返回(译者注:而是返回了一个包含 method 属性的自定义对象)。

在上面的例子中,使用或者不使用 new 关键字没有功能性的区别。

译者注:上面两种方式创建的对象不能访问 Bar 原型链上的属性,如下所示:

1 var bar1 = new Bar();
2 typeof(bar1.method); // "function"
3 typeof(bar1.foo); // "undefined"
4
5 var bar2 = Bar();
6 typeof(bar2.method); // "function"
7 typeof(bar2.foo); // "undefined"

 

通过工厂模式创建新对象

我们常听到的一条忠告是不要使用 new 关键字来调用函数,因为如果忘记使用它就会导致错误。

为了创建新对象,我们可以创建一个工厂方法,并且在方法内构造一个新对象。

01 function Foo() {
02 var obj = {};
03 obj.value = 'blub';
04
05 var private = 2;
06 obj.someMethod = function(value) {
07 this.value = value;
08 }
09
10 obj.getPrivate = function() {
11 return private;
12 }
13 return obj;
14 }

 

虽然上面的方式比起 new 的调用方式不容易出错,并且可以充分利用私有变量带来的便利, 但是随之而来的是一些不好的地方。

会占用更多的内存,因为新创建的对象不能共享原型上的方法。
为了实现继承,工厂方法需要从另外一个对象拷贝所有属性,或者把一个对象作为新创建对象的原型。
放弃原型链仅仅是因为防止遗漏 new 带来的问题,这似乎和语言本身的思想相违背。
总结

虽然遗漏 new 关键字可能会导致问题,但这并不是放弃使用原型链的借口。 最终使用哪种方式取决于应用程序的需求,选择一种代码书写风格并坚持下去才是最重要的。

作用域与命名空间

作用域与命名空间

尽管 JavaScript 支持一对花括号创建的代码段,但是并不支持块级作用域; 而仅仅支持函数作用域。

1 function test() { // 一个作用域
2 for(var i = 0; i < 10; i++) { // 不是一个作用域
3 // count
4 }
5 console.log(i); // 10
6 }

注意:如果不是在赋值语句中,而是在 return 表达式或者函数参数中,{…} 将会作为代码段解析, 而不是作为对象的字面语法解析。如果考虑到 自动分号插入,这可能会导致一些不易察觉的错误。

译者注:如果 return 对象的左括号和 return 不在一行上就会出错。

1 // 译者注:下面输出 undefined
2 function add(a, b) {
3 return
4 a + b;
5 }
6 console.log(add(1, 2));

JavaScript 中没有显式的命名空间定义,这就意味着所有对象都定义在一个全局共享的命名空间下面。

每次引用一个变量,JavaScript 会向上遍历整个作用域直到找到这个变量为止。 如果到达全局作用域但是这个变量仍未找到,则会抛出 ReferenceError 异常。

隐式的全局变量

1 // 脚本 A
2 foo = '42';
3
4 // 脚本 B
5 var foo = '42'

 

上面两段脚本效果不同。脚本 A 在全局作用域内定义了变量 foo,而脚本 B 在当前作用域内定义变量 foo。

再次强调,上面的效果完全不同,不使用 var 声明变量将会导致隐式的全局变量产生。

1 // 全局作用域
2 var foo = 42;
3 function test() {
4 // 局部作用域
5 foo = 21;
6 }
7 test();
8 foo; // 21

在函数 test 内不使用 var 关键字声明 foo 变量将会覆盖外部的同名变量。 起初这看起来并不是大问题,但是当有成千上万行代码时,不使用 var 声明变量将会带来难以跟踪的 BUG。

01 // 全局作用域
02 var items = [/* 数组 */];
03 for(var i = 0; i < 10; i++) {
04 subLoop();
05 }
06
07 function subLoop() {
08 // subLoop 函数作用域
09 for(i = 0; i < 10; i++) { // 没有使用 var 声明变量
10 // 干活
11 }
12 }

 

外部循环在第一次调用 subLoop 之后就会终止,因为 subLoop 覆盖了全局变量 i。 在第二个 for 循环中使用 var 声明变量可以避免这种错误。 声明变量时绝对不要遗漏 var 关键字,除非这就是期望的影响外部作用域的行为。

局部变量

JavaScript 中局部变量只可能通过两种方式声明,一个是作为函数参数,另一个是通过 var 关键字声明。

01 // 全局变量
02 var foo = 1;
03 var bar = 2;
04 var i = 2;
05
06 function test(i) {
07 // 函数 test 内的局部作用域
08 i = 5;
09
10 var foo = 3;
11 bar = 4;
12 }
13 test(10);

 

foo 和 i 是函数 test 内的局部变量,而对 bar 的赋值将会覆盖全局作用域内的同名变量。

变量声明提升(Hoisting)

JavaScript 会提升变量声明。这意味着 var 表达式和 function 声明都将会被提升到当前作用域的顶部。

01 bar();
02 var bar = function() {};
03 var someValue = 42;
04
05 test();
06 function test(data) {
07 if (false) {
08 goo = 1;
09
10 } else {
11 var goo = 2;
12 }
13 for(var i = 0; i < 100; i++) {
14 var e = data[i];
15 }
16 }

 

上面代码在运行之前将会被转化。JavaScript 将会把 var 表达式和 function 声明提升到当前作用域的顶部。

01 // var 表达式被移动到这里
02 var bar, someValue; // 缺省值是 'undefined'
03
04 // 函数声明也会提升
05 function test(data) {
06 var goo, i, e; // 没有块级作用域,这些变量被移动到函数顶部
07 if (false) {
08 goo = 1;
09
10 } else {
11 goo = 2;
12 }
13 for(i = 0; i < 100; i++) {
14 e = data[i];
15 }
16 }
17
18 bar(); // 出错:TypeError,因为 bar 依然是 'undefined'
19 someValue = 42; // 赋值语句不会被提升规则(hoisting)影响
20 bar = function() {};
21
22 test();

 

没有块级作用域不仅导致 var 表达式被从循环内移到外部,而且使一些 if 表达式更难看懂。

在原来代码中,if 表达式看起来修改了全部变量goo,实际上在提升规则被应用后,却是在修改局部变量。

如果没有提升规则(hoisting)的知识,下面的代码看起来会抛出异常 ReferenceError。

1 // 检查 SomeImportantThing 是否已经被初始化
2 if (!SomeImportantThing) {
3 var SomeImportantThing = {};
4 }

实际上,上面的代码正常运行,因为 var 表达式会被提升到全局作用域的顶部。

1 var SomeImportantThing;
2
3 // 其它一些代码,可能会初始化 SomeImportantThing,也可能不会
4
5 // 检查是否已经被初始化
6 if (!SomeImportantThing) {
7 SomeImportantThing = {};
8 }

 

译者注:在 Nettuts+ 网站有一篇介绍 hoisting 的文章,其中的代码很有启发性。

1 // 译者注:来自 Nettuts+ 的一段代码,生动的阐述了 JavaScript 中变量声明提升规则
2 var myvar = 'my value';
3
4 (function() {
5 alert(myvar); // undefined
6 var myvar = 'local value';
7 })();

 

名称解析顺序

JavaScript 中的所有作用域,包括全局作用域,都有一个特别的名称 this 指向当前对象。

函数作用域内也有默认的变量 arguments,其中包含了传递到函数中的参数。

比如,当访问函数内的 foo 变量时,JavaScript 会按照下面顺序查找:

当前作用域内是否有 var foo 的定义。
函数形式参数是否有使用 foo 名称的。
函数自身是否叫做 foo。
回溯到上一级作用域,然后从#1重新开始。
注意:自定义 arguments 参数将会阻止原生的 arguments 对象的创建。

命名空间

只有一个全局作用域导致的常见错误是命名冲突。在 JavaScript中,这可以通过匿名包装器轻松解决。

1 (function() {
2 // 函数创建一个命名空间
3
4 window.foo = function() {
5 // 对外公开的函数,创建了闭包
6 };
7
8 })(); // 立即执行此匿名函数

 

匿名函数被认为是 表达式;因此为了可调用性,它们首先会被执行。

1 ( // 小括号内的函数首先被执行
2 function() {}
3 ) // 并且返回函数对象
4 () // 调用上面的执行结果,也就是函数对象
5 有一些其他的调用函数表达式的方法,比如下面的两种方式语法不同,但是效果一模一样。
6
7 // 另外两种方式
8 +function(){}();
9 (function(){}());

 

结论

推荐使用匿名包装器(译者注:也就是自执行的匿名函数)来创建命名空间。这样不仅可以防止命名冲突, 而且有利于程序的模块化。

另外,使用全局变量被认为是不好的习惯。这样的代码倾向于产生错误和带来高的维护成本。

数组遍历与属性

数组遍历与属性

虽然在 JavaScript 中数组是是对象,但是没有好的理由去使用 for in 循环 遍历数组。 相反,有一些好的理由不去使用 for in 遍历数组。

注意:JavaScript 中数组不是关联数组。 JavaScript 中只有对象 来管理键值的对应关系。但是关联数组是保持顺序的,而对象不是。

由于 for in 循环会枚举原型链上的所有属性,唯一过滤这些属性的方式是使用 hasOwnProperty 函数, 因此会比普通的 for 循环慢上好多倍。

遍历

为了达到遍历数组的最佳性能,推荐使用经典的 for 循环。

1 var list = [1, 2, 3, 4, 5, ...... 100000000];
2 for(var i = 0, l = list.length; i < l; i++) {
3 console.log(list[i]);
4 }

上面代码有一个处理,就是通过 l = list.length 来缓存数组的长度。

虽然 length 是数组的一个属性,但是在每次循环中访问它还是有性能开销。可能最新的 JavaScript 引擎在这点上做了优化,但是我们没法保证自己的代码是否运行在这些最近的引擎之上。

实际上,不使用缓存数组长度的方式比缓存版本要慢很多。

length 属性

length 属性的getter方式会简单的返回数组的长度,而setter方式会截断数组。

1 var foo = [1, 2, 3, 4, 5, 6];
2 foo.length = 3;
3 foo; // [1, 2, 3]
4
5 foo.length = 6;
6 foo; // [1, 2, 3]

 

译者注:在 Firebug 中查看此时 foo 的值是: [1, 2, 3, undefined, undefined, undefined] 但是这个结果并不准确,如果你在 Chrome 的控制台查看 foo 的结果,你会发现是这样的: [1, 2, 3] 因为在 JavaScript 中 undefined 是一个变量,注意是变量不是关键字,因此上面两个结果的意义是完全不相同的。

// 译者注:为了验证,我们来执行下面代码,看序号 5 是否存在于 foo 中。
5 in foo; // 不管在 Firebug 或者 Chrome 都返回 false
foo[5] = undefined;
5 in foo; // 不管在 Firebug 或者 Chrome 都返回 true
为 length 设置一个更小的值会截断数组,但是增大 length 属性值不会对数组产生影响。

结论

为了更好的性能,推荐使用普通的 for 循环并缓存数组的 length 属性。 使用 for in 遍历数组被认为是不好的代码习惯并倾向于产生错误和导致性能问题。

Array 构造函数

由于 Array 的构造函数在如何处理参数时有点模棱两可,因此总是推荐使用数组的字面语法 – [] – 来创建数组。

01 [1, 2, 3]; // 结果: [1, 2, 3]
02 new Array(1, 2, 3); // 结果: [1, 2, 3]
03
04 [3]; // 结果: [3]
05 new Array(3); // 结果: []
06 new Array('3') // 结果: ['3']
07
08 // 译者注:因此下面的代码将会使人很迷惑
09 new Array(3, 4, 5); // 结果: [3, 4, 5]
10 new Array(3) // 结果: [],此数组长度为 3

 

译者注:这里的模棱两可指的是数组的两种构造函数语法

由于只有一个参数传递到构造函数中(译者注:指的是 new Array(3); 这种调用方式),并且这个参数是数字,构造函数会返回一个 length 属性被设置为此参数的空数组。 需要特别注意的是,此时只有 length 属性被设置,真正的数组并没有生成。

译者注:在 Firebug 中,你会看到 [undefined, undefined, undefined],这其实是不对的。在上一节有详细的分析。

1 var arr = new Array(3);
2 arr[1]; // undefined
3 1 in arr; // false, 数组还没有生成

这种优先于设置数组长度属性的做法只在少数几种情况下有用,比如需要循环字符串,可以避免 for 循环的麻烦。

1 new Array(count + 1).join(stringToRepeat);

译者注:new Array(3).join(‘#’) 将会返回 ##

结论

应该尽量避免使用数组构造函数创建新数组。推荐使用数组的字面语法。它们更加短小和简洁,因此增加了代码的可读性。

相等与比较

相等与比较

JavaScript 有两种方式判断两个值是否相等。

等于操作符

等于操作符由两个等号组成:==

JavaScript 是弱类型语言,这就意味着,等于操作符会为了比较两个值而进行强制类型转换。

1 "" == "0" // false
2 0 == "" // true
3 0 == "0" // true
4 false == "false" // false
5 false == "0" // true
6 false == undefined // false
7 false == null // false
8 null == undefined // true
9 " \t\r\n" == 0 // true

上面的表格展示了强类型转换,这也是使用 == 被广泛认为是不好编程习惯的主要原因, 由于它的复杂转换规则,会导致难以跟踪的问题。

此外,强制类型转换也会带来性能消耗,比如一个字符串为了和一个数组进行比较,必须事先被强制转换为数字。

严格等于操作符

严格等于操作符由三个等号组成:===

不想普通的等于操作符,严格等于操作符不会进行强制类型转换。

1 "" === "0" // false
2 0 === "" // false
3 0 === "0" // false
4 false === "false" // false
5 false === "0" // false
6 false === undefined // false
7 false === null // false
8 null === undefined // false
9 " \t\r\n" === 0 // false

上面的结果更加清晰并有利于代码的分析。如果两个操作数类型不同就肯定不相等也有助于性能的提升。

比较对象

虽然 == 和 === 操作符都是等于操作符,但是当其中有一个操作数为对象时,行为就不同了。

1 {} === {}; // false
2 new String('foo') === 'foo'; // false
3 new Number(10) === 10; // false
4 var foo = {};
5 foo === foo; // true

这里等于操作符比较的不是值是否相等,而是是否属于同一个身份;也就是说,只有对象的同一个实例才被认为是相等的。 这有点像 Python 中的 is 和 C 中的指针比较。

结论

强烈推荐使用严格等于操作符。如果类型需要转换,应该在比较之前显式的转换, 而不是使用语言本身复杂的强制转换规则。

typeof操作符

typeof 操作符

typeof 操作符(和 instanceof 一起)或许是 JavaScript 中最大的设计缺陷, 因为几乎不可能从它们那里得到想要的结果。

尽管 instanceof 还有一些极少数的应用场景,typeof 只有一个实际的应用(译者注:这个实际应用是用来检测一个对象是否已经定义或者是否已经赋值), 而这个应用却不是用来检查对象的类型。

注意:由于 typeof 也可以像函数的语法被调用,比如 typeof(obj),但这并是一个函数调用。 那两个小括号只是用来计算一个表达式的值,这个返回值会作为 typeof 操作符的一个操作数。 实际上不存在名为 typeof 的函数。

JavaScript 类型表格

Value Class Type
————————————-
“foo” String string
new String(“foo”) String object
1.2 Number number
new Number(1.2) Number object
true Boolean boolean
new Boolean(true) Boolean object
new Date() Date object
new Error() Error object
[1,2,3] Array object
new Array(1, 2, 3) Array object
new Function(“”) Function function
/abc/g RegExp object (function in Nitro/V8)
new RegExp(“meow”) RegExp object (function in Nitro/V8)
{} Object object
new Object() Object object
上面表格中,Type一列表示 typeof 操作符的运算结果。可以看到,这个值在大多数情况下都返回 “object”。

Class一列表示对象的内部属性 [[Class]] 的值。

JavaScript 标准文档中定义:[[Class]] 的值只可能是下面字符串中的一个: Arguments, Array, Boolean, Date, Error, Function, JSON, Math, Number, Object, RegExp, String.

为了获取对象的 [[Class]],我们需要使用定义在 Object.prototype 上的方法 toString。

对象的类定义

JavaScript 标准文档只给出了一种获取 [[Class]] 值的方法,那就是使用 Object.prototype.toString。

1 function is(type, obj) {
2 var clas = Object.prototype.toString.call(obj).slice(8, -1);
3 return obj !== undefined && obj !== null && clas === type;
4 }
5
6 is('String', 'test'); // true
7 is('String', new String('test')); // true

 

上面例子中,Object.prototype.toString 方法被调用,this 被设置为了需要获取 [[Class]] 值的对象。

译者注:Object.prototype.toString 返回一种标准格式字符串,所以上例可以通过 slice 截取指定位置的字符串,如下所示:

1 Object.prototype.toString.call([]) // "[object Array]"
2 Object.prototype.toString.call({}) // "[object Object]"
3 Object.prototype.toString.call(2) // "[object Number]"

ES5 提示:在 ECMAScript 5 中,为了方便,对 null 和 undefined 调用 Object.prototype.toString 方法, 其返回值由 Object 变成了 Null 和 Undefined。

译者注:这种变化可以从 IE8 和 Firefox 4 中看出区别,如下所示:

01 // IE8
02 Object.prototype.toString.call(null) // "[object Object]"
03 Object.prototype.toString.call(undefined) // "[object Object]"
04
05 // Firefox 4
06 Object.prototype.toString.call(null) // "[object Null]"
07 Object.prototype.toString.call(undefined) // "[object Undefined]"
08 测试为定义变量
09
10 typeof foo !== 'undefined'

 

上面代码会检测 foo 是否已经定义;如果没有定义而直接使用会导致 ReferenceError 的异常。 这是 typeof 唯一有用的地方。

结论

为了检测一个对象的类型,强烈推荐使用 Object.prototype.toString 方法; 因为这是唯一一个可依赖的方式。正如上面表格所示,typeof 的一些返回值在标准文档中并未定义, 因此不同的引擎实现可能不同。

除非为了检测一个变量是否已经定义,我们应尽量避免使用 typeof 操作符。

instanceof 操作符

instanceof 操作符用来比较两个操作数的构造函数。只有在比较自定义的对象时才有意义。 如果用来比较内置类型,将会和 typeof 操作符 一样用处不大。

比较自定义对象

1 function Foo() {}
2 function Bar() {}
3 Bar.prototype = new Foo();
4
5 new Bar() instanceof Bar; // true
6 new Bar() instanceof Foo; // true

 

01 // 如果仅仅设置 Bar.prototype 为函数 Foo 本省,而不是 Foo 构造函数的一个实例
02 Bar.prototype = Foo;
03 new Bar() instanceof Foo; // false
04 instanceof 比较内置类型
05
06 new String('foo') instanceof String; // true
07 new String('foo') instanceof Object; // true
08
09 'foo' instanceof String; // false
10 'foo' instanceof Object; // false

 

有一点需要注意,instanceof 用来比较属于不同 JavaScript 上下文的对象(比如,浏览器中不同的文档结构)时将会出错, 因为它们的构造函数不会是同一个对象。

结论

instanceof 操作符应该仅仅用来比较来自同一个 JavaScript 上下文的自定义对象。 正如 typeof 操作符一样,任何其它的用法都应该是避免的。

类型转换

JavaScript 是弱类型语言,所以会在任何可能的情况下应用强制类型转换。

01 // 下面的比较结果是:true
02 new Number(10) == 10; // Number.toString() 返回的字符串被再次转换为数字
03
04 10 == '10'; // 字符串被转换为数字
05 10 == '+10 '; // 同上
06 10 == '010'; // 同上
07 isNaN(null) == false; // null 被转换为数字 0
08 // 0 当然不是一个 NaN(译者注:否定之否定)
09
10 // 下面的比较结果是:false
11 10 == 010;
12 10 == '-10';

 

ES5 提示:以 0 开头的数字字面值会被作为八进制数字解析。 而在 ECMAScript 5 严格模式下,这个特性被移除了。

为了避免上面复杂的强制类型转换,强烈推荐使用严格的等于操作符。 虽然这可以避免大部分的问题,但 JavaScript 的弱类型系统仍然会导致一些其它问题。

内置类型的构造函数

内置类型(比如 Number 和 String)的构造函数在被调用时,使用或者不使用 new 的结果完全不同。

1 new Number(10) === 10; // False, 对象与数字的比较
2 Number(10) === 10; // True, 数字与数字的比较
3 new Number(10) + 0 === 10; // True, 由于隐式的类型转换

使用内置类型 Number 作为构造函数将会创建一个新的 Number 对象, 而在不使用 new 关键字的 Number 函数更像是一个数字转换器。

另外,在比较中引入对象的字面值将会导致更加复杂的强制类型转换。

最好的选择是把要比较的值显式的转换为三种可能的类型之一。

转换为字符串

” + 10 === ’10′; // true
将一个值加上空字符串可以轻松转换为字符串类型。

转换为数字

+’10′ === 10; // true
使用一元的加号操作符,可以把字符串转换为数字。

译者注:字符串转换为数字的常用方法:

1 +'010' === 10
2 Number('010') === 10
3 parseInt('010', 10) === 10 // 用来转换为整数
4
5 +'010.2' === 10.2
6 Number('010.2') === 10.2
7 parseInt('010.2', 10) === 10

 

转换为布尔型

通过使用否操作符两次,可以把一个值转换为布尔型。

1 !!'foo'; // true
2 !!''; // false
3 !!'0'; // true
4 !!'1'; // true
5 !!'-1' // true
6 !!{}; // true
7 !!true; // true

 

setTimeout和setInterval

setTimeout 和 setInterval

由于 JavaScript 是异步的,可以使用 setTimeout 和 setInterval 来计划执行函数。

注意:定时处理不是ECMAScript 的标准,它们在 DOM (文档对象模型) 被实现。

1 function foo() {}
2 var id = setTimeout(foo, 1000); // 返回一个大于零的数字

当 setTimeout 被调用时,它会返回一个 ID 标识并且计划在将来大约1000 毫秒后调用 foo 函数。 foo 函数只会被执行一次。

基于 JavaScript 引擎的计时策略,以及本质上的单线程运行方式,所以其它代码的运行可能会阻塞此线程。 因此没法确保函数会在 setTimeout 指定的时刻被调用。

作为第一个参数的函数将会在全局作用域中执行,因此函数内的 this 将会指向这个全局对象。

1 function Foo() {
2 this.value = 42;
3 this.method = function() {
4 // this 指向全局对象
5 console.log(this.value); // 输出:undefined
6 };
7 setTimeout(this.method, 500);
8 }
9 new Foo();

注意:setTimeout 的第一个参数是函数对象,一个常犯的错误是这样的 setTimeout(foo(), 1000), 这里回调函数是 foo 的返回值,而不是foo本身。 大部分情况下,这是一个潜在的错误,因为如果函数返回 undefined,setTimeout 也不会报错。

setInterval 的堆调用

setTimeout 只会执行回调函数一次,不过 setInterval – 正如名字建议的 – 会每隔 X 毫秒执行函数一次。 但是却不鼓励使用这个函数。

当回调函数的执行被阻塞时,setInterval 仍然会发布更多的毁掉指令。在很小的定时间隔情况下,这会导致回调函数被堆积起来。

1 function foo(){
2 // 阻塞执行 1 秒
3 }
4 setInterval(foo, 1000);

上面代码中,foo 会执行一次随后被阻塞了一分钟。

在 foo 被阻塞的时候,setInterval 仍然在组织将来对回调函数的调用。 因此,当第一次 foo 函数调用结束时,已经有10次函数调用在等待执行。

处理可能的阻塞调用

最简单也是最容易控制的方案,是在回调函数内部使用 setTimeout 函数。

1 function foo(){
2 // 阻塞执行 1 秒
3 setTimeout(foo, 1000);
4 }
5 foo();

这样不仅封装了 setTimeout 回调函数,而且阻止了调用指令的堆积,可以有更多的控制。 foo 函数现在可以控制是否继续执行还是终止执行。

手工清空定时器

可以通过将定时时产生的 ID 标识传递给 clearTimeout 或者 clearInterval 函数来清除定时, 至于使用哪个函数取决于调用的时候使用的是 setTimeout 还是 setInterval。

1 var id = setTimeout(foo, 1000);
2 clearTimeout(id);

 

清除所有定时器

由于没有内置的清除所有定时器的方法,可以采用一种暴力的方式来达到这一目的。

1 // 清空"所有"的定时器
2 for(var i = 1; i < 1000; i++) {
3 clearTimeout(i);
4 }

可能还有些定时器不会在上面代码中被清除(译者注:如果定时器调用时返回的 ID 值大于 1000), 因此我们可以事先保存所有的定时器 ID,然后一把清除。

隐藏使用 eval

setTimeout 和 setInterval 也接受第一个参数为字符串的情况。 这个特性绝对不要使用,因为它在内部使用了 eval。

注意:由于定时器函数不是 ECMAScript 的标准,如何解析字符串参数在不同的 JavaScript 引擎实现中可能不同。 事实上,微软的 JScript 会使用 Function 构造函数来代替 eval 的使用。

01 function foo() {
02 // 将会被调用
03 }
04
05 function bar() {
06 function foo() {
07 // 不会被调用
08 }
09 setTimeout('foo()', 1000);
10 }
11 bar();

 

由于 eval 在这种情况下不是被直接调用,因此传递到 setTimeout 的字符串会自全局作用域中执行; 因此,上面的回调函数使用的不是定义在 bar 作用域中的局部变量 foo。

建议不要在调用定时器函数时,为了向回调函数传递参数而使用字符串的形式。

1 function foo(a, b, c) {}
2
3 // 不要这样做
4 setTimeout('foo(1,2, 3)', 1000)
5
6 // 可以使用匿名函数完成相同功能
7 setTimeout(function() {
8 foo(a, b, c);
9 }, 1000)

 

注意:虽然也可以使用这样的语法 setTimeout(foo, 1000, a, b, c), 但是不推荐这么做,因为在使用对象的属性方法时可能会出错。 (译者注:这里说的是属性方法内,this 的指向错误)

结论

绝对不要使用字符串作为 setTimeout 或者 setInterval 的第一个参数, 这么写的代码明显质量很差。当需要向回调函数传递参数时,可以创建一个匿名函数,在函数内执行真实的回调函数。

另外,应该避免使用 setInterval,因为它的定时执行不会被 JavaScript 阻塞。

解决页面由单引号、双引号、换行符导致的问题

最近在做驴妈妈旅游网站改版时,遇到了一个关于数据在界面显示的问题。网站使用的是el表达式作为页面数据显示,但在使用过程中,如果读取的字符串中存在‘”’(引号),则会出现错误

  例子:title="海螺沟自由行,观"日照金山" 泡温泉,住逸家温泉酒店+金山饭店双人标间_双人套票(CD)"  提示信息title显示:海螺沟自由行,观

  针对该问题可以使用如下解决方案

  (1)使用替换:title="${title?if_exists?replace(‘"’,)}"

  (2)用EL表达式的自定义函数

首先写格式化字符串的方法。

01
02
03
04
05
06
07
08
09
10
11
//这里我将换行符转化为空格," 转化为 \",'转化为 \'; ,也就是转化为JS的转义格式
public static String fs(String s){
 if(s!=null && s.length()>0){
 s = s.replaceAll("(\r|\n|\r\n|\n\r)", " ");
 s = s.replaceAll("\"","\\\\"+"\"");
 s = s.replaceAll("\'","\\\\"+"\'");
 return s;
 }else{
 return "";
 }
 }

WEB-INF下创建文件夹tags,用来存放标签文件。

然后在tags文件夹下创建formatForJS.tld文件,描述自定义函数。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8" ?>
 version="2.0">
 <description>A tag library exercising SimpleTag handlers.</description>
 <tlib-version>1.0</tlib-version>
 <short-name>SimpleTagLibrary</short-name>
 <uri>/fs</uri>
<function>
<description>format string value for JS</description>
<name>fs</name>
<function-class>kyo.org.ClassName</function-class>
<function-signature>java.lang.String fs(java.lang.String)</function-signature>
</function>
  
</taglib>

配置web.xml

1
2
3
4
5
6
7
8
<jsp-config>
<taglib>
<taglib-uri>http://www.2016k.com</taglib-uri>
<taglib-location>
/WEB-INF/tags/formatForJS.tld
</taglib-location>
</taglib>
</jsp-config>

在JSP页面的应用.
1
2
3
4
<%@ taglib prefix="k"  uri="http://www.2016k.com"%>
<script type="text/javascript">
alert('${k:fs(param)}');
</script>

如果param中含有单引号,就不会报脚本错误了。
以上代码,在tomcat6下测试通过,但是如果你的应用是部署在Websphere下,则还有需要注意的地方。

代码分析工具FindBugs详细配置使用教程

一,关于FindBugs

 (1) FindBugs 是由马里兰大学提供的一款开源 Java 静态代码分析工具,它检查类或者 JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题(先对编译后的class进行扫描,然后进行对比),寻找出真正的缺陷和潜在的性能问题。在开发阶段和维护阶段都可使用。

  现今检测的类型如下:

  正确性:如强制类型转换
  标准:如某个类实现了equals方法但未实现hashCode方法
  多线程:主要在同步和多线程问题
  性能:潜在的性能委托
  安全:相关
  高危:导致Bug的可能性最高
  更多的Bug描述:http://findbugs.sourceforge.net/bugDescriptions.html

二,下载FindBugs

 (1) 可以在官方网站 http://findbugs.sourceforge.net/downloads.html 下载相应的版本(命令行版本,swing版本,ant版本,eclipse插件版本-离线包和在线更新两种方式)。

三,使用FindBugs

  (1) 开发可以使用eclipse插件方式安装好FindBugs,然后在eclipse中的Package Explorer 或者Navigater 里面,右键点击项目,在弹出的右键菜单中即可选中FindBugs运行。

  (2) 运行完成后代码中会有相应Bug级别的虫子样式标识(红色图标表示 bug 较为严重,黄色的图标表示 bug 为警告程度),鼠标移动到相应的虫子上,可查看详细描述和建议方案。

  (3) 高级运用:在eclipse中的属性配置可以针对不同的项目配置不同的FindBugs属性。

  (4) 在ant中也可以配置FindBugs;其他使用如:命令行检查,swing检查等

  (4) 在项目中设置自动运行:选中项目-右键-FindBugs-Run automatically

四,检查Bugs

1.找出 hash equals 不匹配

  List、Map、Set 等都调用equals() 和 hashCode(),重写对象的 equals() 方法,但是没有重写它的 hashCode 方法,或者相反的情况时。

2.忽略方法返回值
/**
* 对点评内容中的一些对页面有影响的字进行替换
* @param content
* @return*/
private final String convert(final String content) {
    if (null != content) {
       
content.replaceAll("\"", "\\\\\"").replaceAll("<", "<").replaceAll(">", ">");
       
return content;
    }
   
 return null;
}

   注:replaceAll基于正则表达式来转换的
3.Null指针
private void nullPoint(){
     OverrideEquals oe=oMap.get(“str”);
     if(oe!=null){
       oe .process();  
      }
      System.out.println(oe.getName());

}

 
4.初始化之前读取数据
private List<String> strList;
private void readList(String strings){
   StringTokenizer st=new StringTokenizer(strings);
   while(st.hasMoreTokens()){
      strList.add(st.nextToken());
   }

}

5.使用泛型时如非必要 可以加入:@SuppressWarnings("rawtypes")

 

HTTP服务器 nginx for windows下载 详细安装与配置

nginx for windo

Nginx内容导航

本文内容大纲如下
一、nginx的特性和简介
二、nginx for windows的下载与安装
三、nginx处理静态资源的配置
四、nginx 反向代理设置
五、nginx 常见错误

引言:为什么要使用nginx

目前很多大型网站都使用了nginx,新浪、网易、QQ等都使用了nginx,说明nginx的稳定性和性能还是非常不错的。这主要是因tomcat处理静态资源的速度比较慢,所以首先想到的就是把所有静态资源(JS,CSS,image,swf),提到单独的服务器,用更加快速的HTTP服务器,这里选择了nginx了,nginx相比apache,更加轻量级,配置更加简单,而且nginx不仅仅是高性能的HTTP服务器,还是高性能的反向代理服务器。

一、nginx的特性和简介

  nginx简介
Nginx(发音同 engine x)是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。由俄罗斯的程序设计师Igor Sysoev所开发,最初供俄国大型的入口网站及搜寻引擎Rambler(俄文:Рамблер)使用。  其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页伺服器中表现较好.目前中国大陆使用nginx网站用户有:新浪、网易、 腾讯,另外知名的微网志Plurk也使用nginx。

  nginx的特性
1.目前官方 Nginx 并不支持Windows,您只能在包括Linux,UNIX,BSD系统下安装和使用
2.Nginx 本身只是一个HTTP和反向代理服务器,它无法像Apache一样通过安装各种模块来支持不同的页面脚本,例如PHP、CGI等
3.Nginx 支持简单的负载均衡和容错
4.支持作为基本 HTTP 服务器的功能,例如日志、压缩、Byte ranges、Chunked responses、SSL、虚拟主机等等,应有尽有

二、nginx for windows的下载与安装

–下载: 国内几大软件站都有已经封装好的nginx下载地址,管网的nginx由于没有封装,所以安装起来有点儿麻烦,推荐下载地址:

CSDN下载  51CTO下载   程序员下载  ITeye下载

 –安装:一直点下一步,操作非常简单

三、nginx处理静态资源的配置

nginx的配置到,nginx的配置文件目录:nginx/conf/nginx.conf

下面是各个配置文件的参数解释:

#启动GZIP压缩CSS和JS
gzip on;
# 压缩级别 1-9,默认是1,级别越高压缩率越大,当然压缩时间也就越长
gzip_comp_level 4;
# 压缩类型
gzip_types text/css application/x-javascript;

# 定义静态资源访问的服务,对应的域名:res.bywei.cn
server {
listen 80;
server_name res.bywei.cn;

# 开启服务器读取文件的缓存,
open_file_cache max=200 inactive=2h;
open_file_cache_valid 3h;
open_file_cache_errors off;

charset utf-8;

# 判断如果是图片或swf,客户端缓存5天
location ~* ^.+.(ico|gif|bmp|jpg|jpeg|png|swf)$ {
root /usr/local/resource/;
access_log off;
index index.html index.htm;
expires 5d;
}

# 因JS,CSS改动比较频繁,客户端缓存8小时
location ~* ^.+.(js|css)$ {
root /usr/local/resource/;
access_log off;
index index.html index.htm;
expires 8h;
}

# 其他静态资源
location / {
root /usr/local/resource;
access_log off;
expires 8h;
}
}

四、nginx 反向代理设置

# 反向代理服务,绑定域名www.bywei.cn
server {
listen 80;
server_name www.bywei.cn;

charset utf-8;

# BBS使用Discuz!
# 因反向代理为了提高性能,一部分http头部信息不会转发给后台的服务器,
# 使用proxy_pass_header 和 proxy_set_header 把有需要的http头部信息转发给后台服务器
location ^~ /bbs/ {
root html;
access_log off;
index index.php;
# 转发host的信息,如果不设置host,在后台使用request.getServerName()取到的域名不是www.bywei.cn,而是127.0.0.1
proxy_set_header Host $host;
# 因Discuz! 为了安全,需要获取客户端User-Agent来判断每次POST数据是否跟第一次请求来自同1个浏览器,
# 如果不转发User-Agent,Discuz! 提交数据就会报"您的请求来路不正确,无法提交"的错误
proxy_pass_header User-Agent;
proxy_pass http://127.0.0.1:8081;
}

# 其他请求转发给tomcat
location / {
root html;
access_log off;
index index.jsp;
proxy_pass http://127.0.0.1:8080;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}

 

五、nginx 常见错误

1. nginx启动时: bind() to 0.0.0.0:80 failed (98: Address already in use)  这个错误是80端口别占用,可以检查一下是否有程序在占用这个端口,nginx的默认端口

2. 配置php – fpm_children_bury() fpm_children_make()错误

php-fpm的日志情况:
Aug 06 16:18:48.813346 [WARNING] fpm_children_bury(), line 215: child 15381 (pool default) exited on signal 9 SIGKILL after 19293.913994 seconds from start
Aug 06 16:18:48.816439 [NOTICE] fpm_children_make(), line 352: child 26844 (pool default) started
解决:
修改php-fpm.conf的配置,将max_requests和max_children调整为合适的值。
<value name="max_requests">102400</value>
<value name="max_children">100</value>

3.“upstream timed out”的解决办法

I found answer on my posting on nginx forum – http://forum.nginx.org/read.php?2,127854

The answer is in my case was to set:

request_terminate_timeout=30s

in php-fpm config.

Note, you can use values other than 30s also.

I used it to match my value in main php.ini file which is:

max_execution_time = 30

其它解决办法:

504 Gateway Time-out问题常见于使用nginx作为web server的服务器的网站, 我遇到这个问题是在升级discuz论坛的时候遇到的。一般看来, 这种情况可能是由于nginx默认的fastcgi进程响应的缓冲区太小造成的, 这将导致fastcgi进程被挂起, 如果你的fastcgi服务对这个挂起处理的不好, 那么最后就极有可能导致504 Gateway Time-out
现在的网站, 尤其某些论坛有大量的回复和很多内容的, 一个页面甚至有几百K,默认的fastcgi进程响应的缓冲区是8K, 我们可以设置大点在nginx.conf里,

jdk1.7正式版新特性一览表

jdk1.7新特性和新功能的用法

  新增了jLayer类,修饰swing组件;
  把swing从java.sun.***.swing 移动到了javax.swing中;
  增加了窗体透明效果和不规则窗体效果;
  JColorChooser 类新增 HSV tab;
  在html中嵌入jnlp文件;
  可在javascript中检测applet是否加载完成;
  可拖拽程序到浏览器外部或者使用快捷方式启动;
  网络URLClassLoader.close方法;
  支持 Sockets Direct Protocol (SDP) 提供高性能网络连接;
  集合新增TransferQueue接口,是BlockingQueue的改进版,实现类为LinkedTransferQuene;
  xml中支持jaxp,jax-ws,jaxb;
  switch代码块中加入String的支持;
  创建List / Set / Map 时写法:
    List< String> list = ["item"]; 
    String item = list[0]; 
    Set< String > set = {"item"}; 
    Map< String,Integer > map = {"key" : 1}; 
    int value = map["key"];
  资源的自动回收管理:
        try (BufferedReader br = new BufferedReader(new FileReader(path)) { 
         return br.readLine(); 
        }
  泛型实例创建:
   Map<String,String> hello = new Map<>();
  在数字中使用下划线
    int billion = 1_000_000_000;
  二进制符号:
    int binary = 0b1001_1001;
  一个catch里捕捉多个异常类型:
        try { 
    Here comes your code…. 
    } 
    catch(IOException | NullPointerException | ……….) { 
    }
 
  jdk1.7的bug:jdk1.7hotspot循环bug,OSR(On-Stack Replacement)控制流退出且对应的内存没

有被考虑到,JVM统计导致使用循环优化后的JVM崩溃
 

 

 
作者:Nasser  新特性一览表

  Swing

  新增 JLayer 类,是一个灵活而且功能强大的Swing组件修饰器,使用方法:How to Decorate Components with JLayer.

  Nimbus Look and Feel 外观从 com.sun.java.swing 包移到 javax.swing 包中,详情:javax.swing.plaf.nimbus.

  更轻松的重量级和轻量级组件的混合。

  支持透明窗体以及非矩形窗体的图形界面,请看 How to Create Translucent and Shaped Windows。

  JColorChooser 类新增 HSV tab.

  网络

  新增 URLClassLoader.close 方法,请看 Closing a URLClassLoader.

  支持 Sockets Direct Protocol (SDP) 提供高性能网络连接,详情请看 Understanding the Sockets Direct Protocol.

  集合

  新增 TransferQueue 接口,是 BlockingQueue 的改进版,实现类为 LinkedTransferQueue.

  RIA/发布

  拖拽的小程序使用一个默认或者定制的标题进行修饰,详情:Requesting and Customizing Applet Decoration in Draggable Applets.

  JNLP 文件做了如下方面的增强,详情请看 JNLP File Syntax:

   The os attribute in the information and resources elements can now contain specific versions of Windows, such as Windows Vista or Windows 7.

  Applications can use the install attribute in the shortcut element to specify their their desire to be installed. Installed applications are not removed when the Java Web Start cache is cleared, but can be explicitly removed using the Java Control Panel.

  Java Web Start applications can be deployed without specifying the codebaseattribute; see Deploying Without Codebase

  可直接在 HTML 中嵌入 JNLP 文件:Embedding JNLP File in Applet Tag.

  可在 JavaScript 代码中检查 Applet 是否已经加载完成:Handling Initialization Status With Event Handlers.

  可在 Applet 从快捷方式启动或者拖出浏览器时对窗口样式和标题进行控制:Requesting and Customizing Applet Decoration in Developing Draggable Applets.

  XML

   包含 Java API for XML Processing (JAXP) 1.4.5, 支持 Java Architecture for XML Binding(JAXB) 2.2.3, 和 Java API for XML Web Services (JAX-WS) 2.2.4.

 

JavaRebel详细安装与使用技巧

JavaRebel详细安装与使用技巧

曾一直使用Eclipse自带的发布启动项目功能,感觉功能已经蛮强大,但是最近遇到了很大的系统,这就让我每次花在启动项目的时间上费了很大的功夫。偶然听同事介绍了一项技术,我才真正接触了JavaRebel,不得不感叹它功能的强大。现在我简单介绍一项JavaRebel,方便以后有需要的同事使用。

     JavaRebel是一个非开源的JVM插件,给Java带来了Ruby和PHP风格的动态重新装载类特性,JavaRebel允许一个独立的或运行在应用服务器上的应用重新装载在运行过程中(on the fly)发生的大多数类变化,包括增加或删除方法和域。在最新版本中关注了启动时间和后台 CPU 的使用率。一些用户报称启动应用服务器的时间比用之前版本快了 2-3 倍。支持所有主流的容器和框架,在其他的之上也可能工作的很好。扩展了对 Java 1.4 
的支持。像 BEA Weblogic 8.X、Oracle OC 4J 9.x/10.X 和 Tomcat 4.x 也被支持。支持使用反射。新加到类中的方法对可用 Java 5+ 反射 API 访问到。新版本对于所支持系统提供即拆即用。javarebel 可以使Java class文件重新加载速度更快,节省了开发时间,而且修改了配置文件和在class中加入任何的代码都不用重启服务器,完全支持Annotation reloading.(Annotation动态载入),当classes字节码文件重新载入的时,它会在触发寄存器监听,允许使用自定义的方法去处理。

安装方法一:
在eclipse中>Window>preferences>Myeclipse>servers>tomcat>tomcat x>jdk中有个选项‘Optional Java VM arguments’中加入下面代码:
    -noverify  
    -javaagent:F:/jrebel-3.0-M1/jrebel.jar  
    -Drebel.dirs=F:\eclipse\workspace\qhtjProject_new\WebRoot\WEB-INF\classes 
   个别参数说明:
         -javaagent:     这个是你使用的javaRebel(jRebel)的jar包的路径,注意其中的斜线方向。
         -Drebel.dirs=   这个是你要监控的项目的class文件路径

安装方法二:

  eclipse插件安装地址
  http://www.zeroturnaround.com/blog/how-to-install-and-use-jrebel-formerly-javarebel-in-tomcat-with-the-eclipse-ide

javarebel 可以使Java class文件重新加载速度更快,节省了开发时间,而且修改了配置文件和在class中加入任何的代码都不用重启服务器,完全支持Annotation reloading.(Annotation动态载入),当classes字节码文件重新载入的时,它会在触发寄存器监听,允许使用自定义的方法去处理。

javaRebel项目:

    项目主页: http://www.zeroturnaround.com/javarebel/
    文档地址: http://www.zeroturnaround.com/javarebel/installation/
    下载地址: http://www.zeroturnaround.com/download/

文章原创:程序员百味
转载请注明出处  http://www.bywei.cn/blog