React.js学习笔记(13) 面向对象编程 + ( 构造函数
(1) 对象
- 对象是单个实物的抽象。
- 对象是一个容器,封装了属性(property)和方法(method),属性是对象的状态,方法是对象的行为(完成某种任务)。
(2) 构造函数
(一)JavaScript 语言的对象体系,是基于构造函数(constructor)和原型链(prototype)。
- 所谓”构造函数”,就是专门用来生成对象的函数。
- 构造函数提供模板,描述对象的基本结构。
- 一个构造函数,可以生成多个对象,这些对象都有相同的结构。
(二)构造函数的写法就是一个普通的函数,但是有自己的特征和用法。
- 为了与普通函数区别,构造函数名字的第一个字母通常大写。
- 构造函数的特点:
函数体内部使用了 this 关键字,代表了所要生成的对象实例。
生成对象的时候,必需用 new 命令,调用构造函数。
var Vehicle = function () { // 构造函数首字母要大写,来区分普通函数
'use strict'; // 严格模式,保证调用构造函数时,忘记加new命令,就会报错
this.price = 1000; // this代表所要生成的实例对象
};
var v = new Vehicle(); // new命令,执行构造函数Vehicle,返回一个实例对象,保存在变量v中。
// var v = new Vehicle; // new命令本身就可以执行构造函数,所以可以带括号,也可以不带括号。
v.price // 1000 // v继承了构造函数Vehicle中的price属性
------
上面代码中,Vehicle (交通工具的意思) 就是构造函数,它提供模板,用来生成实例对象。
为了与普通函数区别,构造函数名字的第一个字母通常大写。
------
上面代码通过new命令,让构造函数Vehicle生成一个实例对象,保存在变量v中。
这个新生成的实例对象,从构造函数Vehicle继承了price属性。
new命令执行时,构造函数内部的this,就代表了新生成的实例对象
this.price表示实例对象有一个price属性,值是1000。
(三)如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。
- 构造函数:new命令总是返回一个对象,要么是实例对象,要么是return语句指定的对象。
- 普通函数:return后跟一个对象,new命令会返回return指定的对象,return后跟的不是对象(如:return 'xxxxx';字符串),new命令返回一个空对象
var Vehicle = function () {
this.price = 1000;
return 1000; // 构造函数内部,return后面如果跟一个对象,就会返回return指定的对象
}; // 如果构造函数内部return后不是跟的对象,就会忽略这个return语句,返回this对象。
(new Vehicle()) === 1000
// false
上面代码中,构造函数Vehicle的return语句返回一个数值。
这时,new命令就会忽略这个return语句,返回“构造”后的this对象。
---------------------------------------------------------------------------------
var Vehicle = function (){
this.price = 1000;
return { price: 2000 };
};
(new Vehicle()).price
// 2000
上面代码中,构造函数Vehicle的return语句,返回的是一个新对象。new命令会返回这个对象,而不是this对象
(四)如果对普通函数(内部没有this关键字的函数)使用new命令,如果return后跟的不是一个对象,则会返回一个空对象。
(return 后跟的是对象,还是会返回return指定的对象)
function getMessage() {
return 'this is a message';
}
var msg = new getMessage(); // 普通函数使用new命令,会返回一个空对象
// new命令总是要返回一个对象,实例对象或者return指定的对象
msg // {}
typeof msg // "object"
上面代码中,getMessage是一个普通函数,返回一个字符串。对它使用new命令,会得到一个空对象。
这是因为new命令总是返回一个对象,要么是实例对象,要么是return语句指定的对象。
本例中,return语句返回的是字符串,所以new命令就忽略了该语句。
构造函数中return的不同情况
(3) new 命令
new命令的作用,就是执行构造函数,返回一个实例对象。
- 使用new命令时,根据需要,构造函数也可以接受参数
var Vehicle = function (p) { // 构造函数也可以接受参数。
this.price = p;
};
var v = new Vehicle(500);
-----------------------------------------------
new命令本身就可以执行构造函数,所以后面的构造函数可以带括号,也可以不带括号。下面两行代码是等价的。
var v = new Vehicle();
var v = new Vehicle;
- 如果忘了使用new命令,直接调用构造函数,构造函数就变成了普通函数,并不会生成实例对象。而且由于后面会说到的原因,this这时代表全局对象,将造成一些意想不到的结果。
var Vehicle = function (){
this.price = 1000;
};
var v = Vehicle();
v.price
// Uncaught TypeError: Cannot read property 'price' of undefined
price
// 1000
上面代码中,调用Vehicle构造函数时,忘了加new命令。
导致,price属性变成了全局变量,而变量v变成了undefined。
- 为了保证构造函数必须与new命令一起使用,在构造函数内部使用严格模式,即第一行加上use strict。
function Fubar(foo, bar){
'use strict'; // 严格模式中,函数内部的this不能指向全局对象,默认等于undefined
this._foo = foo;
this._bar = bar;
}
Fubar()
// TypeError: Cannot set property '_foo' of undefined
上面代码的Fubar为构造函数,use strict命令保证了该函数在严格模式下运行。
由于在严格模式中,函数内部的this不能指向全局对象,默认等于undefined,
导致不加new调用会报错(JavaScript 不允许对undefined添加属性)。
- 如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。
- 如果return语句返回的是一个跟this无关的新对象,new命令会返回这个新对象,而不是this对象。这一点需要特别引起注意。
- 如果对普通函数(内部没有this关键字的函数)使用new命令,return后如果跟的不是对象,则会返回一个空对象。
(4) new.target
函数内部可以使用new.target属性。如果当前函数是new命令调用,new.target指向当前函数,否则为undefined。
(5) Object.create() 创建实例对象
构造函数作为模板,可以生成实例对象。但是,有时只能拿到实例对象,而该对象根本就不是由构造函数生成的,这时可以使用Object.create()方法,直接以某个实例对象作为模板,生成一个新的实例对象。
var person1 = {
name: '张三',
age: 38,
greeting: function() {
console.log('Hi! I\'m ' + this.name + '.');
}
};
var person2 = Object.create(person1); // 以person1对象为模板,生成person2 对象,相当于复制
person2.name // 张三
person2.greeting() // Hi! I'm 张三.
上面代码中,对象person1是person2的模板,后者继承了前者的属性和方法。
prototype 对象
(1) 构造函数的缺点
同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。
- 下面的例子(重要):
function Cat(name, color) {
this.name = name;
this.color = color;
this.meow = function () {
console.log('喵喵');
};
}
var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');
cat1.meow === cat2.meow
// false
上面代码中,cat1和cat2是同一个构造函数的两个实例,它们都具有meow方法。
由于meow方法是生成在每个实例对象上面,所以两个实例就生成了两次。( 重要 )
也就是说,每新建一个实例,就会新建一个meow方法。( 重要 )
这既没有必要,又浪费系统资源,因为所有meow方法都是同样的行为,完全应该共享。
这个问题的解决方法,就是 JavaScript 的原型对象(prototype)。
(2) prototype 属性的作用
JavaScript 的每个对象都继承另一个对象,后者称为“原型”(prototype)对象。
- 一方面,任何一个对象,都可以充当其他对象的原型;
- 另一方面,由于原型对象也是对象,所以它也有自己的原型。
- null也可以充当原型,区别在于它没有自己的原型对象
- JavaScript 继承机制的设计就是,原型的所有属性和方法,都能被子对象共享。( 重要 )
- 每一个构造函数都有一个prototype属性,这个属性会在生成实例的时候,成为实例对象的原型对象。( 重要 )
- 原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
- 当实例对象本身没有某个属性或方法的时候,它会到构造函数的prototype属性指向的对象,去寻找该属性或方法。这就是原型对象的特殊之处。
- 如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
- 构造函数也是普通的函数, 所以实际上所有函数都有prototype属性。
总结:
原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
function Animal(name) {
this.name = name;
}
Animal.prototype.color = 'white';
// 每一个构造函数都有prototype属性,在生成实例的时候成为实例对象的原型对象
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');
cat1.color // 'white' // 实例对象继承了原型对象(prototype)的color属性
cat2.color // 'white'
上面代码中,构造函数Animal的prototype对象,就是实例对象cat1和cat2的原型对象。
原型对象上添加一个color属性,结果,实例对象都继承了该属性。
(3) 原型链
对象的属性和方法,有可能定义在自身,也有可能定义在它的原型对象。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype chain)。
- 如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。
- Object.prototype对象的原型对象是null对象,而null对象没有自己的原型。
- Object.getPrototypeOf(object)方法返回指定对象的原型
- “原型链”的作用是,读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
- 如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)
- 性能影响:在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
Object.getPrototypeOf(Object.prototype)
// null
Object.getPrototypeOf(object)方法返回指定对象的原型
(4) constructor 属性
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
- 由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。
- constructor属性的作用,是分辨原型对象到底属于哪个构造函数。
- 有了constructor属性,就可以从实例新建另一个实例。 ( 重要 )
- 通过name属性,可以从实例得到构造函数的名称。
function P() {}
var p = new P();
p.constructor // 实例对象继承了构造函数的prototype属性的constructor属性
// function P() {}
p.constructor === P.prototype.constructor // P.prototype.constructor指向prototype对象的构造函数
// true // 即 P.prototype.constructor 指向 大P
p.hasOwnProperty('constructor') // constructor不是实例对象上的属性
// false
----------------------------------------------------------------------------
function F() {};
var f = new F();
f.constructor === F // true
f.constructor === RegExp // false
上面代码表示,使用constructor属性,确定实例对象f的构造函数是F,而不是RegExp。
- 有了constructor属性,就可以从实例新建另一个实例。( 重要 )
function Constr() {}
var x = new Constr();
var y = new x.constructor(); // x.constructor == Constr(){} 相当于 var y = new Constr();
y instanceof Constr // true
上面代码中,x是构造函数Constr的实例,可以从x.constructor间接调用构造函数。
------------------------------------------------------------------------------------
这使得在实例方法中,调用自身的构造函数成为可能。
- 通过name属性,可以从实例得到构造函数的名称。
function Foo() {}
var f = new Foo();
f.constructor.name // "Foo"
prototype对象上constructor属性指向prototype所在的构造函数
(5) instanceof 运算符
instanceof运算符返回一个布尔值,表示某个对象是否为指定的构造函数的实例。
( instance是实例的意思 )
- instanceof运算符的左边是实例对象,右边是构造函数
var v = new Vehicle();
v instanceof Vehicle // true
// instanceof 返回布尔值,表示对象是否是构造函数的实例;
Object 对象与继承
(1) Object.getOwnPropertyNames()
- Object.getOwnPropertyNames方法返回一个数组,成员是对象本身的所有属性的键名,不包含继承的属性键名。
- Object.getOwnPropertyNames方法返回所有键名。可以枚举的(enumerable)和 不可以枚举的
- 只获取那些可以枚举的属性,使用Object.keys方法。
(2) Object.prototype.hasOwnProperty()-----自身属性
( in运算符不会区分该属性是对象自身的属性,还是继承的属性 )
对象实例的hasOwnProperty方法返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上。
- hasOwnProperty方法是JavaScript之中唯一一个处理对象属性时,不会遍历原型链的方法。
var name = {
'1':10,
'2':20
}
name.hasOwnProperty('1'); // true
// 因为hasOwnProperty是Object.prototype对象上的属性,所以被所有实例对象继承。
//实例对象可以直接使用hasOwnProperty
----------------------------
Date.hasOwnProperty('length') // true
Date.hasOwnProperty('toString') // false
(3) in 运算符和 for…in 循环
- in运算符返回一个布尔值,表示一个对象是否具有某个属性。它不区分该属性是对象自身的属性,还是继承的属性。
'length' in Date // true
'toString' in Date // true
- 为了在for...in循环中获得对象自身的属性,可以采用hasOwnProperty方法判断一下。
for ( var name in object ) {
if ( object.hasOwnProperty(name) ) {
/* loop code */
}
}