对象是什么
从两个层次来理解。
(1)对象是单个实物的抽象。
一本书、一辆汽车、一个人都可以是对象,一个数据库、一张网页、一个与远程服务器的连接也可以是对象。当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程。
(2)对象是一个容器,封装了属性(property)和方法(method)。
属性是对象的状态,方法是对象的行为(完成某种任务)。比如,我们可以把动物抽象为animal对象,使用“属性”记录具体是那一种动物,使用“方法”表示动物的某种行为(奔跑、捕猎、休息等等)。
构造函数
面向对象编程的第一步,就是要生成对象。前面说过,对象是单个实物的抽象。通常需要一个模板,表示某一类实物的共同特征,然后对象根据这个模板生成。JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。
JavaScript 语言使用构造函数(constructor)作为对象的模板。所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。
构造函数就是一个普通的函数,但是有自己的特征和用法。
var Vehicle = function () {
this.price = 1000;
};
为了与普通函数区别,构造函数名字的第一个字母通常大写。构造函数的特点有两个。
- 函数体内部使用了this关键字,代表了所要生成的对象实例。
- 生成对象的时候,必须使用new命令。
new 命令
new命令的作用,就是执行构造函数,返回一个实例对象使用new命令时,根据需要,构造函数也可以接受参数。
var Vehicle = function (p) {
this.price = p;
};
var v = new Vehicle(500);
new 命令的原理
使用new命令时,它后面的函数依次执行下面的步骤。
- 创建一个空对象,作为将要返回的对象实例。
- 将这个空对象的原型,指向构造函数的prototype属性。
- 将这个空对象赋值给函数内部的this关键字。
- 开始执行构造函数内部的代码。
也就是说,构造函数内部,this指的是一个新生成的空对象,所有针对this的操作,都会发生在这个空对象上。构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即this对象),将其“构造”为需要的样子。
new.target
函数内部可以使用new.target属性。如果当前函数是new命令调用,new.target指向当前函数,否则为undefined。
function f() {
console.log(new.target === f);
}
f() // false
new f() // true
使用这个属性,可以判断函数调用的时候,是否使用new命令。
function f() {
if (!new.target) {
throw new Error('请使用 new 命令调用!');
}
f() // Uncaught Error: 请使用 new 命令调用!
Object.create() 创建实例对象
构造函数作为模板,可以生成实例对象。但是,有时拿不到构造函数,只能拿到一个现有的对象。我们希望以这个现有的对象作为模板,生成新的实例对象,可以使用Object.create()方法。
var person1 = {
name: '张三',
age: 38,
greeting: function() {
console.log('Hi! I\'m ' + this.name + '.');
}
};
var person2 = Object.create(person1);
person2.name // 张三
person2.greeting() // Hi! I'm 张三.
上面代码中,对象person1是person2的模板,后者继承了前者的属性和方法。
this关键字
this关键字是一个非常重要的语法点。this可以用在构造函数之中,表示实例对象。除此之外,this还可以用在别的场合。但不管是什么场合,this都有一个共同点:它总是返回一个对象。简单说,this就是属性或方法“当前”所在的对象。
this.property
上面代码中,this就代表property属性当前所在的对象。
下面是一个实际的例子。
var person = {
name: '张三',
describe: function () {
return '姓名:'+ this.name;
}
};
person.describe()
// "姓名:张三"
使用场合
this主要有以下几个使用场合。
(1)全局环境
全局环境使用this,它指的就是顶层对象window。
this === window // true
function f() {
console.log(this === window);
}
f() // true
(2)构造函数
构造函数中的this,指的是实例对象。
var Obj = function (p) {
this.p = p;
};
(3)对象的方法
如果对象的方法里面包含this,this的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this的指向。
但是,这条规则很不容易把握。请看下面的代码。
var obj ={
foo: function () {
console.log(this);
}
};
obj.foo() // obj
使用注意点
避免多层 this
由于this的指向是不确定的,所以切勿在函数中包含多层的this。
避免数组处理方法中的 this,数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。
避免回调函数中的 this,回调函数中的this往往会改变指向,最好避免使用。
对象的继承
面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript 语言的继承不通过 class,而是通过“原型对象”(prototype)实现。
构造函数的缺点
JavaScript 通过构造函数生成新对象,可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部。构造函数内部定义了多个属性,所有实例对象都会生成构造函数里的属性,这两个属性会定义在实例对象上面。通过构造函数为实例对象定义属性,虽然很方便,但是有一个缺点。同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。这个问题的解决方法,就是 JavaScript 的原型对象(prototype)。
prototype 属性的作用
JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。JavaScript 中,每个函数都有一个prototype属性,指向一个对象。
对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。原型对象的属性不是实例对象自身的属性。
关于原型实例对象
- 只要修改原型对象,变动就立刻会体现在所有实例对象上。
- 如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
- 当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法
function Animal(name) {
this.name = name;
}
Animal.prototype.color = 'white';
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');
cat1.color // 'white'
cat2.color // 'white'
原型链
JavaScript 规定,所有对象都有自己的原型对象(prototype)。由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOf和toString方法的原因,因为这是从Object.prototype继承的Object.prototype的原型是null。null没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null
读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
constructor 属性
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。
构造函数的实例对象自身没有constructor属性,该属性其实是读取原型链上面的P.prototype.constructor属性。
constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。另一方面,有了constructor属性,就可以从一个实例对象新建另一个实例。
function Constr() {}
var x = new Constr();
var y = new x.constructor();
y instanceof Constr // true
//instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。