封装是面向对象的第一个特征,也是最容易理解的一个特征。所谓封装就是将一些属性(方法)统一放到一个对象中,来完成事物的抽象。封装的关键点在于一个字装。
我们要表示一个人的信息,就要将这个人相关的属性和方法装到一个对象中。要表示一辆汽车的信息,就要把一辆汽车的属性和方法装到一个对象中。就像我们之前举的猪八戒的那个例子一样。
const zbj = {
name:'猪八戒',
age:28,
address:'高老庄',
sleep:function () {
console.log(`${this.name}睡着了~~~`);
}
};
封装的概念实在是简单不过。但是这样也存在一个问题,上例中直接通过对象字面量({})来创建对象。这种方式创建对象的方式好理解,也十分的方便,但是如果在创建大量对象时依然采用这种方式似乎不是特别的明智。
假设我们要创建10个表示人的信息的对象,它们的结构和猪八戒一样,都有name、age、address三个属性和sleep一个方法,只是name、age和address的值不相同,如果采用上边的方式创建,我们必须要把相同的代码写10次,可能是这样的:
const zbj = { name:'猪八戒', age:28, address:'高老庄', sleep:function () { console.log(`${this.name}睡着了~~~`); } };
const swk = { name:'孙悟空', age:18, address:'花果山', sleep:function () { console.log(`${this.name}睡着了~~~`); } };
const shs = { name:'沙和尚', age:38, address:'流沙河', sleep:function () { console.log(`${this.name}睡着了~~~`); } };
略……
上例中只创建了三个对象,但你会发现创建对象的过程中出现了大量的重复代码,并且这种创建对象的方式实在是太自由了,对象中有什么属性,没有什么属性完全没有限制,说不准某个对象的name属性可能被我写成了naem我都发现不了。
使用函数来创建对象
我们需要另外一种更加方便的创建对象的方式,遇到重复代码第一个想到的就是通过定义函数来避免重复的代码,由此我们可以使用这样一个函数来创建对象。
function createPerson(name, age, address){ return { name:name, age:age, address:address, sleep:function(){ console.log(`${this.name}睡着了~~~`) } }; }
const zbj =
createPerson
('猪八戒',28,'高老庄'); const swk =
createPerson
('孙悟空',18,'花果山'); const shs =
createPerson
('沙和尚',38,'流沙河');
怎么样,这样创建对象是不是简单的多了?
构造函数
上述方式中的函数可以用来创建对象,但它也不是我们通常会使用的方式。在JS中为我们提供了一种专门用来创建对象的函数叫做构造函数(constructor)。
定义构造函数和定义一个普通函数没有区别,都是使用function关键字,不同点在于普通函数的名字是小写字母开头,而构造函数是大写字母开头。
function MyClass(){}
上例中的函数就是一个构造函数,构造函数我们也可以称为类(Class),所以起名字时,将构造函数的名字命名为MyClass。
一个函数如果直接调用那么它就是一个普通函数,如果使用new关键字调用那么它就是一个构造函数。
普通函数:
MyClass()
构造函数:
new MyClass()
这一点真的是JS中非常让人烦恼的地方,这里的烦恼并不是指构造函数的使用,而是构造函数的理解上。对于初学者来说,这里会比较痛苦,两种函数的作用不同,但定义方式却没什么太大的区别,没办法,愣记吧!直接调用是普通函数,使用new调用是构造函数。
调用构造函数时,总会返回一个新的对象。
let mc = new MyClass();
上例中调用了构造函数,同时将其返回指赋值给了mc,现在mc指向的就是构造函数所创建的新的对象,当然现在对象中什么属性都没有,它是一个空的对象。在构造函数中,this关键字指向的是它新建的对象,所以可以通过this关键字来向新对象中添加属性,比如可以这样写。
function Person(name, age){
this.name = name;
this.age = age;
this.sleep = function(){
console.log(`${this.name}睡着了~~~`);
};
}
let p1 = new Person('孙悟空',18);
在构造函数中this表示的就是新建的对象,向this中添加属性就相当于向新建对象中添加属性。如此一来我们创建好了构造函数Person,并且可以通过Person来创建出许多的Person对象,调用时不要忘了加new。
一个构造函数也被称为一个类,也最好直接称其为类。如果一个对象是通过某个类所创建的,那么我们就说这个对象是这个类的实例,上例中p1就是Person类的实例。通过同一个类创建的对象,称为一类对象。可以通过instancof运算符来检查一个对象是否是一个类的实例:
function
Person
(name, age){ this.name = name; this.age = age; this.sleep = function(){ console.log(`${this.name}睡着了~~~`); }; } let p1 = new
Person
('孙悟空',18); let o = {}; console.log(p1 instanceof
Person
);
//true
console.log(o instanceof
Person
);
//false
上例中p1是有Person类所创建,所以使用instance检查是返回true,o不是通过Person所创建,使用instance检查时返回false。
原型
上例中的Person构造函数中含有两个属性和一个方法,两个属性是name和age,一个方法是sleep。在Person中通过this将属性和方法统一添加到了新的对象上。这样会带来一个问题,每创建一个新的对象,就会在新的对象上添加name、age还有sleep。这一点对于name和age来说不是问题,因为每个Person实例的name和age属性都是不同的。但是对于sleep却不是这样了,因为无论是孙悟空还是猪八戒,睡觉的方法都是一摸一样的。也就是我们完全可以让多个对象共用一个方法,而不需要为每一个对象都添加一个方法。
构造函数拥有一个属性叫做prototype,中文翻译过了叫做原型。它指向的是一个对象,所以我们称其为原型对象。一个类只有一个原型对象,一个类的原型对象可以被它的所有实例访问。原型对象就相当于是一个公共的区域由所有实例共享。
在创建类时,可以把一些公共的属性或方法统一设置在原型对象中,这样所有的实例都能使用这些属性或方法,并且可以避免对这些属性或方法重复创建。
function
Person
(name, age){ this.name = name; this.age = age; }
//将sleep方法添加到原型中,可以避免重复创建方法 Person
.prototype.sleep = function(){ console.log(`${this.name}睡着了~~~`); };
上例的创建方式和之前的执行效果是相同的,但是这种方式可以有效的避免重复创建方法对象。总之先记着,定义构造函数时,值不同的属性方法(像name和age)在函数中通过this设置。值相同的属性方法(像sleep)在函数外通过原型对象设置。