学习js时候,读到几篇不错的博客。
http://www.cnblogs.com/yexiaochai/p/3802681.html
一,作用域

和C、C++、Java 等常见语言不同,JavaScript 的作用域不是以花括号包围的块级作用域(block scope),这个特性经常被大多数人忽视,因而导致莫名其妙的错误。例如下面代码,在大多数类C 的语言中会出现变量未定义的错误,而在JavaScript 中却完全合法:

if(true) {
var somevar = 'value';
}
console.log(somevar); // 输出value

  这是因为JavaScript 的作用域完全是由函数来决定的,if、for 语句中的花括号不是独立的作用域。

1,函数作用域

不同于大多数类C 的语言,由一对花括号封闭的代码块就是一个作用域,JavaScript 的作用域是通过函数来定义的,在一个函数中定义的变量只对这个函数内部可见,我们称为函数作用域。在函数中引用一个变量时,JavaScript 会先搜索当前函数作用域,或者称为“局部作用域”,如果没有找到则搜索其上层作用域,一直到全局作用域。我们看一个简单的例子:

var v1 = 'v1'; 

var f1 = function() {
  console.log(v1); // 输出v1
};
f1(); var f2 = function() {
  var v1 = 'local';
  console.log(v1); // 输出local
};
f2();

  以上示例十分明了,JavaScript 的函数定义是可以嵌套的,每一层是一个作用域,变量搜索顺序是从内到外。下面这个例子可能就有些令人困惑:

var scope = 'global';
var f = function() {
  console.log(scope); // 输出undefined
  var scope = 'f';
}
f();

  

这是JavaScript 的一个特性,按照作用域搜索顺序,在console.log 函数访问 scope 变量时,JavaScript 会先搜索函数f 的作用域,恰巧在f 作用域里面搜索到scope 变量,所以上层作用域中定义的scope 就被屏蔽了,但执行到 console.log 语句时,scope 还没被定义,或者说初始化,所以得到的就是 undefined 值了。

  函数作用域的嵌套关系是定义时决定的,而不是调用时决定的,也就是说,JavaScript 的作用域是静态作用域,又叫词法作用域,这是因为作用域的嵌套关系可以在语法分析时确定,而不必等到运行时确定。下面的例子说明了这一切:

  

var scope = 'top';
  var f1 = function() {
  console.log(scope);
}; f1(); // 输出top var f2 = function() {
  var scope = 'f2';
  f1();
};
f2(); // 输出to

  

这个例子中,通过f2 调用的f1 在查找 scope 定义时,找到的是父作用域中定义的scope 变量,而不是 f2 中定义的 scope 变量。这说明了作用域的嵌套关系不是在调用时确定的,而是在定义时确定的。

2,全局作用域

在JavaScript 中有一种特殊的对象称为 全局对象。这个对象在Node.js 对应的是 global对象,在浏览器中对应的是 window 对象。由于全局对象的所有属性在任何地方都是可见的,所以这个对象又称为 全局作用域。全局作用域中的变量不论在什么函数中都可以被直接引用,而不必通过全局对象。
满足以下条件的变量属于全局作用域:
        在最外层定义的变量;
        全局对象的属性;
        任何地方隐式定义的变量(未定义直接赋值的变量)。
   需要格外注意的是第三点,在任何地方隐式定义的变量都会定义在全局作用域中,即不通过var 声明直接赋值的变量。这一点经常被人遗忘,而模块化编程的一个重要原则就是避免使用全局变量,所以我们在任何地方都不应该隐式定义变量。

二、闭包

1、啥叫闭包

闭包的严格定义是“由函数(环境)及其封闭的自由变量组成的集合体。”,通俗地讲,JavaScript 中每个的函数都是一个闭包

var generateClosure = function() {
  var count = 0;
  var get = function() {
    count ++;
    return count;
  };
  return get;
};
var counter = generateClosure();
console.log(counter()); // 输出1
console.log(counter()); // 输出2
console.log(counter()); // 输出3

  当一个函数返回它内部定义的一个函数时,就产生了一个闭包,闭包不但包括被返回的函数,还包括这个函数的定义环境。上面例子中,当函数generateClosure() 的内部函数get 被一个外部变量counter 引用时,counter 和generateClosure() 的局部变量就是一个闭包。

var generateClosure = function() {
  var count = 0;
  var get = function() {
    count ++;
    return count;
  };
  return get;
};
var counter1 = generateClosure();
var counter2 = generateClosure();
console.log(counter1()); // 输出1
console.log(counter2()); // 输出1
console.log(counter1()); // 输出2
console.log(counter1()); // 输出3
console.log(counter2()); // 输出2

  counter1 和 counter2 分别调用了 generate-Closure() 函数,生成了两个闭包的实例,它们内部引用的 count 变量分别属于各自的运行环境。我们可以理解为,在generateClosure() 返回get 函数时,私下将 get 可能引用到的 generateClosure() 函数的内部变量(也就是 count 变量)也返回了,并在内存中生成了一个副本,之后generateClosure() 返回的函数的两个实例counter1和counter2 就是相互独立的了。

2、闭包的用途

1)、嵌套的回调函数

如下代码是在Node.js 中使用MongoDB 实现一个简单的增加用户的功能:

exports.add_user = function(user_info, callback) {
varuid = parseInt(user_info['uid']);
mongodb.open(function(err, db) {
if(err) {callback(err); return;}
db.collection('users', function(err, collection) {
if(err) {callback(err); return;}
collection.ensureIndex("uid", function(err) {
if(err) {callback(err); return;}
collection.ensureIndex("username", function(err) {
if(err) {callback(err); return;}
collection.findOne({uid: uid}, function(err) {
if(err) {callback(err); return;}
if(doc) {
callback('occupied');
} else{
varuser = {
uid: uid,
user: user_info,
};
collection.insert(user, function(err) {
callback(err);
});
}
});
});
});
});
});
};

  

这段代码中用到了闭包的层层嵌套,每一层的嵌套都是一个回调函数。回调函数不会立即执行,而是等待相应请求处理完后由请求的函数回调。我们可以看到,在嵌套的每一层中都有对 callback 的引用,而且最里层还用到了外层定义的 uid 变量。由于闭包机制的存在,即使外层函数已经执行完毕,其作用域内申请的变量也不会释放,因为里层的函数还有可能引用到这些变量,这样就完美地实现了嵌套的异步回调。

2)、实现私有成员

  JavaScript 的对象没有私有属性,也就是说对象的每一个属性都是曝露给外部的。这样可能会有安全隐患,譬如对象的使用者直接修改了某个属性,导致对象内部数据的一致性受到破坏等。JavaScript通过约定在所有私有属性前加上下划线(例如_myPrivateProp),表示这个属性是私有的,外部对象不应该直接读写它。但这只是个非正式的约定,假设对象的使用者不这么做,有没有更严格的机制呢?答案是有的,通过闭包可以实现。

var generateClosure = function() {
  var count = 0;
  var get = function() {
    count ++;
    return count;
  };
  return get;
};
var counter = generateClosure();
console.log(counter()); // 输出1
console.log(counter()); // 输出2
console.log(counter()); // 输出3

  

只有调用counter() 才能访问到闭包内的 count 变量,并按照规则对其增加1,除此之外决无可能用其他方式找到count 变量。受到这个简单例子的启发,我们可以把一个对象用闭包封装起来,只返回一个“访问器”的对象,即可实现对细节隐藏

三、对象

1、对象的创建和访问

JavaScript 中的对象实际上就是一个由属性组成的关联数组,属性由名称和值组成,值的类型可以是任何数据类型,或者函数和其他对象

在JavaScript 中,你可以用以下方法创建一个简单的对象:

var foo = {}; //也可以用这种 var foo = new Object() 来显式地创建一个对象。

foo.prop_1 = 'bar';
foo.prop_2 = false;
foo.prop_3 = function() {
  return'hello world';
}
console.log(foo.prop_3());

  我们还可以用关联数组的模式来创建对象,以上代码修改为:

var foo = {};
foo['prop1'] = 'bar';
foo['prop2'] = false;
foo['prop3'] = function() {
  return'hello world';
}

  

2、call 和 apply

call 和 apply 的功能是以不同的对象作为上下文来调用某个函数。简而言之,就是允许一个对象去调用另一个对象的成员函数

call 和 apply 的功能是一致的,两者细微的差别在于 call 以参数表来接受被调用函数的参数,而 apply 以数组来接受被调用函数的参数。call 和 apply 的语法分别是:
func.call(thisArg[, arg1[, arg2[, ...]]]) 
func.apply(thisArg[, argsArray])

var someuser = {
  name: 'byvoid',
  display: function(words) {
    console.log(this.name + ' says ' + words);
  }
}; var foo = {
  name: 'foobar'
};
someuser.display.call(foo, 'hello'); // 输出foobar says hello

  3、原型

functionPerson() {
} Person.prototype.name = 'BYVoid';
Person.prototype.showName = function() {
  console.log(this.name);
}; varperson = newPerson();
person.showName();

  

上面这段代码使用了原型而不是构造函数初始化对象。这样做与直接在构造函数内定义属性有什么不同呢?

 构造函数内定义的属性继承方式与原型不同,子对象需要显式调用父对象才能继承构造函数内定义的属性。
 构造函数内定义的任何属性,包括函数在内都会被重复创建,同一个构造函数产生的两个对象不共享实例。
 构造函数内定义的函数有运行时闭包的开销,因为构造函数内的局部变量对其中定义的函数来说也是可见的。

function Foo() {
  var innerVar = 'hello';
  this.prop1 = 'BYVoid';
  this.func1 = function(){
    innerVar = '';
  };
} Foo.prototype.prop2 = 'Carbo';
Foo.prototype.func2 = function() {
  console.log(this.prop2);
}; var foo1 = newFoo();
var foo2 = newFoo();
console.log(foo1.func1 == foo2.func1); // 输出false
console.log(foo1.func2 == foo2.func2); // 输出true

  

那么我们什么时候使用原型,什么时候使用构造函数内定义来创建属性呢?

 除非必须用构造函数闭包,否则尽量用原型定义成员函数,因为这样可以减少开销。
 尽量在构造函数内定义一般成员,尤其是对象或数组,因为用原型定义的成员是多个实例共享的。

4、原型链

  JavaScript 中有两个特殊的对象:Object 与Function,它们都是构造函数,用于生成对象。Object.prototype 是所有对象的祖先,Function.prototype 是所有函数的原
型,包括构造函数,我把JavaScript 中的对象分为三类,一类是用户创建的对象,一类是构造函数对象,一类是原型对象。

  用户创建的对象,即一般意义上用new 语句显式构造的对象。构造函数对象指的是普通的构造函数,即通过 new 调用生成普通对象的函数。原型对象特指构造函数prototype 属性指向的对象。这三类对象中每一类都有一个__proto__ 属性,它指向该对象的原型,从任何对象沿着它开始遍历都可以追溯到 Object.prototype。构造函数对象有prototype 属性,指向一个原型对象,通过该构造函数创建对象时,被创建对象的 __proto__ 属性将会指向构造函数的 prototype 属性。原型对象有 constructor属性,指向它对应的构造函数。

functionFoo() {
}
Object.prototype.name = 'My Object';
Foo.prototype.name = 'Bar'; var obj = new Object();
var foo = newFoo(); console.log(obj.name); // 输出My Object
console.log(foo.name); // 输出Bar
console.log(foo.__proto__.name); // 输出Bar
console.log(foo.__proto__.__proto__.name); // 输出My Object
console.log(foo. __proto__.constructor.prototype.name); // 输出Bar

  

在JavaScript 中,继承是依靠一套叫做原型链(prototype chain)的机制实现的。属性继承的本质就是一个对象可以访问到它的原型链上任何一个原型对象的属性。例如上例的foo 对象,它拥有foo. __proto__ 和 foo. __proto__.__proto__ 所有属性的浅拷贝(只复制基本数据类型,不复制对象)。所以可以直接访问foo.constructor(来自foo.
__proto__,即Foo.prototype),foo.toString(来自foo. __proto__.__proto__,即Object.prototype)。

5、对象的复制

  JavaScript 和Java 一样都没有像C语言中一样的指针,所有对象类型的变量都是指向对象的引用,两个变量之间赋值传递一个对象并不会对这个对象进行复制,而只是传递引用,有些时候我们需要完整地复制一个对象,这该如何做呢?Java 语言中有 clone 方法可以实现对象复制,但JavaScript 中没有这样的函数。因此我们需要手动实现这样一个函数,一个简单的做法是复制对象的所有属性:

//自己写的clone方法
Object.prototype.clone = function() {
  var newObj = {};
  for(var i in this) {
    newObj[i] = this[i];
  }
  return newObj;
} //定义一个obj对象
var obj = {
  name: 'byvoid',
  likes: ['node']
}; //clone一个对象
var newObj = obj.clone();
obj.likes.push('python'); console.log(obj.likes); // 输出[ 'node', 'python' ]
console.log(newObj.likes); // 输出[ 'node', 'python' ]

  

上面的代码是一个对象浅拷贝shallow copy)的实现,即只复制基本类型的属性,而共享对象类型的属性。浅拷贝的问题是两个对象共享对象类型的属性,浅拷贝的问题是两个对象共享对象类型的属性,例如上例中 likes 属性指向的是同一个数组。

实现一个完全的复制,或深拷贝(deep copy)并不是一件容易的事,因为除了基本数据类型,还有多种不同的对象,对象内部还有复杂的结构,因此需要用递归的方式来实现:

Object.prototype.clone = function() {
  var newObj = {};
  for(var i in this) {
    if(typeof(this[i]) == 'object' || typeof(this[i]) == 'function') {
      newObj[i] = this[i].clone();
    } else{
      newObj[i] = this[i];
    }
  }
  return newObj;
}; Array.prototype.clone = function() {
  var newArray = [];
  for(var i = 0; i < this.length; i++) {
    if(typeof(this[i]) == 'object' || typeof(this[i]) == 'function') {
      newArray[i] = this[i].clone();
    } else{
      newArray[i] = this[i];
    }
  }
  return newArray;
}; Function.prototype.clone = function() {
  var that = this;
  var newFunc = function() {
    returnthat.apply(this, arguments);
  };
  for(var i in this) {
    newFunc[i] = this[i];
  }
  returnnewFunc;
}; var obj = {
  name: 'byvoid',
  likes: ['node'],
  display: function() {
    console.log(this.name);
  },
}; var newObj = obj.clone();
newObj.likes.push('python');
console.log(obj.likes); // 输出[ 'node' ]
console.log(newObj.likes); // 输出[ 'node', 'python' ]
console.log(newObj.display == obj.display); // 输出false

  

js中闭包和对象相关知识点的更多相关文章

  1. JS中this指向问题相关知识点及解析

    概括:this指向在函数定义的时候是无法确定的,只有在函数调用执行的时候才能确定this最终指向了谁,this最终指向的是调用它的对象(常见的说法,后面有小小的纠正): 例1: 图中的函数fn1其实是 ...

  2. 关于js中闭包的理解

    1.以前很不理解js中闭包的概念及使用,下面来看一下 function foo() { var a = 123; var b = 456; return function () { return a; ...

  3. JavaScript -- 时光流逝(五):js中的 Date 对象的方法

    JavaScript -- 知识点回顾篇(五):js中的 Date 对象的方法 Date 对象: 用于处理日期和时间. 1. Date对象的方法 <script type="text/ ...

  4. JavaScript -- 时光流逝(四):js中的 Math 对象的属性和方法

    JavaScript -- 知识点回顾篇(四):js中的 Math 对象的属性和方法 1. Math 对象的属性 (1) E :返回算术常量 e,即自然对数的底数(约等于2.718). (2) LN2 ...

  5. JavaScript -- 时光流逝(三):js中的 String 对象的方法

    JavaScript -- 知识点回顾篇(三):js中的 String 对象的方法 (1) anchor(): 创建 HTML 锚. <script type="text/javasc ...

  6. JS中的event 对象详解

    JS中的event 对象详解   JS的event对象 Event属性和方法:1. type:事件的类型,如onlick中的click:2. srcElement/target:事件源,就是发生事件的 ...

  7. js中两个对象的比较

    代码取自于underscore.js 1.8.3的isEqual函数. 做了一些小小的修改,主要是Function的比较修改. 自己也加了一些代码解读. <!DOCTYPE html> & ...

  8. MVC中处理Json和JS中处理Json对象

    MVC中处理Json和JS中处理Json对象 ASP.NET MVC 很好的封装了Json,本文介绍MVC中处理Json和JS中处理Json对象,并提供详细的示例代码供参考. MVC中已经很好的封装了 ...

  9. js中的json对象详细介绍

    JSON一种简单的数据格式,比xml更轻巧,在JavaScript中处理JSON数据不需要任何特殊的API或工具包,下面为大家详细介绍下js中的json对象, 1.JSON(JavaScript Ob ...

随机推荐

  1. CSS选择器的匹配规则

    css选择器是从右向左匹配的, 比如:.list a {color:blue;} 先解析到 a 标签,并将页面上所有 a 标签的字体颜色都按照 color:blue 进行渲染(蓝色),再解析到 .li ...

  2. word2012写论文之参考文献和图片

    每次写论文都感到word的“博大精深”啊,一个版本一个样,一些原来版本还有的功能,后来的版本就没有了,一些原来版本叫这个名字的功能,下一个版本就换了个名字,你也太考验你用户的智商和耐心了吧!即使同一份 ...

  3. 解剖Nginx·模块开发篇(4)模块开发中的命名规则和模块加载与运行流程

    1 命名规则 1.1 基本变量 基本变量有三个: ngx_module_t 类型的 ngx_http_foo_bar_module: ngx_command_t 类型的数组 ngx_http_foo_ ...

  4. Codeforces 678E 状压DP

    题意:有n位选手,已知n位选手之间两两获胜的概率,问主角(第一个选手)最终站在擂台上的概率是多少? 思路:一看数据范围肯定是状压DP,不过虽然是概率DP,但是需要倒着推:我们如果正着推式子的话,初始状 ...

  5. Activiti 整合的小插曲

    虽然是令人头痛的小插曲,真不令人省心.2年不用它又忘了怎么配,这次一定记录下来,呵呵哒. 1.下载及运行设计器 官网下载源码压缩包,解压后找到设计器目录:Activiti-activiti-5.22. ...

  6. MYSQL中GROUP BY的细节及SELECT语句顺序

    一.GROUP BY语句的细节 我们知道,在sql中,GROUP BY语句主要用来给数据分组,以便能对每个组进行聚集计算,但是GROUP BY也有一些限制需要知道: 1. GROUP BY字句可以包含 ...

  7. linux常用的一些命令行操作(ubuntu)

    软件安装 sudo apt-get install xxx 压缩和解压缩 1. *.tar 用 tar –xvf 解压 2. *.gz 用 gzip -d或者gunzip 解压 3. *.tar.gz ...

  8. Solidity oraclize解析Json格式数据

    solidity虽然不能解析json数据但是oraclize_query可以直接处理: pragma solidity ^; import "github.com/oraclize/ethe ...

  9. Openssl rsautl命令

    一.简介 rsautl指令能够使用RSA算法签名,验证身份,加密/解密数据 二.语法 openssl rsautl [-in file] [-out file] [-inkey file] [-pas ...

  10. libpcap编程实例

    #include <stdio.h> #include <stdlib.h> #include <pcap.h> #include <errno.h> ...