声明:感谢Node.js开发指南,参考了他的附录部分。
一、作用域
//demo0:
if (true) { var a = 'Valve'; } console.log(a);
以上的代码片段将会输出Valve。
//demo1:
var a1 = 'Valve'; var foo1 = function() { console.log(a1); }; foo1(); var foo2 = function() { var a1 = 'DOTA2'; console.log(a1); } foo2();
显然,foo1的结果是Valve,foo2的结果是DOTA2,应该很容易理解。
//demo2:
var a1 = 'mercurial'; var foo = function() { console.log(a1); var a1 = 'git'; } foo();
此时,结果将会是 undefined 。因为在函数foo内部的a1将会覆盖函数外部的变量a1,js搜索作用域是按照从内到外的,而且当执行到console.log时,函数作用域内部的a1还尚未被初始化,所以会输出undefined。
//demo3:
var foo = function() { var a1 = 'foo'; (function() { var a1 = 'foo1'; (function() { console.log(a1); })(); })(); }; foo();
输出结果是foo1。这里我在最内层的console.log中打印a1,此时,因为最内层的作用域中没有a1的相关定义,所以会往上层作用域搜索,得到a1='foo1'。这里实际上有一个嵌套的作用域关系。
//demo4
var a1 = 'global'; var foo1 = function() { console.log(a1); } foo1(); var foo2 = function() { var a1 = 'locale'; foo1(); } foo2();
示例的输出结果都将会是'global'。因为foo2在执行时,调用foo1,foo1方法会从他自己的作用域开始搜索变量a1,最终在其父级作用域中找到a1,即a1 = 'global'。由此可以看出,foo2内部的foo1在执行时并没有去拿foo2作用域中的变量a1,所以说作用域的嵌套关系并不是在执行时确定的。
//demo5:
var closure = function() { var count = 0; return function() { count ++; return count; } }; var counter = closure(); console.log(counter()); console.log(counter()); console.log(counter());
最后的结果是1,2,3。这个demo中,closure是一个函数(其实他相当于一个类的构造函数),并且返回一个函数(这个被返回的函数加上 其定义环境 通俗上被称为闭包,)。在返回的函数中,引用了外部的count变量。在var counter = closure()这句代码之后,counter实际上就是一个函数,这样每次在counter()时,先将count自增然后打印出来。这里counter的函数内部并没有关于count的定义,所以在执行时会往上层作用域搜索,而他的上层作用域是closure函数,而不是counter()执行时所在的上层作用域。为什么 他的上层作用域是closure函数呢?因为第一 这是在定义的时候就已经确定好的函数作用域嵌套关系,更重要的是第二点,闭包的返回不但有函数而且还包含定义函数的上下文环境。这里上下文环境就是closure函数的内部作用域,所以能够拿到closure函数中的count变量。
//demo6:
var closure = function() { var count = 0; return function() { count ++; return count; } }; var counter1 = closure(); var counter2 = closure(); console.log(counter1()); //1 console.log(counter1()); //2 console.log(counter2()); //1 console.log(counter2()); //2 console.log(counter1()); //3
从结果可以看出,生成的闭包实例是各自独立的,他们内部引用的count变量分别属于各自不同的运行环境。我们可以这样理解,在闭包生成时,将原上下文环境做了一份拷贝副本,这样不同的闭包实例就有自己独立的运行环境了。
闭包目前来说有两大用处:第一是嵌套的回调函数,第二是隐藏对象的细节。
//demo7
$('#id0').animate({ left: '+50px' }, 1000, function() { $('#id1').animate({ left: '+50px' }, 1000, function() { $('#id2').animate({ left: '+50px' }, 1000, function() { alert('done'); }); }); });
js对象没有私有成员的概念。一般的编码规范中会要求类似_privateProp的形式来定义私有属性。但是这是一个非正式的约定,而且_privateProp仍然能够被访问到。
//demo8
var student = function(yourName, yourAge) { var name, age; name = yourName || ''; age = yourAge || 0; return { getName: function() { return name; }, getAge: function() { return age; }, setName: function(yourName) { name = yourName; }, setAge: function(yourAge) { age = yourAge; } } } var mamamiya = student('mamamiya', 23); mamamiya.getName(); mamamiya.getAge();
这里我封装了一个student类,并设置了两个属性name,age。这两个属性除了通过student对象的访问器方法访问之外,绝无其他的方法能够访问到。这里就实现了对部分属性的隐藏。
三、对象
//demo9
var foo = { 'a': 'baz', 'b': 'foz', 'c': function() { return 'hello js'; } };
我们还可以通过构造函数来创建对象。
//demo10
function user(name, uri) { this.name = name; this.uri = uri; this.show = function() { console.log(this.name); } }; var mamamiya = new user('mamamiya', 'http://qzone.qq.com/806717031'); mamamiya.show();
js中上下文对象就是this,他表示被调用函数所处的环境。他的作用就是在一个函数内部引用调用它自己。
//demo11
var user = { name: 'mamamiya', show: function(words) { console.log(this.name + ' says ' + words); } }; var foo = { name: 'baz' }; user.show.call(foo, 'hello');
这段代码的结果是baz says hello。这里通过call方法,改变了user.show方法的上下文环境,user.show方法在执行时,内部的this指向的是foo对象。
//demo12
var user = { name: 'mamamiya', func: function() { console.log(this.name); } }; var foo = { name: 'baz' }; foo.func = user.func; foo.func(); //baz foo.func1 = user.func.bind(user); foo.func1(); //mamamiya func = user.func.bind(foo); func(); //baz func2 = func; func2(); //baz
其实,bind还可以在绑定上下文时代用参数。
var user = { name: 'mamamiya', func: function() { console.log(this.name); } }; var foo = { name: 'baz' }; func = user.func.bind(foo); func(); //baz func2 = func.bind(user); func2(); //baz
我们这样来看,
func = user.func.bind(foo) ≈ function() { return user.func.call(foo); } func2 = func.bind(user) = function() { return func.call(user); }
//demo14
function Class() { var a = 'hello'; this.prop1 = 'git'; this.func1 = function() { a = ''; }; } Class.prototype.prop2 = 'Mercurial'; Class.prototype.func2 = function() { console.log(this.prop2); }; var class1 = new Class(); var class2 = new Class(); console.log(class1.func1 === class2.func1); //false console.log(class1.func2 === class2.func2); //true
//demo15
function foo() { } Object.prototype.name = 'My Object'; foo.prototype.name = 'baz'; var obj = new Object(); var foo = new foo(); console.log(obj.name); // My Object console.log(foo.name); // baz console.log(foo.__proto__.name); // baz console.log(foo.__proto__.__proto__.name); // My Object console.log(foo.__proto__.constructor.prototype.name); // baz
function ClassA() { } ClassA.prototype.color = "blue"; ClassA.prototype.sayColor = function () { alert(this.color); }; function ClassB() { }ClassB.prototype = new ClassA();
Object.prototype.makeCopy = funciton() { var newObj = {}; for (var i in this) { newObj[i] = this[i]; } return newObj; } var obj = { name: 'mamamiya', likes: ['js'] }; var newObj = obj.makeCopy(); obj.likes.push('python'); console.log(obj.likes); // ['js', 'python'] console.log(newObj.likes); // ['js', 'python']
Object.prototype.makeDeepCopy = function() { var newObj = {}; for (var i in this) { if (typeof(this[i]) === 'object' || typeof(this[i]) === 'function') { newObj[i] = this[i].makeDeepCopy(); } else { newObj[i] = this[i]; } } return newObj; }; Array.prototype.makeDeepCopy = function() { var newArray = []; for (var i = 0; i < this.length; i++) { if (typeof(this[i]) === 'object' || typeof(this[i]) === 'function') { newArray[i] = this[i].makeDeepCopy(); } else { newArray[i] = this[i]; } } return newArray; }; Function.prototype.makeDeepCopy = function() { var self = this; var newFunc = function() { return self.apply(this, arguments); } for (var i in this) { newFunc[i] = this[i]; } return newFunc; }; var obj = { name: 'mamamiya', likes: ['js'], show: function() { console.log(this.name); } }; var newObj = obj.makeDeepCopy(); newObj.likes.push('python'); console.log(obj.likes); // ['js'] console.log(newObj.likes); // ['js', 'python'] console.log(newObj.show == obj.show); // false
上面的demo中很好的实现了对象,函数,数组在做深拷贝的逻辑。在一般情况下都是比较好用的。但是有一种情况下,这种方法却无能为力。如下:
var obj1 = {
ref: null
};
var obj2 = {
ref: obj1
};
obj1.ref = obj2;
上面这段代码块的逻辑很简单,就是两个相互引用的对象。
当我们试图使用深拷贝来复制 obj1 和obj2 中的任何一个时,问题就出现了。因为深拷贝的做法是遇到对象就进行递归 复制,那么结果只能无限循环下去。对于这种情况,简单的递归已经无法解决,必须设计一 套图论算法,分析对象之间的依赖关系,建立一个拓扑结构图,然后分别依次复制每个顶点, 并重新构建它们之间的依赖关系。这已经超出了这里的讨论范围,而且在实际的工程操作中 几乎不会遇到这种需求,所以我们就不继续讨论了。