《Javascript面向对象精要》札记
《Javascript面向对象精要》笔记
刚读过《Javascript面向对象精要》这本书,在现有的知识体系里面有一些新鲜的认识,记录一下。
原始类型和引用类型
Javascript存在两种类型:原始类型和引用类型。原始类型包括String、Number、Boolean、Null、Undefined,引用类型保存对象,其本质是对象所在内存位置的引用。
原始类型的赋值或者给函数传参,实际上都是传递原始类型值的拷贝;
引用类型则是引用的拷贝。修改其中一个引用的话,其他引用也会受到影响。如果对象中的某个属性也是对象,在对象拷贝时就会引入深拷贝和浅拷贝的区别。
var a = {"name": "Hello"}; var b = a; a.name; // "Hello" b.name = "World"; a.name; // "World"
关于正则表达式的字面形式
Javascript允许使用字面形式定义正则表达式,与RegExp构造函数的区别在于,构造函数的参数涉及到转义。
var reg = /\d+/g; //等价于 var reg = new RegExp("\\d+", "g");
关于原始封装类型
String、Number和Boolean有原始封装类型,读取这些类型的值时,自动创建临时的原始封装类型的对象,使用后立即删除。
引用一个较为诡异的问题:
var name = "Nicholas"; name.last = "Zakas"; console.log(name.last); // undefined
这段代码实际的执行过程如下:
var name = "Nicholas"; var temp = new String(name); temp.last = "Zakas"; temp = null; var temp = new String(name); console.log(temp.last); // undefined temp = null;
所以在获取时,读取的都是临时变量,用后即焚。
关于函数重载
面向对象编程的一个重要特性就是函数重载。Javascript中的函数没有原型的概念,就更提不到重载。
function showMessage(message) { console.log("Parameter: " + message); } function showMessage() { console.log("No Parameter"); } showMessage("Hello"); // "No Parameter"
利用原始封装类型的概念帮助理解执行过程如下:
var showMessage = new Function("message", "console.log(\"Parameter: \" + message)"); showMessage = new Function("console.log(\"No Parameter\")"); showMessage("Hello"); // "No Parameter"
重载的本质在于参数列表的不同,可以通过判断参数的个数、类型等执行不同的处理过程,实现重载。
1)arguments对象——类似数组类型的对象,可循环遍历,可检查其length属性;
注意:arguments其实不是数组类型的对象,Array.isArray(arguments)永远返回false;使用arguments的弊端在于如果代码复杂或很长,容易漏掉代码中对于参数的处理。
2)鉴于以上提到的弊端,实际上检查命名参数是否未定义,或通过typeof/instanceof检查其类型的方法更常见
关于继承
Javascript通过原型链实现继承,每个对象都有一个prototype对象作为其原型对象,继承其属性和方法。for-in循环会遍历继承而来的属性,可通过hasOwnProperty检查是否为自有属性。
修改原型对象时,会导致所有的对象实例都受到影响,并且影响是实时的;通过构造函数继承的办法,可以实现继承自同一原型对象,但构造过程不同的对象实例的初始化。
function Rectangle(length, width) { this.length = length; this.width = width; } Rectangle.prototype.getArea = function() { return this.length * this.width; } Rectangle.prototype.toString = function() { return "[Rectangle " + this.length + " x " + this.width + "]"; } function Square(size) { this.length = size; this.width = size; } Square.prototype = Object.create(Rectangle.prototype, { constructor: { configurable: true, enumerable: true, value: Square, writable: true } }; Square.prototype.toString = function() { return "[Square " + this.length + " x " + this.width + "]"; } var rect = new Rectangle(5, 10); var square = new Square(6); console.log(rect.getArea()); // 50 console.log(square.getArea()); // 36 console.log(rect.toString()); // "[Rectangle 5x10]" console.log(square.toString()); // "[Square 6x6]" console.log(rect instanceof Rectangle); // true console.log(rect instanceof Object); // true console.log(square instanceof Square); // true console.log(square instanceof Rectangle); // true console.log(square instanceof Object); // true
私有成员
Javascript对象的属性都是public的,通过闭包实现属性的私有化。
1)模块模式
person对象被赋值为一个立即执行的函数,该函数返回一个包含name属性和getAge、growOlder方法的对象。由于Javascript的函数作用域限制,函数内的变量是无法被函数以外的上下文访问的,这就保证了只有函数内部可以访问age变量。实际上就是利用了闭包。
var person = (function(){ var age = 25; return { name: "Nicholas", getAge: function() { return age; }, growOlder: function() { age++; } }; }()); console.log(person.name); // "Nicholas" console.log(person.getAge()); // 25 person.age = 100; console.log(person.getAge()); // 25 person.growOlder(); console.log(person.getAge()); // 26
2)构造函数的私有成员
function Person(name){ var age = 25; this.name = name; this.getAge = function() { return age; }; this.growOlder = function() { age++; }; } var person = new Person("Nicholas"); console.log(person.name); // "Nicholas" console.log(person.getAge()); // 25 person.age = 100; console.log(person.getAge()); // 25 person.growOlder(); console.log(person.getAge()); // 26
3)以上二者结合
构造函数的私有成员示例中,虽然实现了成员的私有化,但是getAge和growOlder方法被对象实例独有,如果需要所有实例共享私有数据,可通过结合模块模式和构造函数来实现。
var Person = (function(){ var age = 25; function realPerson(name) { this.name = name; } realPerson.prototype.getAge = function() { return age; } realPerson.prototype.growOlder = function() { age++; } return realPerson; }()); var Nicholas = new Person("Nicholas"); var Greg = new Person("Greg"); console.log(Nicholas.name); // "Nicholas" console.log(Nicholas.getAge()); // 25 console.log(Greg.name); // "Nicholas" console.log(Greg.getAge()); // 25 Nicholas.growOlder(); console.log(Nicholas.getAge()); // 26 console.log(Greg.getAge()); // 26
作用域安全的构造函数
这个问题的出现来自于创建对象实例时,很容易漏写new关键字。构造函数也是函数,所以可以不用new操作符直接调用它们来改变this的值。在非严格模式下,this被强制指向全局对象;在严格模式下,构造函数抛出错误。解决这个问题,可在构造函数中进行检查。
function Person(name) { if(this instanceof Person) { this.name = name; } else { return new Person(name); } }