[20141227]编写高质量JS代码的68个有效方法(八)
Tips:
一般来说,由于原型属性指向的对象是所有实例共享的。所以不建议在原型指向的对象中存储共享数据。下面给一个简单的例子:
var Person = function(name){ this.name = name; }; Person.prototype = { children: [], addChild: function(childName){ this.children.push(childName); }, getChildren: function(){ return this.children; } }; var p1 = new Person('P1'); var p2 = new Person('P2'); p2.addChild('P2_C1'); console.log(p1.getChildren());
结果比较明显。p2的孩子成p1的了。标准做法是将children存储在实例对象中。
var Person = function(name){ this.name = name; this.children = []; }; Person.prototype = { addChild: function(childName){ this.children.push(childName); }, getChildren: function(){ return this.children; } };
Tips:
老规矩,看一个简单的示例:
var testObj = { a1: 0, fun1: function(){ function fun2(){ console.log(this.a1); } fun2(); } }; testObj.fun1();
为什么会这样呢?因为this变量是以不同的方式被绑定的。每个函数都有一个this变量的隐式绑定。this变量是隐式的绑定到最近的封闭函数。针对以上的问题,可以有集中方法来处理,参考如下:
//通过将this用变量self保存的方式实现 var testObj = { a1: 0, fun1: function(){ var self = this; function fun2(){ console.log(self.a1); } fun2(); } }; testObj.fun1(); //通过call方法指定接收者(也可以用apply) var testObj = { a1: 0, fun1: function(){ function fun2(){ console.log(this.a1); } fun2.call(this); } }; testObj.fun1(); //通过bind来实现 var testObj = { a1: 1, fun1: function(){ function fun2(){ console.log(this.a1); } fun2.bind(this)(); } }; testObj.fun1();
Tips:
JS中实现的继承:
var Animal = function(){ this.weight = 50; }; Animal.prototype.eat = function(){ console.log('eat food...'); }; var Dog = function(){ Animal.call(this); Dog.prototype = Object.create(Animal.prototype); }; var dog = new Dog(); console.log(dog.weight);
Tips:
由于JS中,属性都是key-value存储,那么同名的属性指向同样的地址,所以以下代码:
var Animal = function(){ this.weight = 50; this.id = ++Animal.nextId; }; Animal.nextId = 0; Animal.prototype.eat = function(){ console.log('eat food...'); }; var Dog = function(){ Animal.call(this); this.id = ++ Dog.nextId; Dog.prototype = Object.create(Animal.prototype); }; Dog.nextId = 0; var dog = new Dog(); console.log(dog.id);
两个类都试图给实例属性id写数据。
Tips:
扩展标注库使得其功能更强大是很有诱惑力的,但不幸的是它们的定义具有很多特殊的行为,所以很难写出正确的子类。
var ArrayEx = function(){ for(var i = 0, len = arguments.length; i<len ; i++){ this[i] = arguments[i]; } }; ArrayEx.prototype = Object.create(Array.prototype); var ar = new ArrayEx('1', '2'); console.log(ar.length) //猜猜结果是什么?
原因分析:length属性只对在内部标记为“真正的”数组对象才起作用。直接继承的对象并没有继承 Array的标记标签属性[[Class]]。测试如下:
var ar = new ArrayEx('1', '2'); console.log(Object.prototype.toString.call(ar)); //[object Object] console.log(Object.prototype.toString.call([])); //[object Array]
ECMAScript标准库中干掉大多数构造函数都有类似的问题。基于这个原因,最好避免继承一下的标准类: Array,Boolean,Date,Function,Number,RegExp或String。
要想实现类似的功能,可以采用属性委托的方式:
var ArrayEx = function(){ this.array = [] for(var i = 0, len = arguments.length; i<len ; i++){ this.array[i] = arguments[i]; } }; ArrayEx.prototype.forEach = function(f, thisArg){ if(typeof thisArg === 'undefined'){ thisArg = this; } this.array.forEach(f, thisArg); }; var ar = new ArrayEx('1sfdfsd', '2fdsfs'); ar.forEach(function(item, i){ console.log(item); });