JavaScript的一些认识

最近看了一下《nodejs开发指南》发现nodejs在某些特定的领域由他自己的长处,适合密集计算但是业务逻辑比较简单的场景,如果做网站还是选择php吧,呵呵,这本书我除了第5章《用nodejs开发web》没有看,其他章节都大概看完了,了解了nodejs的简单用法,感觉对我作用最大的还是附录A《javascript的高级特性》,这里的内容让我对js的高级特性有了深一步的认识,以下做个记录:

一、作用域

  和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 

源自:http://www.cnblogs.com/whoamme/p/3492467.html

时间: 2024-10-25 10:19:35

JavaScript的一些认识的相关文章

Javascript中call的使用

call 方法应用于:Function 对象调用一个对象的一个方法,以另一个对象替换当前对象.call([thisObj[,arg1[, arg2[,   [,.argN]]]]])参数:thisObj 可选项.将被用作当前对象的对象. arg1, arg2, , argN 可选项.将被传递方法参数序列. 说明:call 方法可以用来代替另一个对象调用一个方法.call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象.如果没有提供 thisObj 参数,那么 G

你不知道的javascript 之 &gt;&gt;

?远大于符号 在数学公式中,">>"代表远大于符号,表示一个数远大于另一个数,如76>>3,-2>>-99等.庞加莱与波莱尔1901年首先使用了它,很快被数学界所接受,沿用至今. 折叠编辑本段右移运算符 在许多计算机编程语言(例如:C语言.C++语言.Java语言.JavaScript语言.Pascal语言等)中,">>"代表右移运算符,就相当于"shr".该运算符为双目运算符,结合方向为从左到右,

初识JavaScript

JavaScript简介 JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型.它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HTML(标准通用标记语言下的一个应用)网页上使用,用来给HTML网页增加动态功能.因为JavaScript兼容于ECMA标准,因此也称为ECMAScript. ECMAScript,描述了该语javascript组成言的语法和基本对象. 文档对象模型(DOM),描述处理网页内容的方法

最全解析如何正确学习JavaScript指南,必看!

划重点 鉴于时不时,有同学私信问我:怎么学前端的问题.这里统一回复一下,如下次再遇到问我此问题同学,就直接把本文链接地址发给你了. "前端怎么学"应该因人而异,别人的方法未必适合自己.就说说我的学习方法吧:我把大部分时间放在学习js上了.因为这个js的学习曲线,先平后陡.项目实践和练习啥的,我不说了,主要说下工作之外的时间利用问题.我是怎么学的呢,看书,分析源码.个人这几天统计了一下,前端书籍目前看了50多本吧,大部分都是js的.市面上的书基本,差不多都看过. 第一个问题:看书有啥好处

JavaScript数据类型检测

一.JavaScript 数据类型 1.基本数据类型(6种) Undefined Null Boolean Number String Symbol (ES6新增) 2.引用数据类型: Object 二.数据类型检测 1. typeof 可以检测除null 外的基本类型.null 和所有对象的typeof都是"object", 不能用于检测用户自定义类型. 比如Date, RegExp, Array, DOM Element的类型都是"object". var s

JavaScript的进阶之路(二)函数简介,变量、作用域和内存问题

<h3>ECMAScript中函数不存在函数签名的概念,没有重载</h3><h3>无需指定返回值,可以在任何时候返回任何值.未指定返回值的函数,返回的是一个特殊的undefined值</h3> <script type="text/javascript"> function sayHi(){ console.log("Hi"); }; sayHi(); function sayName(name,age){

JavaScript 二进制的 AST

本文和大家分享的主要是javascript中二进制的 AST相关内容,一起来看看吧,希望对大家学习javascript有所帮助. 背景介绍 多年来,JavaScript 已经从最慢的脚本语言之一,从老爷车发展为兰博基尼,不管是通过 Web 浏览器还是其他环境.它都能够快到可以运行桌面.服务器.移动甚至嵌入式应用程序. 随着 JavaScript 的增长,应用程序的复杂程度和规模都越来越复杂.然而,二十年前,少数使用过 JavaScript 的网站也就加载几千字节的 JavaScript,许多网站

JavaScript 对象

JavaScript 中的所有事物都是对象:字符串.数值.数组.函数... 此外,JavaScript 允许自定义对象. JavaScript 对象 JavaScript 提供多个内建对象,比如 String.Date.Array 等等. 对象只是带有属性和方法的特殊数据类型. 建 JavaScript 对象 通过 JavaScript,您能够定义并创建自己的对象. 创建新对象有两种不同的方法: 定义并创建对象的实例 使用函数来定义对象,然后创建新的对象实例

实现一个函数clone,使JavaScript中的5种主要的数据类型(包括Number、String、Object、Array、Boolean)进行值复制

实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number.String.Object.Array.Boolean)进行值复制. 1 /** 对象克隆 2 * 支持基本数据类型及对象 3 * 递归方法 */ 4 function clone(obj) { 5 var o; 6 switch (typeof obj) { 7 case "undefined": 8 break; 9 case "string": o = obj + &q

javascript的优美与鸡肋

--总结来自:<javascript语言精粹> 任何语言都有其优美的地方和其鸡肋的地方.避归一些语言的糟粕,能相应的降低bug出现的几率. 优美处: 函数是头等对象 基于原型继承的动态对象 对象字面量和数组字面量 糟粕: 1. 全局变量 全局变量有三种表达方式: var声明:var foo = value; 添加属性到全局对象上,即添加到window上:window.foo = value; 未经声明的变量:foo = value; 虽然变量可以未经声明就使用,但是这会导致后期的很多的bug出