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

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

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

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

1,函数作用域

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

  1. var v1 = 'v1';
  2.  
  3. var f1 = function() {
  4.   console.log(v1); // 输出v1
  5. };
  6. f1();
  7.  
  8. var f2 = function() {
  9.   var v1 = 'local';
  10.   console.log(v1); // 输出local
  11. };
  12. f2();

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

  1. var scope = 'global';
  2. var f = function() {
  3.   console.log(scope); // 输出undefined
  4.   var scope = 'f';
  5. }
  6. f();

  

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

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

  

  1. var scope = 'top';
  2.   var f1 = function() {
  3.   console.log(scope);
  4. };
  5.  
  6. f1(); // 输出top
  7.  
  8. var f2 = function() {
  9.   var scope = 'f2';
  10.   f1();
  11. };
  12. f2(); // 输出to

  

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

2,全局作用域

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

二、闭包

1、啥叫闭包

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

  1. var generateClosure = function() {
  2.   var count = 0;
  3.   var get = function() {
  4.     count ++;
  5.     return count;
  6.   };
  7.   return get;
  8. };
  9. var counter = generateClosure();
  10. console.log(counter()); // 输出1
  11. console.log(counter()); // 输出2
  12. console.log(counter()); // 输出3

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

  1. var generateClosure = function() {
  2.   var count = 0;
  3.   var get = function() {
  4.     count ++;
  5.     return count;
  6.   };
  7.   return get;
  8. };
  9. var counter1 = generateClosure();
  10. var counter2 = generateClosure();
  11. console.log(counter1()); // 输出1
  12. console.log(counter2()); // 输出1
  13. console.log(counter1()); // 输出2
  14. console.log(counter1()); // 输出3
  15. console.log(counter2()); // 输出2

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

2、闭包的用途

1)、嵌套的回调函数

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

  1. exports.add_user = function(user_info, callback) {
  2. varuid = parseInt(user_info['uid']);
  3. mongodb.open(function(err, db) {
  4. if(err) {callback(err); return;}
  5. db.collection('users', function(err, collection) {
  6. if(err) {callback(err); return;}
  7. collection.ensureIndex("uid", function(err) {
  8. if(err) {callback(err); return;}
  9. collection.ensureIndex("username", function(err) {
  10. if(err) {callback(err); return;}
  11. collection.findOne({uid: uid}, function(err) {
  12. if(err) {callback(err); return;}
  13. if(doc) {
  14. callback('occupied');
  15. } else{
  16. varuser = {
  17. uid: uid,
  18. user: user_info,
  19. };
  20. collection.insert(user, function(err) {
  21. callback(err);
  22. });
  23. }
  24. });
  25. });
  26. });
  27. });
  28. });
  29. };

  

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

2)、实现私有成员

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

  1. var generateClosure = function() {
  2.   var count = 0;
  3.   var get = function() {
  4.     count ++;
  5.     return count;
  6.   };
  7.   return get;
  8. };
  9. var counter = generateClosure();
  10. console.log(counter()); // 输出1
  11. console.log(counter()); // 输出2
  12. console.log(counter()); // 输出3

  

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

三、对象

1、对象的创建和访问

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

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

  1. var foo = {}; //也可以用这种 var foo = new Object() 来显式地创建一个对象。
  2.  
  3. foo.prop_1 = 'bar';
  4. foo.prop_2 = false;
  5. foo.prop_3 = function() {
  6.   return'hello world';
  7. }
  8. console.log(foo.prop_3());

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

  1. var foo = {};
  2. foo['prop1'] = 'bar';
  3. foo['prop2'] = false;
  4. foo['prop3'] = function() {
  5.   return'hello world';
  6. }

  

2、call 和 apply

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

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

  1. var someuser = {
  2.   name: 'byvoid',
  3.   display: function(words) {
  4.     console.log(this.name + ' says ' + words);
  5.   }
  6. };
  7.  
  8. var foo = {
  9.   name: 'foobar'
  10. };
  11. someuser.display.call(foo, 'hello'); // 输出foobar says hello

  3、原型

  1. functionPerson() {
  2. }
  3.  
  4. Person.prototype.name = 'BYVoid';
  5. Person.prototype.showName = function() {
  6.   console.log(this.name);
  7. };
  8.  
  9. varperson = newPerson();
  10. person.showName();

  

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

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

  1. function Foo() {
  2.   var innerVar = 'hello';
  3.   this.prop1 = 'BYVoid';
  4.   this.func1 = function(){
  5.     innerVar = '';
  6.   };
  7. }
  8.  
  9. Foo.prototype.prop2 = 'Carbo';
  10. Foo.prototype.func2 = function() {
  11.   console.log(this.prop2);
  12. };
  13.  
  14. var foo1 = newFoo();
  15. var foo2 = newFoo();
  16. console.log(foo1.func1 == foo2.func1); // 输出false
  17. 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属性,指向它对应的构造函数。

  1. functionFoo() {
  2. }
  3. Object.prototype.name = 'My Object';
  4. Foo.prototype.name = 'Bar';
  5.  
  6. var obj = new Object();
  7. var foo = newFoo();
  8.  
  9. console.log(obj.name); // 输出My Object
  10. console.log(foo.name); // 输出Bar
  11. console.log(foo.__proto__.name); // 输出Bar
  12. console.log(foo.__proto__.__proto__.name); // 输出My Object
  13. 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 中没有这样的函数。因此我们需要手动实现这样一个函数,一个简单的做法是复制对象的所有属性:

  1. //自己写的clone方法
  2. Object.prototype.clone = function() {
  3.   var newObj = {};
  4.   for(var i in this) {
  5.     newObj[i] = this[i];
  6.   }
  7.   return newObj;
  8. }
  9.  
  10. //定义一个obj对象
  11. var obj = {
  12.   name: 'byvoid',
  13.   likes: ['node']
  14. };
  15.  
  16. //clone一个对象
  17. var newObj = obj.clone();
  18. obj.likes.push('python');
  19.  
  20. console.log(obj.likes); // 输出[ 'node', 'python' ]
  21. console.log(newObj.likes); // 输出[ 'node', 'python' ]

  

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

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

  1. Object.prototype.clone = function() {
  2.   var newObj = {};
  3.   for(var i in this) {
  4.     if(typeof(this[i]) == 'object' || typeof(this[i]) == 'function') {
  5.       newObj[i] = this[i].clone();
  6.     } else{
  7.       newObj[i] = this[i];
  8.     }
  9.   }
  10.   return newObj;
  11. };
  12.  
  13. Array.prototype.clone = function() {
  14.   var newArray = [];
  15.   for(var i = 0; i < this.length; i++) {
  16.     if(typeof(this[i]) == 'object' || typeof(this[i]) == 'function') {
  17.       newArray[i] = this[i].clone();
  18.     } else{
  19.       newArray[i] = this[i];
  20.     }
  21.   }
  22.   return newArray;
  23. };
  24.  
  25. Function.prototype.clone = function() {
  26.   var that = this;
  27.   var newFunc = function() {
  28.     returnthat.apply(this, arguments);
  29.   };
  30.   for(var i in this) {
  31.     newFunc[i] = this[i];
  32.   }
  33.   returnnewFunc;
  34. };
  35.  
  36. var obj = {
  37.   name: 'byvoid',
  38.   likes: ['node'],
  39.   display: function() {
  40.     console.log(this.name);
  41.   },
  42. };
  43.  
  44. var newObj = obj.clone();
  45. newObj.likes.push('python');
  46. console.log(obj.likes); // 输出[ 'node' ]
  47. console.log(newObj.likes); // 输出[ 'node', 'python' ]
  48. 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. java算法 第七届 蓝桥杯B组(题+答案) 6.方格填数

    6.方格填数  (结果填空) 如下的10个格子 (如果显示有问题,也可以参看[图1.jpg]) 填入0~9的数字.要求:连续的两个数字不能相邻.(左右.上下.对角都算相邻) 一共有多少种可能的填数方案 ...

  2. C# Common Keyword II

    [C# Common Keyword II] 1.as 运算符用于在兼容的引用类型之间执行某些类型的转换. class csrefKeywordsOperators { class Base { pu ...

  3. 阻塞和唤醒线程——LockSupport功能简介及原理浅析

    目录 1.LockSupport功能简介 1.1 使用wait,notify阻塞唤醒线程 1.2 使用LockSupport阻塞唤醒线程 2. LockSupport的其他特色 2.1 可以先唤醒线程 ...

  4. 欲望都市游戏设计 背景图层和UI图层的设计

  5. Professional C# 6 and .NET Core 1.0 - Chapter 43 WebHooks and SignalR

    本文内容为转载,重新排版以供学习研究.如有侵权,请联系作者删除. 转载请注明本文出处:Professional C# 6 and .NET Core 1.0 - Chapter 43 WebHooks ...

  6. Item2的使用

    网址:http://wulfric.me/2015/08/iterm2/ 巧用 Command 键 按住⌘键: 可以拖拽选中的字符串: 点击 url:调用默认浏览器访问该网址: 点击文件:调用默认程序 ...

  7. 8-机器分配(hud4045-组合+第二类斯特林数)

    http://acm.hdu.edu.cn/showproblem.php?pid=4045 Machine schedulingTime Limit: 5000/2000 MS (Java/Othe ...

  8. redis 面试题2

    使用过Redis分布式锁么,它是什么回事? 先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放. 这时候对方会告诉你说你回答得不错,然后接着问如果在setnx之后执行 ...

  9. 利用SHELL脚本修改当前环境变量

    转自http://www.chinaunix.net/old_jh/7/21485.html 1.背景 ---- 在日常的工作中,为了设置一大批环境变量,我们通常编辑了一个shell程序,包含了多个的 ...

  10. CSS选择器种类及介绍

    首先说主都有哪些先择器 1.标签选择器(如:body,div,p,ul,li) 2.类选择器(如:class="head",class="head_logo") ...