漫谈受控与非受控组件

在大多数情况下,我们推荐使用 受控组件 来处理表单数据。在一个受控组件中,表单数据是由 react 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。

以上是 react 官网对受控组件与非受控组件的一次解释,大学刚刚毕业时候,看到这一段, 实在有些难以接受,在我看来,既然已经选择使用了 react ,就应该完全彻底的使用受控组件,为什么开发者会有直接使用 DOM 节点开发的的非受控组件。当时在 vue 中,并没有这种设定。同时我当时在开发 Sass 网站,因为在开发 pc 端网站总是需要即时验证(即时给予用户交互,不让用户在填写完整的数据后再提示错误以致于过分沮丧)。

不过现在来看,非受控组件的确是 react 非常好的设计。

非受控与受控组件的区别与选择

非受控的输入就像传统的html表单输入一样:

class Form extends Component {
  /** 提交时候获取数据 */  
  handleSubmitClick = () => {
    const name = this._name.value;
    // 检测数据提示然后
  }
  render() {
    return  (
      <div>
        <input type="text" ref={input => this._name = input} />
        <button onClick={this.handleSubmitClick}>Sign up</button>
      </div>
    );
  }
}

我们只有在触发其他事件(例如点击提交按钮时),才可以获取 DOM 数据中的值。

受控的输入接受当前的值作为参数,并且在值发生改变的时候执行回调函数。

class NameForm extends react.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('提交的名字: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          名字:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="提交" />
      </form>
    );
  }
}

当用户一旦进行了数据的修改,程序立刻就可以知道哪些状态发生了改变,这时候我们就可以基于状态修改来构建更好的表单交互与用户体验。

受控与非受控组件可以类比服务器和客户端的 push 和 pull 模型,push 是服务器主动发送数据给客户端,一旦有“数据改变”就立即推送,所以用户可以基于最新的消息做处理,而 pull 模型没法知道数据的细节变化,即使遇到了错误输出也只能间接检查通知用户。

如果你的表单在交互的过程中足够简单,仅仅只需要提交时候验证,并且没有级联数据,强制格式输入等复杂用户交互。那么可以选择非受控组件。当然选择并不是一次性的,我们可以在开发过程中迁移非受控组件到受控组件(应该没有小伙伴反向操作吧)。

全量与增量问题

事实上,我们在生活与开发过程中,总是会遇到类似 react 中受控与非受控组件等同的问题。其实也就是全量与增量问题。基于不同的领域,不同的设计目标,不同的用户,不同的团队,当前受限的资源以及当下要完成的目标设定,都会影响问题的最终决策。这里我列出最近我遇到的几个问题。供大家参考阅读。

老程序库 ts 升级

从去年开始,新项目都使用 TypeScript 进行开发,面对复杂的大型项目来说,TypeScript 利好不言而喻。尤其在业务不稳定,面临大量改动时候。动态类型总会让人担惊受怕。对于老项目,我们也想从中得到 TypeScript 的利好。

前端的 vue 项目已经开发 3 年多了,当前的项目也从一个简单的单页面应用程序变成了基于业务的多页面应用。同时也拆分出来一系列的基础库与 widget。

我们都知道修改必然是从基础升级,目前我们还面临着繁重的开发任务,所以我们没有时间资源进行全量升级,同时基础依赖中也有 vue 业务组件。vue 3 此时的开发进度也让我们“进退两难”。所以我们只能想去改动非组件的辅助代码。

这是我们先增加了 tsc 编译配置,可以让新模块使用 TypeScript,同时可以在空闲时候修改部分老代码。

但是,作为依赖库,仅仅只在内部 TypeScript 代码是不够的,进一步来说,老大还想要定义文件(.d.ts)辅助其他项目开发。所以我们使用 tsc 编译出当前代码的所有 TypeScript 定义文件,然后使用 gulp-insert 对定义文件进行修改后投入使用。

小程序组件开发

面对移动端 Sass 开发,我们仍要提供复杂的表单,同时移动端不需要太多的交互。结合小程序 setData 非常耗费性能,同时小程序组件不像 vue,react 有单向数据流的说法。所以数据放在各个组件内部反而更好,最后在提交时候,把各个组件的数据组合起来进行验证与对比提交,无论是性能还是开发都具有更好的体验。所以小程序表单提交反而使用“非受控组件”更好。

当然,移动端表单的侧重点是减少用户的输入,开发的精力应该投入在表单的默认数据上,从而减少用户的输入操作。

查询非法字符

在用户创造价值的时代中,审核用户提供的数据一定是重中之重。而我们目前小程序可以提供分享功能。而小程序本身也有查询文本安全的增值服务 api security.msgSecCheck。

这时候我们有两种选择,我们可以每一次提交可分享数据时候检查并提示含有非法字符,这样用户可以清楚的知道该次的数据提交中有非法字符。当然也可以在最终分享的时候提示有非法信息,但是此时面对如此大的数据量分享,用户恐怕很难查询出究竟是哪条信息出了问题。究竟是那种方式更好?这取决于小程序的用户量以及投入的资源。

同时还有很多例子,例如 js 文件修改,究竟是增量(字符串级别)还是全量(单个 js 文件) ? 大家需要根据公司和项目来判断。所以究竟使用什么方式,取决于你的业务,所拥有的资源,甚至是你面对的客户量级。

鼓励一下

如果你觉得这篇文章不错,希望可以给与我一些鼓励,在我的 github 博客下帮忙 star 一下。
博客地址

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/9312

利用 Powershell 编写简单的浏览器脚本

生活中有很多事情是低效益,重复性。比如每天上某些网站,先登录再签到打卡,比如每隔一段时间清理回收站的文件等等。一个成熟的软件工程师应该想到用软件解决他。

对于这些简单的小任务,一般用脚本实现。比如Python脚本或者JavaScript脚本等等,不过还得装运行环境。如果能在电脑上直接用就好了,那么有没有呢。

有,那就是基于命令行(.bat)脚本或者 Powershell 脚本(.ps1),这两个东东都是 Windows 自带运行环境的,比较方便的脚本。

而本文的主角就是 Powershell。

PowerShell 是一种跨平台的任务自动化和配置管理框架,由命令行管理程序和脚本语言组成。 与大多数接受并返回文本的 shell 不同,PowerShell 构建在 .NET 公共语言运行时 (CLR) 的基础之上,接受并返回 .NET 对象。 这一根本上的改变引入了全新的自动化工具和方法。详见 Powershell官方说明

长话短说,Show me the code。

下面是一个简单的示例,我准备写一个脚本,控制 IE 浏览器打开百度,然后搜索“微软药丸”,看看专业的药丸师有什么见解。

#声明变量
#待会要用的链接
$Url = "http://baidu.com"

#待会要用的关键词
$Keyword = "微软药丸"

#创建一个IE对象
$IE = New-Object -com internetexplorer.application
$IE.visible = $true

#导航到URL
$IE.navigate($url);

#等待完全加载
while ($IE.Busy -eq $true) {
    Start-Sleep -s 1
    Write-Host "wait for loading..." -ForegroundColor Gray
}

#在 Console 打印 Log
Write-Host "Enter Baidu Main Page" -ForegroundColor Cyan

#找到百度首页的输入框元素,输入关键词
$IE.Document.getElementById("kw").value = $Keyword

#找到百度首页的搜索按钮,执行点击事件   
$IE.Document.getElementById("su").Click()

Write-Host "Submit keyword" -ForegroundColor Cyan
start-sleep -milliseconds 500

#等待页面加载
while ($IE.Busy -eq $true) {
    Write-Host "wait for loading..." -ForegroundColor Gray
    Start-Sleep -s 1
}


#没有意义的等待延时,就是任性一下
Start-Sleep -s 1

Write-Host "Bye!" -ForegroundColor Cyan

在记事本输入这些代码,保存为 Baidu.ps1 文件。

在文件所在目录按住SHIFT键,在当前目录启动 Powershell。

输入.\Baidu.ps1,就可以执行了。

下面就是执行效果,完美~

这只是一个简单的例子,但是麻雀虽小,五脏俱全,我想介绍的启动 IE,输入文本,模拟点击,输出Log 都用到了。

你也可以用他来登录某些网站进行签到,领奖,打卡等等,思路是一样的。总之浏览器能做的事情都可以将其程序自动化。

这个时候你或许会觉得,哎,我每次都用手动执行脚本,那也不省事呀。能不能每天领一次奖呢?

Absolutely,下回我们简单介绍一下如何用“Windows任务计划”把脚本安排得明明白白。

========================== PS ===================================

1.Powershell的相关文章里面,通过IE获取元素一般写的是

$IE.Document.getElementById( " kw ")

本文也是,这个东西一般没啥问题,但是真出问题你就头疼,比如在Win10环境中我需要获取的某个Button元素一直找不到,然后还搜不到原因,一度没法使用。

但是最终在文档找到 了 IE 的全新 COM接口

$IE.Document.IhtmlDocument3_getElementById( " kw ")

如果执行脚本时发现类似下面这样的错误,搜百度也解决不了,那么请试试使用新的API。

Exception from HRESULT: 0x800A138A
At <path>.ps1:
char:5
+ $IE.Document.getElementsByID("kw").Click();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException

由于Powershell是通过COM接口去控制IE的,所以这个问题时 IE 浏览器的锅。因为它是预装的,而且IE11完全够用,一般还是用他。

原文 http://www.cnblogs.com/aureate-sunshine/p/13095763.html

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/9297

Js Function函数深入总结

整理了JavaScript中函数Function的各种,感觉函数就是一大对象啊,各种知识点都能牵扯进来,不单单是 Function 这个本身原生的引用类型的各种用法,还包含执行环境,作用域,闭包,上下文,私有变量等知识点的深入理解。

函数中的return

 return 语句可以不带有任何返回值,在这种情况下( return; 或函数中不含 return 语句时),函数在停止执行后将返回 undefiend 值。这种用法一般在需要提前停止函数执行而又不需要返回值的情况下。

 return false 可以阻止元素的默认事件。

 return 返回的是其所在函数的返回值

function n(){
  (function(){
     return 5;
  })();
}
n();// undefined
//立即执行匿名函数中的return语句其实是返回给它所在的匿名函数的。

function n(){
  var num= (function(){
     return 5;
  })();
  console.log(num);
}

Function类型

函数实际上是对象,每个函数实际上都是 Function 类型的实例。而且与其他引用类型一样具有属性和方法。函数名实际上是一个指向内存堆中某个函数对象的指针。

定义函数的方式

函数声明

function sum(num1,num2){
  return num1+num2;
}

函数表达式

var sum=function(num1,num2){
 return num1+num2;
};

定义了一个变量 sum 并将其初始化为一个函数,注意到 function 关键字后面并没有函数名,这是因为在使用函数表达式定义函数,没必要使用函数名,通过变量 sum 即可引用函数。还要注意函数末尾有个分号,就像声明其他变量一样。

 new 构造函数,虽然这种用法也是函数表达式,但该用法不推荐。因为这种语法会导致解析两次代码(第一次是解析常规的ECMAScript代码,第二次是解析传入构造函数中的字符串),影响性能。
使用 Function 构造函数,构造函数可以接受任意数量的参数,但最后一个参数始终都被看成是函数体,前面的参数则枚举出了新函数的参数。

var sum=new Function('num1','num2','return num1+num2;');
sum;// 
function anonymous(num1,num2
/**/) {
return num1+num2;
}

当使用不带圆括号的函数名是访问函数指针,而非调用函数。

理解参数

ECMAScript中所有参数传递的都是值(即使是引用也是传递的地址值,不是引用传递参数。ECMAScript函数不介意传递进来多少个参数,也不在乎传进来的参数是什么数据类型。之所以这样,是因为ECMAScript中的参数在内部是用一个数组表示的。函数接收到的始终都是这个数组,而不关心数组中包含哪些参数。在函数体内,可以通过 arguments 对象来访问这个数组。从而获取传递给函数的每个参数。

function func(){
 console.log(Object.prototype.toString.call(arguments));
}

func();// [object Arguments]

关于 arguments 的行为,它的值永远与对应命名参数的值保持同步。因为 arguments 对象中的值会自动反映到对应的命名参数。所以修改 arguments[1] ,也就修改了 num2 。不过这并不是说读取这两个值会访问相同的内存空间,它们的内存空间是独立的,但他们值会同步(WHY??),要是JavaScript能直接访问内存就好了验证一下。

但如果只传入了一个参数,那么 arguments[1] 设置的值不会反映到命名参数中,这是因为 arguments 对象的长度是由传入参数个数决定的,不是由定义函数时的命名参数个数决定的,没有传递值的命名参数将自动被赋予 undefiend 值,这就跟定义了变量但没初始化一样。

function doAdd(num1,num2){
  console.log(arguments.length);
  console.log(num2)
  arguments[1]=10;
  console.log(num2);
}
doAdd(5,0);//2 0 10

doAdd(5);//1 undefiend undefined

没有重载

ECMAScript函数不能像传统意义上那样实现重载,而在其他语言中(Java),可以为一个函数编写两个定义,只要这两个定义的签名(接收参数的类型和数量)不同即可。

不能实现重载的原因:

ECMAScript函数没有签名,因为其参数是由包含零个或多个值的数组来表示的。没有函数签名,真正的重载是不可能做到的。在ECMAScript中定义两个名字相同的的函数,则该名字只属于后定义的函数。如何实现类似于Java中的重载呢,其实可以通过判断传入函数的参数类型和个数来做出不同响应。

function reload(){
   if(arguments.length==0){
       console.log('没传参');
   }else if(arguments.legth==1){
      console.log('传了一个参数');
  }
}

深入理解:将函数名想象为指针,也有助于理解为什么ECMAScript中没有函数重载的概念。

function add(){
  return 100;
}
function add(num){
 return num+200; 
}

//实际上和下面代码没什么区别
function add(){
  return 100;
}
add=function(num){
 return num+200; 
}

函数声明和函数表达式

实际上解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。对于解释型语言来说编译步骤为:

词法分析(将字符流转换为记号流,是一对一的硬性翻译得到的是一堆难理解的记号流)

语法分析(这里进行所谓的变量提升操作,其实我觉得是把这些提升的变量保存在语法树中。要构造语法树,若发现无法构造就会报语法错误,并结束整个代码块的解析)

之后可能有语义检查,代码优化等。得到语法树后就开始解释执行了。解释性语言没有编译成二进制代码而是从语法树开始执行。

解析器会先读取函数声明,并使其在执行任何代码之前可用。至于函数表达式,则必须等到执行阶段才会被真正赋值。什么意思呢?虽然两者都进行了变量提升,待真正执行时构造活动对象从语法树种取声明添加到执行环境中,但一个是函数提升,一个是变量提升。

//函数声明
console.log(func);//function func(){}
function func(){

}

//函数表达式
console.log(func1);// undefined
var func1=function(){};
console.log(func1);// function(){}

作为值的函数

因为ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用。不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。

function callSomeFunction(someFunction,someArgument){
  return someFunction(someArgument);
}

function concated(str){
  return "Hi "+str;
}

callSomeFunction(concated,'xx');// 'Hi xx'

从一个函数中返回另一个函数的应用:假设有一个对象数组,想要根据某个对象属性对数组进行排序,但传给 sort() 方法的比较函数要接收两个参数,即要比较的。我们需要一种方式来指明按照哪个属性来排序。我们可以定义一个函数它接收一个属性名,然后根据这个属性名来创建一个比较函数。默认情况下, sort 函数会调用每个对象的 toString() 方法以确定它们的次序。

function createCompare(property){
  return function(obj1,obj2){
    var value1=obj1[property],
        value2=obj2[property];   
    if(value1<value2) return -1;
    else if(value1>value2)  return 1;
    else return 0;
  }
}
var data=[{name:'aa',age:20},{name:'bb',age:12},{name:'cc',age:30}];
data.sort(createCompare("age"));// [{name:'bb',age:12},{name:'aa',age:20},{name:'bb',age:30}]

函数的内部属性

arguments :类数组对象,包含传入函数中所有参数。是每个函数自身的属性,之所以可以直接访问 arguments ,是因为命名空间??以下变化是为了加强JavaScript语言的安全性,这样第三方代码就不能在相同的环境下窥视其他代码了。

 callee 属性:是一个指针,指向拥有 arguments 对象的函数。严格模式访问会导致错误。

//一般阶乘函数
function factorial(num){
   if(num<=1){ return 1;}
   else {
     return num*factorial(num-1);
  }
}

定义阶乘函数用到递归算法,这样定义是没问题。
缺点:这个函数的执行与函数名 factorial 紧紧耦合在一起。万一出现改变函数指向的这种情况就不太好了,

factorial=function(){}
factorial(3);// undefiend

为了消除这种现象。

function factorial(num){   
   if(num<=1){     return 1;    }
   else{     
      return num*arguments.callee(num-1);    
  } 
}

这样无论引用函数使用的是什么名字都可以保证完成递归。

 caller 属性:不过在非严格模式下这个属性始终是 undefiend 。即使在严格模式下访问也会出错。增加这个属性是为了分清 arguments.caller 和函数对象上的 caller 属性。

function a(){
return Object.getOwnPropertyNames(arguments);
}
a();// ["length", "callee"]

this :行为与Java/C#中的 this 大致类似。 this 引用的是函数据以执行环境对象(当在网页的全局作用域中调用函数时, this 对象引用的就是 window )。

caller :不止是ECMAScript5中新增函数对象上的属性,还是 arguments 上的属性。保存着调用当前函数的函数的引用。如果是在全局作用域中调用当前函数,它的值为 null 。
Object.getOwnPropertyNames(Function);// [“length”, “name”, “arguments”, “caller”, “prototype”]

function outer(){
  inner();
}
function inner(){
  console.log(inner.caller); //为了实现更松散的耦合,arguments.callee.caller
}

outer();// function outer(){ inner()}

严格模式下不能为函数的 caller 属性赋值,否则会导致出错。

函数的属性和方法

length:表示函数希望接收的命名参数的个数(也就是定义的形参的个数)。

function sayName(name){
  //
}
function sum(num1,num2){
  //
}
function sayHi(){
 // 
}

sayName.length;// 1
sum.length;// 2
sayHi.length;// 0

prototype:对于ECMAScript中的引用类型而言,prototype是保存它们所有实例方法的真正所在。诸如toString和valueOf等方法实际上都保存在Object.prototype名下(原生构造函数比如Function,Array等 在自己原型上重写了toString)。在ECMAScript5中,prototype属性是不可枚举的,因此使用for-in无法发现。 Object.getOwnPropertyDescriptor(Function,’prototype’);//Object {writable: false, enumerable: false, configurable: false}

每个函数上有两个可用的方法:apply和call。这两个方法实际上是在Function.prototype上, Object.getOwnPropertyNames(Function.prototype);// [“length”, “name”, “arguments”, “caller”, “apply”, “bind”, “call”, “toString”, “constructor”] 它是在JavaScript引擎内部实现的。因为是属于Function.prototype,所以每个Function的实例都可以用(自定义的函数也是Function的实例)。都是在特定的作用域或自定义的上下文中调用执行函数,实际上等于设置函数体内 this 对象的值。

 apply :参数一为在其中运行函数的作用域,参数二为参数数组(可以是数组,也可以是 arguments 对象)。

function sum(num1,num2){
  return num1+num2;
}

function callSum1(num1,num2){
  return sum.apply(this,arguments);//sum.apply(this,[num1,num2])
}

callSum1(10,30);// 40

严格模式下,未指定环境对象而调用函数, this 值不会转型为 window 。除非明确把函数添加到某个对象或者调用 apply 或 call ,否则 this 值将是 undefined

 call :参数一没有变化,变化的是其余参数都是直接传递给函数,参数必须都列出来。

function callSum1(num1,num2){
  retrun sum.call(this,num1,num2);
}

callSum1(10,30);// 40

call 和 apply 真正强大的地方是能够扩充函数赖以运行的作用域,改变函数的执行环境。

 bind :ECMAScript5定义的方法,也是 Function.prototype 上的方法。用于控制函数的执行上下文,返回一个新函数,这个函数的 this 值会被绑定到传给 bind() 函数中的值。

window.color="red";
var o={color:'blue'};
function sayColor(){
  console.log(this.color);
}

var newobj=sayColor.bind(o);
newobj;// function sayColor(){
  console.log(this.color);
}
newobj==sayColor;// false
newobj();// blue

深入理解:可以将函数绑定到指定环境的函数。接收一个函数和一个环境,返回在给定环境中调用给定函数的函数。

function bind(func,context){
  return function(){
    func.apply(context,arguments);//这里创建了一个闭包,arguments使用的返回的函数的,而不是bind的
  }
}

当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数。

function bind(func,context,args){
   return function(){
      func.call(context,args);
   };
}

 toString,toLocaleString :返回函数代码的字符串形式,返回格式因浏览器而异,有的返回源码,有的返回函数代码的内部表示,由于存在差异,用这个也实现不了什么功能。

 valueOf :返回函数的自身引用。

变量,作用域,内存问题

JavaScript接近词法作用域,变量的作用域是在定义时决定而不是在执行时决定,也就是说词法作用域取决于源码。

JavaScript引擎在执行每个函数实例时,都会为其创建一个执行环境,执行环境中包含一个AO变量对象,用来保存内部变量表,内嵌函数表,父级引用列表等语法分析结构(变量提升在语法分析阶段就已经得到了,并保存在语法树中,函数实例执行时会将这些信息复制到AO上)。

ECMA-262定义,JavaScript松散类型的本质决定了它只在特定时间用于保存特定值的一个名字而已,由于不存在定义某个变量必须要保存何种数据类型值得规则,变量的值及其数据类型可在脚本的生命周期内改变。

基本类型和引用类型的值:ECMAScript变量可能包含两种不同数据类型的值:基本类型值,引用类型值。

基本类型值:简单的数据段。

引用类型值:那些可能由多个值构成的对象。是保存在内存中的对象,JavaScript不允许直接访问内存中的位置,也就说不能直接操作对象的内存空间。在操作对象时实际上是在操作对象的引用而不是实际的对象。为此,引用类型值是按引用访问的。(这种说法不严密,当复制保存着对象的某个变量时,操作的是对象的引用。但在为对象添加属性时,操作的是实际的对象)
在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。5种基本数据类型: Undefined,Null,Boolean,Number,String (很多语言中字符串以对象形式来表示因此被认为是引用类型,但ECMAScript放弃这一传统)。这5种基本类型是按值访问的,因此可以操作保存在变量中的实际的值。

动态的属性

复制变量的值:在从一个变量向另一个变量复制基本类型值和引用类型值时,也存在不同。
如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。
当从一个变量向另一个变量赋值引用类型值值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中,不同的是,这个值的副本实际上是个指针(可以理解为复制了地址值),而这个指针指向存储在堆中一个对象。复制操作结束后两个变量实际上将引用同一个对象。

传递参数:ECMAScript中所有函数的参数都是按值传递的,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。基本类型值得传递如同基本类型变量的复制一样,引用类型值的传递如同引用类型变量的复制一样。很多人错误认为:在局部作用域中修改的对象会在全局作用域中反映出来这就说明是按引用传递的。为了证明对象是按值传递的,

function setName(obj){
  obj.name="xx";
  obj=new Object();
  obj.name="bb";
}

var p=new Object();
setName(p);
p.name;// "xx"

如果是按引用传递的,即传递的不是地址值而是堆内存中整个p对象,在 setName 中为其添加了一个新名字叫 obj ,又给其添加 name 属性后,将这个 obj 内容重新填充为新对象,那么之前的那个对象就不存在了更别说有 ”xx” 的名字属性,但是 p.name 仍然访问到了。这说明即使在函数内部修改了参数值,但原始的引用仍然保持未变。实际上,当在函数内部重写 obj 时,这个变量引用的就是一个局部对象了,而这个局部对象会在函数执行完毕后被立即销毁。

类型检测:检测一个变量是不是基本数据类型用 typeof 是最佳工具,但如果变量的值是除了函数的对象或 null  typeof [];// ”object” typeof null;// ”object” ,变量值为函数时 typeoffunction(){};// ”function” (ECMA-262规定任何在内部实现 [[call]] 方法的对象都应该在应用 typeof 操作符返回 ”function” )。但在检测引用类型值时,这个操作符用处不大,因为我们并不是想知道它是个对象,而是想知道它是某种类型对象。如果变量是给定引用类型的实例, instanceof 操作符会返回 true 。所有引用类型值都是 Object 的实例。如果使用 instanceof 操作符检测基本类型的值,则该操作符始终会返回 false ,因为基本类型不是对象。

执行环境及作用域

执行环境(execution context):也称为作用域,定义了变量或函数有权访问的其他数据,决定了它们各自的行为。全局执行环境是最外围的一个执行环境,跟据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样,web浏览器中全局执行环境是 window 对象。某个执行环境中所有代码执行完毕后该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出例如关闭网页或浏览器时才被销毁)。每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中,在函数执行后,栈将其环境弹出,将控制权返回给之前的执行环境。ECMAScript程序中的执行流正是由这个机制控制着。函数的每次调用都会创建一个新的执行环境。执行环境分为创建和执行两个阶段,

创建:解析器初始化变量对象或者活动对象,它由定义在执行环境中的变量,函数声明,参数组成。在这个阶段,作用域链会被初始化,this的值也最终会被确定。

执行:代码被解释执行

变量对象(variable object):环境中定义的所有变量和函数都保存在这个对象中。虽然用代码无法访问它,但解析器在处理数据时会在后台使用它。如果这个环境是函数,则将活动对象(activation object)作变量对象

作用域(scope)和上下文(context):函数的每次调用都有与之紧密相关的作用域和上下文。作用域是基于函数的,上下文是基于对象的。作用域涉及到被调函数中变量的访问,上下文始终是 this 关键字的值,它是拥有当前所执行代码的对象的引用。上下文通常取决于函数是如何被调用的。

作用域链(scope chain):当代码在一个环境中执行时,会创建变量对象的一个作用域链。它是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端始终都是当前执行的代码所在环境的变量对象。活动对象在最开始时只包含一个变量即 arguments 对象(这个对象在全局环境中不存在),作用域链的下一个变量对象来自包含(外部)环境,再下一个变量对象则来自下一个包含环境,这样一直延续到全局执行环境。

var color = "blue";
function changeColor(){
  if(color=="blue"){
     color="red";
  }else{
     color="blue";
  }
}
changeColor();
console.log(color);// red

标识符解析是沿着作用域链一级一级地搜索标识符的过程,函数 changeColor 作用域链包含两个对象:它自己的变量对象(其中定义着 arguments 对象)和全局环境的变量对象。可以在函数内部访问到变量 color 就是因为可以在这个作用域链中找到它。内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境的任何变量和函数。函数参数也被当作变量来对待,因此其访问规则与执行环境中的其他变量相同。

延长作用域链:有些语句可以在作用域的前端临时添加一个变量对象,该变量对象会在代码执行后被移除。当执行流进入下列语句时,作用域链就会加长。

 try-catch 语句的 catch 块:对 catch 语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。<=IE8版本中,在 catch 语句中捕获的错误对象会被添加到执行环境的变量对象而不是 catch 语句的变量对象,换句话说,即使是在 catch 块的外部也可以访问到错误对象。

 with 语句:会将指定的对象添加到作用域链中。

function buildUrl(){
   var qs="?debug=true";
   with(location){
      var url=href+qs;
   }
  return url;
}
buildUrl();// "http://i.cnblogs.com/EditPosts.aspx?postid=5280805?debug=true"

with 语句接收的是一个 location 对象,因此其变量对象中就含有 location 对象的所有属性和方法,且这个变量对象被添加到了作用域链的最前端。当在 with 语句中引用变量 href (实际引用的是 location.href )可以在当前的执行环境中找到,当引用变量 qs 时,引用的则是在下一级执行环境中的变量。由于JavaScript中没有块级作用域,所以在函数内部可以访问 url 才能 return 成功,说明 url 并不是添加到 location 所在的变量对象中。
这两个语句都会在作用域的前端添加一个变量对象。

没有块级作用域:在其他类C的语言中,由花括号封闭的代码块都有自己的作用域(如果用ECMAScript的话来讲,就是他们自己的执行环境),因而支持根据条件来定义变量。如果是在C/C++/Java中, color 会在 if 语句执行完后被销毁,但在JavaScript中, if 语句中的变量声明会将变量添加到当前的执行环境中。

if(true){
  var color="red";
}

console.log(color);// red

声明变量:使用 var 声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境;在 with 语句中,最接近的环境是函数环境。如果初始化变量时没有使用 var 声明,该变量会自动被添加到全局环境。

查询标识符:当在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该标识符代表什么。搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部环境中找到了该标识符,搜索过程停止,变量就绪。如果在局部环境中未找到该变量名,则继续沿作用域链向上搜索。搜索过程将一直追溯到全局环境。如果局部环境存在同名标识符,就不会使用位于父环境中的标识符。

函数表达式

if(condition){
  function sayHi(){
      console.log("Hi");
  }
}else{
  function sayHi(){
      console.log("Yo");
  }
}

以上代码会在 condition 为 true 时使用 sayHi() 的定义,否则就使用另一个定义。实际上这在ECMAScript中属于无效语法,JavaScript引擎会尝试修正错误,将其转换为合理的状态。但问题是浏览器尝试修正的做法不一样。大多数浏览器会返回第二个声明。此种方式很危险,不应该出现你的代码中。在chrome中:

if(true){
  function sayHi(){
      console.log("Hi");
  }
}else{
  function sayHi(){
      console.log("Yo");
  }
}//function sayHi(){ 没有函数声明的变量提升??
      console.log("Hi");
  }
if(false){
  function say(){
      console.log("Hi");
  }
}else{
  function say(){
      console.log("Yo");
  }
}//function say(){
      console.log("Yo");
  }
console.log(sa);//undefined 能输出undefiend说明函数声明并没有提升而是进行的变量提升
if(false){
  function sa(){
      console.log("Hi");
  }
}else{
  function sa(){
      console.log("Yo");
  }
}//function sa(){
      console.log("Yo");
  }

修正:使用函数表达式,那就没什么问题了。

var sayHi;
if(condition){
  sayHi=function(){
     console.log("Hi");
  }
}else{
  sayHi=function(){
     console.log("Yo");
  }
}

递归

在严格模式下,不能通过脚本访问 arguments.callee 。不过可以使用命名函数表达式来完成相同结果。

var factorial=(function f(num){
    if(num<=1){
        return 1;
    }else{
        return num*f(num-1);
   } 
});

注意是用命名函数表达式,单单把命名函数赋值给 factorial 也可以,但是并不能通过f的名字访问

闭包

闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。之所以能够访问外部作用域的变量,是因为内部函数的作用域链中包含外部作用域。当一个函数被调用的时候,

创建一个执行环境(execution context)及相应的作用域链

使用 arguments 和其他命名参数的值来初始化活动对象(activation object),但在作用域链中,外部函数的活动对象始终始终处于第二位…直至作为作用域链终点的全局执行环境。

function compare(value1,value2){
   if(value1<value2){
      return -1;
   }else if(value1>value2){
      return 1;
   }else{
      return 0;
   }
}

var result=compare(5,10);

当调用 compare() 时,会创建一个包含 arguments , value1 , value2 的活动对象,全局执行环境的变量对象(包含 result 和 compare )在 compare() 执行环境的作用域链中处于第二位。

后台的每个执行环境都有一个表示变量的对象(变量对象),全局环境的变量对象始终存在,而像 compare() 函数这样的局部环境的变量对象,则只在函数执行过程中存在。在创建 compare() 函数时,会创建一个预先包含全局对象的作用域链,这个作用域链被保存在 compare 内部的 [[Scope]] 属性中。当调用 compare() 函数时,会为函数创建一个执行环境,然后通过复制函数的 [[Scope]] 属性中的对象构建起执行环境的作用域链。此后又有一个活动对象被创建并被推入执行环境作用域链的最前端。对于这个例子中, compare 函数的执行环境而言,其作用链包含两个变量对象:本地活动对象和全局变量对象。显然,作用域链的本质上是一个指向变量对象的指针列表,它只引用但不包含实际的变量对象。

无论什么时候在函数中访问一个变量,就会从作用域链中搜索具有相应名字的变量,一般来讲当函数执行完后,局部活动对象会被销毁,内存中仅保留着全局作用域(全局执行环境的变量对象)。但是闭包的情况又有所不同。在另一个函数内部定义的函数会将包含函数(外部函数)的活动对象添加到它的作用域链里,当外部函数执行完后其活动对象不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换句话说只是外部函数它自己的作用域链被销毁,但活动对象还存在内存中。直到内部函数被销毁后(例如在外部解除了对闭包即内部函数的引用: func=null; ,解除相当于是闭包仅是执行完后),外部函数的活动对象才会被销毁。

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过多使用闭包可能会导致内存占用过多,建议只在绝对必要再考虑使用。但有的优化后的JavaScript引擎如V8会尝试回收被闭包占用的内存。

闭包缺点:作用域链的这种配置机制引出了一个副作用即闭包只能取得包含函数中任何变量的最后一个值。因为闭包保存的是整个变量对象,而不是某个特殊的变量。

function createFunctions(){
  var result=new Array();
  for(var i=0;i<3;i++){
     result[i]=function(){
        return i;
     };
  }
  return result;
}
createFunctions()[0]();// 3
createFunctions()[1]();// 3
createFunctions()[2]();// 3

当执行 createFunctions 时,它的活动对象里有 arguments=[] , result=undefiend , i=undefiend ,执行完 createFunctions 后, result=[function(){return i},function(){return i},function(){return i}],i=3 ;当此时执行 result 数组时,访问到的i的值总是为3,因为沿着 function(){return i;} 的作用域链查找变量,在外层函数的活动对象上找到i总是为3。数组中每个函数的作用域链中都保存着 createFunctions 的活动对象,所以这些函数们引用的都是同一个活动对象,同一个变量i。

解决方案:要的就是当时执行时的变量i,那么当时把这个i临时保存一下就可以了,但是保存在哪呢?将i保存在 function(){return i;} 的活动对象中,怎么保存呢?传给 arguments 就好了,只传进来还不行

function createFunctions(){
  var result=new Array();
  for(var i=0;i<3;i++){
     result[i]=function(i){
        return i;
     };
  }
  return result;
}

createFunctions()[0]();// undefiend

因为访问i的时候先从自己所在函数的执行环境的活动对象搜索起,找到i发现 i=undefiend 有值就停止向上搜索了。问题就出在上一步中将i保存在活动对象中, result[i]=function(i){return i;} 这句的执行并没有给匿名函数传参,这只是表达式的赋值操作,又不是执行匿名函数。所以现在需要的就是通过某种方式去执行函数的操作把i的值当实参传进去,简单!在匿名函数外部加一层立即执行的匿名函数(这也增加了一层作用域了)。

function createFunctions(){
  var result=new Array();
  for(var i=0;i<3;i++){
     result[i]=(function(i){
         return function(){
              return i;
          }
       })(i);
   }
  return result;
}

createFunctins()[0]();// 0

this对象

this 对象是在运行时基于函数的执行环境绑定的:

全局函数中, this 等于 window

函数被作为某个对象的方法调用时, this 等于那个对象

匿名函数的执行环境具有全局性, this 指向 window

通过 call() 或 apply() 改变函数执行环境的情况下, this 就会指向其他对象。

由于闭包编写的方式不同, this 的表现:

var name="the window";

var obj={
   name:"the obj",
   getNameFunc:function(){
       //console.log(this==obj);
       return function(){
         console.log(this.name);
     }
  }
}

obj.getNameFunc()();// the window

obj.getNameFunc() 返回了一个新函数,然后在再全局环境中执行该函数。为什么匿名函数没有取得其包含作用域(外部作用域)的 this 对象呢?每个函数在被调用时,都会自动获得两个特殊的变量: this (创建作用域时获得)和 arguments (创建活动对象获得),内部函数在搜索这两个变量时,只会搜索到自己的活动对象为止,因此永远不可能直接访问外部函数的这两个变量。不过把外部函数作用域的 this 保存在一个闭包能够访问到的变量里就可以让闭包访问该对象了。

下面几种情况特殊的 this :

var name="the window";
var obj={
   name:"the obj",
   getName:function(){
       return this.name;
   }
};

obj.getName();// "the obj"
(obj.getName)();// "the obj"
(obj.getName=obj.getName)();// "the window"

第一个是直接调用,第二个是调用后立即执行的表达式,第三个是执行了一条赋值语句,然后再调用返回的结果,赋值语句的返回了一个函数,然后全局环境下调用这个函数,见下图

模仿块级作用域

function outputNumber(count){
  for(var i=0;i<count;i++){
     console.log(i);
  }
  var i;// 只变量提升,到后面执行代码步骤时候略过此
  console.log(i);
}

outputNumber(3);// 0 1 2 3

JavaScript不会告诉你是否多次声明了同一个变量,遇到这种情况,它只会对后续的声明视而不见(不过它会执行后续声明中的变量初始化)。

匿名函数可以用来模仿块级作用域(私有作用域),语法如下:

(function(){
  //这里是块级作用域
})();

以上代码定义并立即调用了一个匿名函数,将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。对于这种语法的理解:

var count=5;
outputNumbers(count);

这里初始化了变量 count 将其值设为5。但是这里的变量是没有必要的,因为可以把值直接传给函数  outputNumbers(5);  这样做之所以可行,是因为变量不过是值的另一种表现形式,因此用实际的值替换变量没有问题。

var someFunc=function(){
   //这里是块级作用域 
};
someFunc();

既然可以使用实际的值来取代变量 count ,那这里也用实际的值替换函数名。

function(){
  //这里是块级作用域
}();

然而会报错,是因为JavaScript将 function 关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号。但是函数表达式后面可以圆括号,这也就是为什么这样可以执行

var someFunc=function(){
 //这里是块级作用域
}();

要将函数声明转化为函数表达式,

(function(){
   //这里是块级作用域
})();
function outputNumber(count){
  (function(){
     for(var i=0;i<count;i++){
       console.log(i);
     }
  })();
   console.log(i);// 报错
}

outputNumber(3);// 0 1 2

在 for 循环外边加了一个私有作用域,在匿名函数中定义的任何变量都会在执行结束时被销毁。在私有作用域中访问变量 count ,是因为这个匿名函数是一个闭包,它能访问包含作用域的所有变量。这种技术经常在全局作用域中被用在函数外部从而限制向全局作用域中添加过多的变量和函数。这种做法还可以减少闭包占用内存问题,因为没有指向匿名函数的引用,只要函数执行完毕,就可以立即销毁其作用域链了。

私有变量

严格来讲,JavaScript中没有私有成员的概念,所有对象属性都是公有的。不过有私有变量的概念,任何在函数中定义的变量,都可认为是私有变量,因为不能在函数外部访问这些变量。私有变量包括函数的参数,局部变量,在函数内定义的其他函数。如果在函数内部创建一个闭包,那么闭包通过自己的作用域也可以访问这些变量。利用这一点创建用于访问私有变量的公有方法。

把有权访问私有变量和私有函数的方法叫特权方法(privileged method)。

两种在自定义对象上创建特权方法的方式

在构造函数中定义特权方法

function MyObject(){
   //私有变量和私有函数
   var privateVariable=10;
   function privateFunction(){
      return false;
  } 
  // 特权方法
  this.publicMethod=function(){
     privateVariable++;
     return privateFunction();
  };

}

new MyObject();

这个模式在构造函数内部定义了所有私有变量和函数,又继续创建了能够访问这些私有成员的特权方法。能在构造函数中定义特权方法是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。对这个例子而言,变量 privateVariable 和方法 privateFunction 只能通过特权方法 publicMethod 访问。在创建 MyObject 实例后除了使用 publicMethod() 这一途径外没任何办法可以直接访问私有变量和函数。
利用私有和特权成员,可以隐藏那些不应该被直接修改的数据

function Person(name){
  this.getName=function(){
     return name;
  };
  this.setName=function(value){
     name=value;
  };
}

var p1=new Person("aa");
p1.getName();// "aa"

var p2=new Person("bb");
p2.getName();// "bb"

p1.getName();// "aa"

以上方法定义两个特权方法,在 Person 构造函数外部没有任何办法直接访问 name ,由于这两个方法是在构造函数内部定义的,它们作为闭包能够通过作用域链访问 name 。私有变量 name 在每个 Person 实例都不相同,这么说吧,每次调用构造函数都会重新创建这两个方法, p1.getName 和 p2.getName 是不同的函数,虽然调用的是内存中同一个 Person 函数。但 new 构造新实例的步骤是:先创建新实例对象;再在该实例上调用 Person 函数初始化作用域及作用域链 this 等;再添加属性等。不管换成是

var o1={},o2={};
Person.call(o1,'aa');
Person.call(o2,'bb');
o1.getName();// "aa"

还是换成

function Person(obj,name){
  obj.getName=function(){
     return name;
  };
  obj.setName=function(value){
     name=value;
  };
}
var o1={},o2={};
Person(o1,"aa");
Person(o2,"bb");
o1.getName();// "aa"

都调用了两次 Person ,因为每次调用 Person 就会初始化 Person 的作用域,所以 p1.getName 和 p2.getName 所处的外围作用域是不一样的(之前还认为因为是调用了内存中同一个 Person ,以为 p1.getName 和 p2.getName 有同一个外围作用域,没考虑到每次调用函数实例都会重新初始化作用域)。
缺点:在构造函数中定义特权方法要求你必须使用构造函数模式来达到这个目的。构造函数模式的缺点是针对每个实例都会创建同一组新方法,使用静态私有变量来实现特权方法就可以避免这个问题。

 静态私有变量通过在私有作用域中定义私有变量和函数,也可以创建特权方法。基本模式如下:

(function(){
   //私有变量和私有函数
   var privateVariable=10;
   function privateFunction(){
       return false;
   }
   //构造函数
   MyObject=function(){};
   //公有/特权方法
   MyObject.prototype.publicMethod=function(){
       privateVariable++;
       return privateFunction();
  };
})();

这个模式创建了个私有作用域,并在其中封装了一个构造函数和相应方法。公有方法在原型上定义,这一点体现典型原型模式。注意到这个模式在定义构造函数时并没使用函数声明,而是使用函数表达式,因为函数声明只能创建局部函数,我们也没有在声明 MyObject 时使用 var 关键字,就是想让它成为一个全局变量,能够在私有作用域之外被访问。但严格模式下未经声明的变量赋值会导致出错。可以修改为

'use strict';
var MyObject;
(function(){
   //私有变量和私有函数
   var privateVariable=10;
   function privateFunction(){
       return false;
   }
   //构造函数
   MyObject=function(){};
   //公有/特权方法
   MyObject.prototype.publicMethod=function(){
       privateVariable++;
       return privateFunction();
  };
})();

其实我觉得不用立即执行的匿名函数也可以实现这种在私有作用域中定义私有变量函数的模式,只要把这些放在一个函数中就可以了,然后再执行这个函数。

function staticFunction(){
   //私有变量和私有函数
   var privateVariable=10;
   function privateFunction(){
       return false;
   }
   //构造函数
   MyObject=function(){};
   //公有/特权方法
   MyObject.prototype.publicMethod=function(){
       privateVariable++;
       return privateFunction();
  };
}

staticFunction();

—–分割线—-
这种模式在与构造函数中定义特权方法的主要区别就在于私有变量和函数是由实例共享的因为只调用了即只初始化了一次父环境(意思就是 p1.getName 和 p2.getName 所在的父环境都是同一个,不像构造函数模式中那样拥有各自父环境)。由于特权方法是在原型上定义的,因此所有实例都使用同一个函数。而这个特权方法作为一个闭包总是保存着对包含作用域的引用。

(function(){
   var name="";
   Person=function(value){
      name=value;
   };
   Person.prototype.getName=function(){
      return name;
   };
   Person.prototype.setName=function(value){
      name=value;
   };
})();

var p1=new Person("aa");
p1.getName();// "aa"
var p2=new Person("bb");
p2.getName();// "bb"
p1.getName();// "bb"

Person 构造函数和 getName 和 setName 都有权访问私有变量 name 。在这种模式下,变量 name 就成了一个静态的,由所有实例共享的属性。在一个实例上调用 setName() 会影响所有实例。
以这种模式创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变量。
多查找作用域链中的一个层次,就会在一定程度上影响查找速度,这正是使用闭包和私有变量的一个不足之处。

模块模式:前面的模式用于为自定义类型创建私有变量和特权方法。道格拉斯所说的模块模式则是为单例创建私有变量和特权方法,所谓单例(singleton)指的就是只有一个实例的对象,JavaScript是以对象字面量的方式来创建单例对象的。

var singleton={
  name:value,
  method:function(){
     //这里是方法的代码
  }
};

模块模式通过为单例添加私有变量和特权方法能够使其得到增强。语法如下:

var singleton=function(){
   //私有变量和私有函数
   var privateVariable=10;
   function privateFunction(){
       return false;
   }
   //特权/公有方法和属性
   return {
      publicProperty:true,
      publicMethod:function(){
          privateVariable++;
          return privateFunction();
      }
   } 
}();

这个模式使用了一个返回对象的匿名函数,将一个对象字面量作为函数返回。本质上这个对象字面量定义的是一个单例的公共接口。这种模式在需要对单例进行某些初始化同时又需要维护其私有变量时是非常有用的。

var application=function(){
   //私有变量和函数
   var components=new Array();
   //初始化
   components.push(new BaseComponent());
   //公共
   return {
      getComponentCount:function(){
          return components.length;
      },
      registerComponent:function(component){
          if(typeof component=="object"){
              components.push(component);
          }
      }
   }
}();

在web应用程序中,经常需要使用一个单例来管理应用程序级的信息。如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那就可以使用模块模式。这种模式创建的每个单例都是 Object 的实例。

增强的模块模式:如果想让单例是某种类型的实例,改进了模块模式,在返回对象之前加入对其增强的代码。同时还必须添加某些属性和方法对其加以增强。

var singleton=function(){
  //私有变量和私有函数
  var privateVariable=10;
  function privateFunction(){
    return false;
  }
  //创建对象
  var obj=new CustomType();
  //添加特权/公有属性和方法
  obj.publicProperty=true;
  obj.publicMethod=function(){
      privateVariable++;
      return privateFunction();
  }
  return obj;
}();
var application=function(){
  //私有变量和函数
  var components=new Array();
  //初始化
  components.push(new BaseComponent());
  //创建application的一个局部版本
  var app=new BaseComponent();
  app.getComponentCount=function(){
     return components.length;
  }; 
  app.registerComponent=function(component){
     if(typeof component=="object"){
         components.push(component);
     }
  };
  return app;
}();

闭包的作用总结:

使用闭包可以在JavaScript中模仿块级作用域
创建并立即调用一个函数,这样既可以执行其中代码,又不会在内存中留下对该函数的引用。结果就是函数内部的所有变量都会被立即销毁除非将某些变量赋值给了包含作用域中的变量

闭包还可用于在对象中创建私有变量
通过闭包来实现公有方法,通过公有方法可以访问在包含作用域中定义的变量。有权访问私有变量的公有方法叫特权方法。可以使用构造函数模式,原型模式来实现自定义类型的特权方法,使用模块模式,增强的模块模式实现单例的特权方法。

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/9546

为什么Nginx的性能要比Apache高很多?

为什么Nginx的性能要比Apache高很多? 这得益于Nginx使用了最新的epoll(Linux 2.6内核)和kqueue(freebsd)网络I/O模型,而Apache则使用的是传统的select模型。

目前Linux下能够承受高并发访问的Squid、Memcached都采用的是epoll网络I/O模型。

处理大量的连接的读写,Apache所采用的select网络I/O模型非常低效。

下面用一个比喻来解析Apache采用的select模型和Nginx采用的epoll模型进行之间的区别:

假设你在大学读书,住的宿舍楼有很多间房间,你的朋友要来找你。

select版宿管大妈就会带着你的朋友挨个房间去找,直到找到你为止。

而epoll版宿管大妈会先记下每位同学的房间号,

你的朋友来时,只需告诉你的朋友你住在哪个房间即可,不用亲自带着你的朋友满大楼找人。

如果来了10000个人,都要找自己住这栋楼的同学时,select版和epoll版宿管大妈,谁的效率更高,不言自明。

同理,在高并发服务器中,轮询I/O是最耗时间的操作之一,select和epoll的性能谁的性能更高,同样十分明了。

epoll - I/O event notification facility

在linux的网络编程中,很长的时间都在使用select来做事件触发。

在linux新的内核中,有了一种替换它的机制,就是epoll。

相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。

因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。

并且,在linux/posix_types.h头文件有这样的声明:

#define __FD_SETSIZE    1024

表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。

epoll的接口非常简单,一共就三个函数:

1. int epoll_create(int size);

创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。

这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。

需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,

是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,

而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,

第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fd到epfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:从epfd中删除一个fd;

第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};

events可以是以下几个宏的集合:

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

EPOLLOUT:表示对应的文件描述符可以写;

EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

EPOLLERR:表示对应的文件描述符发生错误;

EPOLLHUP:表示对应的文件描述符被挂断;

EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,

如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

等待事件的产生,类似于select()调用。

参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。

该函数返回需要处理的事件数目,如返回0表示已超时。

4、关于ET、LT两种工作模式:

可以得出这样的结论:

ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知了,大多因为这样;而LT模式是只要有数据没有处理就会一直通知下去的。

那么究竟如何来使用epoll呢?其实非常简单。

通过在包含一个头文件#include <sys/epoll.h> 以及几个简单的API将可以大大的提高你的网络服务器的支持人数。

首先通过create_epoll(int maxfds)来创建一个epoll的句柄,其中maxfds为你epoll所支持的最大句柄数。

这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。

在用完之后,记得用close()来关闭这个创建出来的epoll句柄。

之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。基本的语法为:

nfds = epoll_wait(kdpfd, events, maxevents, -1);

其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。

max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件范围,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则范围。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。

epoll_wait范围之后应该是一个循环,遍利所有的事件。

几乎所有的epoll程序都使用下面的框架:

for( ; ; )
    {
        nfds = epoll_wait(epfd,events,20,500);
        for(i=0;i<nfds;++i)
        {
            if(events[i].data.fd==listenfd) //有新的连接
            {
                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接
                ev.data.fd=connfd;
                ev.events=EPOLLIN|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中
            }
            else if( events[i].events&EPOLLIN ) //接收到数据,读socket
            {
                n = read(sockfd, line, MAXLINE)) < 0    //读
                ev.data.ptr = md;     //md为自定义类型,添加数据
                ev.events=EPOLLOUT|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓
            }
            else if(events[i].events&EPOLLOUT) //有数据待发送,写socket
            {
                struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取数据
                sockfd = md->fd;
                send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据
                ev.data.fd=sockfd;
                ev.events=EPOLLIN|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据
            }
            else
            {
                //其他的处理
            }
        }
    }

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/9548

沐鸣娱乐业务:Java EE使最佳类型的软件集群成为过去

上周发生的两起个人事件让我想起了Adam Bien在JavaOne上对我说的关于为一个给定的项目选择正确的技术的评论,沐鸣娱乐业务:可能是一个最佳软件集群的供应商堆栈。

第一个事件是我在TechTarget的执行编辑请求对J2EE的WhatIs.com定义进行补救。TechTarget是第三方的母公司,而WhatIs.com是TechTarget旗下的姐妹网站。包罗万象的WhatIs定义词典是TechTarget网站的流量生成器,但如果它们不定期更新,就会变得陈旧过时。J2EE定义显然属于这一类。如果您碰巧查看了现有的J2EE定义,请记住它已经被完全重写,并将在下个月底之前进行完整的更新,包括对新名称的更新:Java Platform, Enterprise Edition (Java EE)。

更新这个定义需要重新回顾这个规范的历史,这提醒了我Java生态系统在这些年中发生了多大的变化和发展。从脱离原始EJB规范的大转变到Java EE 5发布后发生的细微变化。令人惊讶的是,即将到来的规范包含了云支持、帮助创建基于SOAP和REST的web服务、合并批处理来帮助大数据应用程序,以及包含JCache来帮助标准化一个重要的、但往往性能不佳的面向性能的任务。

Java EE的更新定义的高跟鞋出现在一个晚上和一个软件开发朋友晚上花了大量的抱怨的麻木的作业他了他大部分的一天是在故障诊断应用程序,它使用iBATIS连接到后端,利用Spring执行依赖注入,并部署到WebSphere应用服务器上,创建WebSphere特定特性和标准Java EE开发的混合,同时使用Wicket和JQuery编写前端。沐鸣登录网站

放弃最好的软件集群

我非常清楚这个应用程序的架构师们在考虑什么,当他们把这些不同的元素混合在一起的时候。他们想要一个利用市场上所有最好的软件开发框架的项目。这是一个崇高的目标,但组合最佳解决方案背后的现实是,它们之间的交互可能会变得混乱,问题可能变得难以排除,而且从长期来看,很难找到能够维护解决方案的软件开发人员

这让我想起了我们在JavaOne上录制的一个小Adam Bien的声音字节。事实是,没有人在乎最好的品种。不管怎么说,在我的项目中不是这样。Bien说,“Java EE是最好的选择,因为它非常简单。Bien是正确的,在绝大多数情况下,Java EE已经足够好了,几乎不需要在它之上添加其他框架,或者在插入其他框架的同时删除一些框架。沐鸣首页

它证明了Java和Java EE在过去十年中已经走了多远。我并不看不起那些把包括Spring、Wicket、iBatis和一堆其他库在内的软件开发框架组合在一起的开发人员。在开发该应用程序时,企业Java还没有像现在这样成熟。这些年来Java EE的成熟程度令人印象深刻。最佳品种实现真的不再需要了。Java EE可以工作,对于大多数人来说,这已经足够好了。

你是Hadoop沐鸣总代平台专家吗?

通过这个大数据Hadoop测试,沐鸣总代平台你将能够修改你的Hadoop概念,检查你的大数据知识,为你提供信心,同时出现在Hadoop面试,以实现你的梦想,在印度和国外的大数据工作。通过Hadoop教程的测验,您还将深入学习大数据概念。沐鸣代理:

所以在我们开始测试之前,让我们先修正一下我们的大数据概念和关键的Hadoop特性,正是由于这些特性,大数据Hadoop通过各种各样的Hadoop角色迅速占领了市场,并极大地增加了Hadoop的工作和薪水。沐鸣登录平台

沐鸣:我们不能简单地“停止调用”Java的被弃用的方法

这是一个非常激烈的举动。Finalize就在Object类中定义,在Java层次结构的顶端,比公共变量更公开。但是finalize方法的语义与JVM臭名昭著的垃圾收集器绑定在一起。20年前设计API的人没有人会想到,在声明了finalize方法20年后,垃圾收集器的垃圾收集模式仍然是完全不可预测的;如果他们这么做了,他们一开始就不会对其进行编码。沐鸣:

finalize方法必须终止,因为依赖JVM的gc系统并随后将功能与POJO的终止绑定是基于字节码的恶意行为。因此,在不久的将来的版本中,将会在最终的方法上添加一个@deprecated注释。这到底是什么意思呢?在Java世界中,它的意义非常小。

不能简单地停止调用不推荐的Java方法

不能简单地停止调用不推荐的Java方法

隐藏Java方法弃用问题

在Java中有很多不推荐的方法,没有什么可以阻止您调用它们。当然,在您的Eclipse或NetBeans IDE的行号栏中可能会出现一个黄色的yield符号,警告您正在调用Java的一个不推荐使用的方法,但仅此而已。解决这个问题的一个简单方法是进入IDE的preferences窗口,告诉它不要报告对已弃用方法的调用。只要执行快速更改,也许还需要重新构建,那么所有不赞成使用的方法警告都将消失。

另一种方法是使用@SuppressWarnings(“deprecation”)注释来装饰您编写的代码。这是另一种消除弃用问题的简单方法。沐鸣注册

解决了Java方法的弃用问题

对于那些管理Jenkins构建的人,在编译器级别上,您可以使用-Xlint:all switch on编译器来阻止您在Gradle或Maven构建期间看到关于不推荐的方法调用的警告。确实,有很多方法可以处理废弃的方法;这些只是一些比较常见的

当然,有些人可能会认为正确的方法是停止调用不推荐的方法。也许在10年或15年前,当一个弃用警告让开发人员感觉时钟在滴答作响,当计时器耗尽时,他们的代码将不再工作。如果不赞成使用的方法被从Java SE api套件中删除,这是正确的,但它们永远不会被删除。

我仍然看到用户调用已弃用的java.util.Date()构造函数。我参与的上一个项目充满了对已弃用的urlencode .encode()的调用。当然,人们不应该调用Java的过时方法,但是考虑到它们可以工作的事实,再加上它们在被弃用15到20年后仍然作为API的一部分存在,开发人员只是继续使用它们。简单地告诉开发人员“跟上程序”显然是不够的。沐鸣娱乐怎么样?

沐鸣总代Ajax、JavaScript支持和CDI对齐排在JSF 2.3新增内容的首位.

很高兴看到上周JavaServer Faces的2.3版最终定稿。沐鸣总代

当JSF 2.0出现时,我迅速转向了基于组件的UI方法来进行软件开发,推广了这个框架并编写了许多关于JSF、Ajax和Facelets开发的文章。但在做了几个项目之后,我的热情减退了,我的倡导被沮丧所取代。我很快就跳出了这个潮流,但是话说回来,看到JSF 2.3发行版中的新内容,我又迫不及待地想跳回去。

CDI和JSF 2.3

对于最初的2.0版本,我最大的挫折之一是Java EE CDI规范和JSF使用的依赖注入之间缺乏一致性。我想这只是一个现实的JSF需要能够运行在一个web容器没有提供CDI支持强制JSF 2.0的原始版本提供它自己的一组注射托管bean,但它创建困惑然而,特别是考虑到2009年或2010年,开发人员难以掌握CDI本身。名称相似但行为不同的CDI和JSF注释的冲突造成了无尽的混乱。但是看看JSF 2.3中的新特性,就会知道CDI对齐最终实现了。对于精通使用上下文和依赖注入的组织来说,这将使JSF 2.3更容易被采用。沐鸣总代理

JSF 2.3中还有哪些新特性?

除了CDI对齐之外,JSF 2.3中的新特性还支持异步编程,使开发人员在没有内置JSF 2.0 Ajax支持的情况下开发应用程序时避免了页面加载时间过长。对Websocket的支持将使那些有幸从事允许使用它的项目的开发人员的UI呈现速度如闪电般快,并且附加的特性将在客户端JavaScript和JSF的服务器端组件模型之间架起桥梁。

新的JSF 2.3 API增强了对Ajax的支持

就JavaScript支持而言,新的JSF 2.3标记commandScript将HTML元素中引用的客户端JavaScript方法映射到服务器端相应的方法,触发交互的事件完全基于Ajax。这是一个非常受欢迎的新特性,此外,甚至在Ajax完成后还可以在客户端运行各种脚本调用。在使用帮助实现响应式设计的JavaScript框架时,这样做的需求似乎无处不在,因此开发人员想出了各种专门的方法来这样做。很高兴看到JSF 2.3缓解了这个JavaScript难题,并标准化了基于Ajax的方法。

阿扬Tijms有一个很棒的博客给什么似乎更了解什么是新的JSF 2.3比实际的规范文档本身,所以我强烈建议,如果你在市场为组件基地,与CID服务器端UI web框架,适用同Ajax和JavaScript。交付JSR-371的团队,包括spec负责人Edward Burns和Manfred Riem,做了一件令人钦佩的工作。沐鸣登录测速

沐鸣娱乐从JSF 2.3发布到Amazon AWS宕机,我们都在瞄准简单的目标

看到科技行业和主流媒体对2017年2月亚马逊S3宕机事件过于简单化,我非常恼火。烦恼转换到按键,产生两个评论文章的主题,其中一个评估信仰上的中断会影响用户放入他们的服务提供者,而第二个尝试过滤掉过去亚马逊噪音和移动媒体的方式大大简化问题。这两篇文章都在TSS、社交媒体和TechTarget的搜索网站上获得了一些有趣的反馈,TechTarget在twitter上积极地发布了这些故事:沐鸣娱乐

亚马逊S3的停电对AWS来说也是切尔诺贝利吗?

不要再重复这种假新闻了:输入错误并不是导致Amazon S3宕机的原因

我们不是在挑亚马逊的毛病

有些人说我是在挑亚马逊的毛病。事实上,最近在TheServerSide上发布的一些特性对每个人都有影响,所以我们并没有把它们单独挑出来。

每个人都喜欢挑剔JSF。该网站上周发表了一篇特性文章,断言web UI框架不应该是Java EE规范的一部分。有些人认为这篇文章只是对JSF的又一次抨击,但事实并非如此。将JSF从Java EE规范中移除实际上对JSF是有好处的,本文阐述了其中的原因。在这里,我们是JSF的拥护者,我对JSF的未来感到很兴奋。看看JSF 2.3中的新特性,我相信许多让开发人员烦恼的小问题都已经解决了,基于Java的web组件框架的未来是光明的。

我们必须从Java EE中删除web UI框架,如JSR-371和JSF

JSF 2.3有什么新特性?CDI对齐和与客户端技术的集成是重中之重

从过时的Java方法到RESTful web开发沐鸣首页

Oracle没有能力从Java SE中删除过时的方法,这也受到了批评。我们在大约100年前采访了Rod Johnson,讨论了Spring如何让方法过时成为开发者应该认真对待的事情。

Oracle什么时候开始从规范中清除Java不推荐的方法?

过度炒作的DevOps趋势也成为了目标,使用XML和JSON的RESTful web开发也是如此。我们甚至尝试了与Java EE竞争的每一个开发栈和框架,这确实是一大杯水。

DevOps和敏捷:驾驭软件开发的神奇独角兽

我的RESTful调用读取JSON,但编写XML。这是不是说我是个坏人?

忘记投身于软件。Java EE已经是最好的了。沐鸣注册平台官网

所以,也许我们确实对亚马逊有点挑剔,但他们并不是我们审查的唯一受害者。如果有人认为亚马逊是他们评论文章的唯一目标,那就大错特错了。

沐鸣平台网站如何在Apache Spark中执行流转换操作。

通过Apache Spark

转换操作教程,您将了解各种Apache沐鸣平台网站

火花流转换操作与示例被使用

使用Apache Spark的Spark专业人员

流的概念。您将学习像Spark这样的流操作

映射操作,平面映射操作,火花过滤操作,计数

操作,火花还原键操作,火花计数值操作

与示例和星火UpdateStateByKey操作与示例,将

在星火工作中帮助你。

Apache Spark流转换操作介绍

在开始学习Spark中的各种流操作之前,让我们修改一下Spark流的概念。

以下是星火工业中使用的各种最常见的流化转换功能:沐鸣平台网址

a map ()

Spark中的Map函数通过一个函数传递源DStream的每个元素,并返回一个新的DStream

火花地图()例子

val conf = new SparkConf().setMaster(“local[2]”) .setAppName(“MapOpTest”)

val ssc = new StreamingContext(conf, Seconds(1))

ssc。socketTextStream (“localhost”, 9999年)

val ans = words。map {word => (“hello”,word)} //将hello映射到每一行

ans.print ()

启动计算

awaittermination() //等待终止

}

b . flatMap ()

Spark中的FlatMap函数类似于Spark map函数,但是在沐鸣登陆地址

flatmap,输入项可以映射到0或更多的输出项。这

在spark中创建map和flatmap操作之间的差异。

火花FlatMap例子
= ssc。socketTextStream (“localhost”, 9999年)

val words = lines.flatMap(_。split(“)) //每一行都用空格分隔单词

val对=单词。map(word => (word, 1))

val wordCounts =对。reduceByKey (_ + _)

wordCounts.print ()

c . filter ()

的过滤器函数只选择的那些记录

func返回true并返回一个新的DStream的源DStream

这些记录。