笔记来自《Node.js开发指南》BYVoid编著

1、作用域

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

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

1.1、函数作用域

作用域是通过函数来定义的,在一个函数中定义的变量仅仅对这个函数内部可见,我们称为函数作用域。在函数中引用一个变量时,JavaScript会先搜索当前函数作用域,或者称为“局部作用域”,假设没有找到则搜索其上层作用域,一直到全局作用域。

var scope = 'global';

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

依照作用域搜索顺序,在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(); // 输出 top

1.2、全局作用域

在JavaScript中有一种特殊的对象称为全局对象。这个对象在Node.js相应的是global对象,在浏览器中相应的是window对象。

在不论什么地方隐式定义的变量都会定义在全局作用域中,即不通过var声明直接赋值的变量。而模块化编程的一个重要原则就是避免使用全局变量,所以我们在不论什么地方都不应该隐式定义变量。

2、闭包

闭包的严格定义是“由函数(环境)及其封闭的自由变量组成的集合体。

2.1、什么是闭包

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

闭包的特性:当一个函数返回它内部定义的一个函数时,就产生了一个闭包,闭包不但包含被返回的函数,还包含这个函数的定义环境。

2.2、闭包的用途


1.嵌套的回调函数



由于闭包机制的存在,即使外层函数已经运行完成,其作用域内申请的变量也不会释放,由于里层的函数还有可能引用到这些变量,这样就完美地实现了嵌套的异步回调。



2.实现私有成员



JavaScript的对象没有私有属性,也就是说对象的每个属性都是曝露给外部的。



JavaScript通过约定在全部私有属性前加上下划线(比如_myPrivateProp),表示这个属性是私有的,外部对象不应该直接读写它。能够通过闭包来实现。



如前面的样例,仅仅有调用counter()才干訪问到闭包内的count变量,并依照规则对其添加�1,除此之外决无可能用其它方式找到count变量。受到这个简单样例的启示,我们能够把一个对象用闭包封装起来,仅仅返回一个“訪问器”的对象,就可以实现对细节隐藏。关于实现JavaScript对象私有成员的很多其它信息,请參考这里

3、对象

JavaScript没有类,但JavaScript是面向对象的语言。JavaScript仅仅有对象,对象就是对象,不是类的实例。



JavaScript中的对象是基于原型的。

3.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());

1.使用关联数组訪问对象成员

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

在JavaScript中,使用句点运算符和关联数组引用是等价的,也就是说不论什么对象(包含this指针)都能够使用这两种模式。使用关联数组的优点是,在我们不知道对象的属性名称的时候,能够用变量来作为关联数组的索引。比如:

varsome_prop='prop2';

foo[some_prop]=false;

2.使用对象初始化器创建对象

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

使用初始化器时,对象属性名称是否加引號是可选的,除非属性名称中有空格或者其它可能造成歧义的字符,否则没有必要使用引號。

3.2、构造函数

function User(name, url) {
this.name = name;
this.url = url;
this.display = function() {
console.log(this.name);
}
} var someuser = new User('ichenxiaodao', 'http://blog.csdn.net/cdztop');
console.log(someuser.name);
console.log(someuser.url);
console.log(someuser.display());

3.3、上下文对象

在JavaScript中,上下文对象就是this指针,即被调用函数所处的环境。上下文对象的作用是在一个函数内部引用调用它的对象本身,JavaScript的不论什么函数都是被某个对象调用的,包含全局对象,所以this指针是一个很重要的东西。

var someuser = {
name: "ichenxiaodao",
func: function() {
console.log(this.name);
}
}; var foo = {
name: "foobar"
} someuser.func(); //输出ichenxiaodao foo.func = someuser.func;
foo.func(); //输出foobar name = "global";
func = someuser.func;
func(); //输出global

使用不同的引用来调用同一个函数时,this指针永远是这个引用所属的对象。在前面的章节中我们提到了JavaScript的函数作用域是静态的,也就是说一个函数的可见范围是在预编译的语法分析中就能够确定的,而上下文对象则能够看作是静态作用域的补充。

1.call和apply



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



JavaScript并没有严格的所谓“成员函数”的概念,函数与对象的所属关系在调用时才展现出来。灵活使用call和apply能够节省不少时间,在后面我们能够看到,call能够用于实现对象的继承。



call和apply的功能是一致的,两者细微的区别在于call以參数表来接受被调用函数的參数,而apply以数组来接受被调用函数的參数。call和apply的语法各自是:

func.call(thisArg[,arg1[,arg2[,...]]])

func.apply(thisArg[,argsArray])

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

someuser.display是被调用的函数,它通过call将上下文改变为foo对象,因此在函数体内訪问this.name时,实际上訪问的是foo.name,因而输出了foobar。

2.bind



使用bind方法来永久地绑定函数的上下文,使其不管被谁调用,上下文都是固定的。bind语法例如以下:

func.bind(thisArg[,arg1[,arg2[,...]]])

var someuser = {
name: 'ichenxiaodao',
func: function() {
console.log(this.name);
}
}; var foo = {
name: 'foobar'
}; foo.func = someuser.func;
foo.func(); //输出foobar // foo.func1使用了bind方法,将someuser作为this指针绑定到someuser.func,
// 调用foo.func1()时,this指针为someuser,所以输出结果是ichenxiaodao。
foo.func1 = someuser.func.bind(someuser);
foo.func1(); //输出ichenxiaodao func = someuser.func.bind(foo);
func(); //输出foobar func2 = func;
func2(); //输出foobar

3.使用bind绑定參数表

var person = {
name: 'ichenxiaodao',
says: function(act, obj) {
console.log(this.name + ' ' + act + ' ' + obj);
}
}; person.says('loves', 'xixi'); //输出ichenxiaodao loves xixi xiaodaoLoves = person.says.bind(person, 'loves');
xiaodaoLoves('you'); //输出ichenxiaodao loves you

xiaodaoLoves将this指针绑定到了person,并将第一个參数绑定到loves,之后在调用xiaodaoLoves的时候,仅仅需传入第三个參数。这个特性能够用于创建一个函数的“捷径”,之后我们能够通过这个“捷径”调用,以便在代码多处调用时省略反复输入同样的參数。

4.理解bind



3.4、原型

在JavaScript语言中,没有类的概念,对象由对象实例化。打个例如来说,基于类的语言中类就像一个模具,对象由这个模具浇注产生,而基于原型的语言中,原型就好像是一件艺术品的原件,我们通过一台100%精确的机器把这个原件复制出非常多份。

function Person() {};

Person.prototype.name = 'ichenxiaodao';
Person.prototype.showName = function() {
console.log(this.name);
}; var person = new Person();
person.showName();

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

•构造函数内定义的属性继承方式与原型不同,子对象须要显式调用父对象才干继承构造函数内定义的属性。

•构造函数内定义的不论什么属性,包含函数在内都会被反复创建,同一个构造函数产生的两个对象不共享实例。

•构造函数内定义的函数有执行时闭包的开销,由于构造函数内的局部变量对当中定义的函数来说也是可见的。



什么时候使用构造函数内定义来创建属性呢?

除非必须用构造函数闭包,否则尽量用原型定义成员函数,由于这样能够降低开销。

尽量在构造函数内定义一般成员,尤其是对象或数组,由于用原型定义的成员是多个实例共享的。

原型链

JavaScript中有两个特殊的对象:Object与Function,它们都是构造函数,用于生成对象。Object.prototype是全部对象的祖先,Function.prototype是全部函数的原型,包含构造函数。我把JavaScript中的对象分为三类,一类是用户创建的对象,一类是构造函数对象,一类是原型对象。用户创建的对象,即一般意义上用new语句显式构造的对象。构造函数对象指的是普通的构造函数,即通过new调用生成普通对象的函数。原型对象特指构造函数prototype属性指向的对象。这三类对象中每一类都有一个__proto__属性,它指向该对象的原型,从不论什么对象沿着它開始遍历都能够追溯到Object.prototype。构造函数对象有prototype属性,指向一个原型对象,通过该构造函数创建对象时,被创建对象的__proto__属性将会指向构造函数的prototype属性。原型对象有constructor属性,指向它相应的构造函数。

function Foo() {}

Object.prototype.name = 'My Object';
Foo.prototype.name = 'Bar'; var obj = new Object();
var foo = new Foo(); 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中,继承是依靠一套叫做原型链(prototypechain)的机制实现的。属性继承的本质就是一个对象能够訪问到它的原型链上不论什么一个原型对象的属性。

3.5、对象的复制

全部对象类型的变量都是指向对象的引用,两个变量之间赋值传递一个对象并不会对这个对象进行复制,而仅仅是传递引用。

Object.prototype.clone = function() {
var newObj = {};
for (var i in this) {
newObj[i] = this[i];
}
return newObj;
} var obj = {
name: "ichenxiaodao",
likes: ['node']
}; var newObj = obj.clone();
obj.likes.push('pomelo'); console.log(obj.likes); //输出['node', 'pomelo']
console.log(newObj.likes); //输出['node', 'pomelo']

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

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() {
return that.apply(this, arguments);
};
for (var i in this) {
newFunc[i] = this[i];
}
return newFunc;
}; var obj = {
name: 'ichenxiaodao',
likes: ['node'],
display: function() {
console.log(this.name);
}
}; var newObj = obj.clone();
newObj.likes.push('pemole');
console.log(obj.likes); //输出 ['node']
console.log(newObj.likes); //输出 ['node', 'pemole']
console.log(newObj.display == obj.display); // 输出 false

上面这个实现看起来非常完美,它不仅递归地复制了对象复杂的结构,还实现了函数的深拷贝。可是对于两个对象相互引用的对象,这样的方法无能为力。

文档信息

  • 最后改动时间:2014年06月22日 15:41

JavaScript【5】高级特性(作用域、闭包、对象)的更多相关文章

  1. Python的高级特性7:闭包和装饰器

    本节跟第三节关系密切,最好放在一起来看:python的高级特性3:神奇的__call__与返回函数 一.闭包:闭包不好解释,只能先看下面这个例子: In [23]: def outer(part1): ...

  2. JavaScript的函数和作用域闭包

    1. 函数 1.1 定义函数 function add(x, y){ return x + y; } 上述函数定义如下: 关键字function指出这是一个函数定义: add是函数的名称: (x, y ...

  3. javascript高级特性

    01_javascript相关内容02_函数_Arguments对象03_函数_变量的作用域04_函数_特殊函数05_闭包_作用域链&闭包06_闭包_循环中的闭包07_对象_定义普通对象08_ ...

  4. javascript高级特性(面向对象)

    javascript高级特性(面向对象): * 面向对象: * 面向对象和面向过程的区别: * 面向对象:人就是对象,年龄\性别就是属性,出生\上学\结婚就是方法. * 面向过程:人出生.上学.工作. ...

  5. 【译】学习JavaScript中提升、作用域、闭包的终极指南

    这似乎令人惊讶,但在我看来,理解JavaScript语言最重要和最基本的概念是理解执行上下文.通过正确学习它,你将很好地学习更多高级主题,如提升,作用域链和闭包.考虑到这一点,究竟什么是"执 ...

  6. JavaScript高级特性-创建对象的九种方式

    1. 对象字面量 通过这种方式创建对象极为简单,将属性名用引号括起来,再将属性名和属性值之间以冒号分隔,各属性名值对之后用逗号隔开,最后一个属性不用逗号隔开,所有的属性名值对用大括号括起来,像这样: ...

  7. 《JAVASCRIPT高级程序设计》闭包

    一.闭包的概念 闭包是JAVASCRIPT中最重要的概念之一,闭包是指有权访问另一个函数作用域中变量的函数:创建闭包常见的方式,就是在一个函数内部,创建另一个函数.以下的例子创建了一个闭包,加粗的两行 ...

  8. JavaScript高级特性-数组

    1. JavaScript中的数组 在C++.Java中,数组是一种高效的数据结构,随机访问性能特别好,但是局限性也特别明显,就是数组中存放的数据必须是同一类型的,而在JavaScript中,数组中的 ...

  9. 你不知道的JavaScript(上)作用域与闭包

    第一部分 作用域与闭包 第一章 作用域是什么 1.作用域 变量赋值操作会执行两个动作:首先编译器会在当前作用域中声明一个变量(如果之前没有声明过), 然后会在运行时引擎会在作用域中查找该变量,找到就会 ...

随机推荐

  1. Java简单文件传输 socket简单文件传输示例

    服务器端代码: import java.io.*; import java.net.*; /** * Created with IntelliJ IDEA. * User: HYY * Date: 1 ...

  2. C++的表驱动法

    目的:使用表驱动法,替换复杂的if/else和switch/case语句. 说明:JS 等其他语言也都支持的. 表驱动发示例:http://blog.csdn.net/zhouyulu/article ...

  3. ANDROID_MARS学习笔记_S01原始版_006_ListView

    一.代码1.xml(1)main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayo ...

  4. 基于msys2工具集,自编译gcc-6.2.0、Qt-5.6.1-1和Qt-4.8.7(有nuwen.net网站提供的脚本)

    好久没更新(其实大可不要经常更新吧),一直都是用Qt4,最近想着转向Qt5了,msys2是自带Qt的,但工具链经常会更新,依赖也较多,简便才方便,做了最后一组Qt工具的更新,如题,Qt-4.8.7作为 ...

  5. 匿名函数和Lambda表达式

    这个题目有点牵强,真不知道如何取一个比较中意的名称,写技术博客,我很少拘泥小节,但是注重细节,如果细节都出现问题了,那么这个博文也就失去了价值. 其实应该从委托说起,委托是C#中的一个重要的内容,记得 ...

  6. MVC——数据库增删改查(Razor)

    一.显示信息 .Models(模板) private MyDBDataContext _context = new MyDBDataContext(); //定义一个变量取出所有数据 public L ...

  7. WPF手写代码配置文件——单例

    public class SettingHelper { //WPF下配置文件路径 public static readonly string SettingFilePath = AppDomain. ...

  8. Win32下 Qt与Lua交互使用(三):在Lua脚本中connect Qt 对象

    话接上文.笔者为了方便使用Lua,自己编写了一个Lua的类.主要代码如下: QLua.h #ifndef QLUA_H #define QLUA_H // own #include "inc ...

  9. 【转】Cannot find -ltinfo when compiling android 4.0.3

    原文网址:http://stackoverflow.com/questions/9055005/cannot-find-ltinfo-when-compiling-android-4-0-3 up v ...

  10. sql - and - or

    sql - and SQL AND links together two or more conditional statements for increased filtering when run ...