对象有属性和行为,对于属性,我们传入值进去;对于行为,我们传入方法进去。创建对象的方式有以下三种。
**
1. 方式一(通过Object类创建)
**
let obj = new Object();
obj.name = "ccc"; //添加属性
obj.age = 23;
obj.say = function(){ //添加方法(这里称为方法,而不是函数。因为函数是可以直接调用的,而方法必须通过对象来调用)
console.log("hi");
}
console.log(obj);
console.log(obj.name);
console.log(obj.age);
结果为
obj打印输出的就是它自己拥有的属性和方法,我们也能通过__proto__看见它的原型
这里打印属性很容易理解,我们再看看方法
obj.say();
console.log(obj.say);
console.log(obj.say());
第一句含义:执行对象的方法,所以会输出 hi
第二句含义:打印这个方法,并不是执行。所以输出的是这个函数
第三句含义:执行对象的方法(会输出hi),并输出它的返回值(由于这里没有写return xxx,所以返回值为undefined)。
如下所示
为证明第三句的说法,我们在对象里添加方法的返回值
obj.say = function(){
console.log("hi");
return "mytest";
}
console.log(obj.say());
此时输出为
所以我们的说法是正确的
2. 方式二(通过字面量的方式创建)
let obj = {};
3. 方式三
属性和值之间用冒号分开,属性和属性之间用逗号分开。
let obj = {
name: "ccc",
age: 12,
say: function(){
console.log("hi");
}
}
4. 工厂函数
工厂函数顾名思义,一种批量制造对象的函数。
实际上就是把创建对象的步骤封装在一个函数里,并且返回。
function getPerson(myName, myAge){
let obj = new Object();
obj.name = myName;
obj.age = myAge;
obj.say = function (){
console.log("hi");
}
return obj; // 返回创建的这个对象
}
let obj1 = getPerson("ccc", 23);
let obj2 = getPerson("hhh", 24);
通过这种方式,可以减少重复的代码,但是也有缺点:不同对象使用相同的方法却占着多个内存。我们继续测试证明。
console.log(obj1.name);
console.log(obj1.age);
obj1.say();
console.log(obj2.name);
console.log(obj2.age);
obj2.say();
输出结果为
判断方法是否相等
console.log(obj1.say() == obj2.say());
console.log(obj1.say() === obj2.say());
结果为
咦?为什么会是true?这里笔者犯了一个常见的错误,obj1.say()是执行这个方法输出"hi",obj2.say()也是执行这个方法输出"hi",那结果必然是相等的。
真正的判断方法是否相等应该如下所示。
console.log(obj1.say == obj2.say);
console.log(obj1.say === obj2.say);
可以看到,这两个输出一样的方法是不一样的,因为会开辟不同的内存空间存储这个方法。我们继续打印出这两个方法到底是什么样子的。
console.log(obj1.say);
console.log(obj2.say);
可以看到长得是一样的,但是存放地址不一样,就不相等哦!
5. 构造函数
构造函数是工厂函数的简写方式。构造函数一般首字母需要大写,且用关键字new构造实例对象
function Person(myName, myAge){
this.name = myName;
this.age = myAge;
this.say = fucntion(){
console.log("hi");
}
}
let obj1 = new Person("ccc", 23);
let obj2 = new Person("hhh", 24);
let obj3 = Person("ttt", 12);
console.log(obj1);
console.log(obj2);
console.log(obj3);
结果为
这是因为在new Person的时候,系统会自动添加如下代码
function Person(myName, myAge){
let obj = new Object(); //系统自动添加
let this = obj; //系统自动添加
obj.name = myName;
obj.age = myAge;
obj.say = function (){
console.log("hello");
}
return this; //系统自动添加
}
因为创建了一个对象并返回,所以obj1,obj2是有值的,而obj3只是简单的调用函数,所以没有返回值。
继续探寻this的使用方法
function Person(myName, myAge){
this.name = myName;
this.age = myAge;
this.say = function (){
console.log(this.name, this.age);
return "mysay";
}
}
let obj1 = new Person("ccc", 23);
console.log(obj1);
console.log(obj1.name);
console.log(obj1.age);
obj1.say();
console.log(obj1.say());
结果为
方法say中的this是:谁调用的这个say,那么this就是指的谁。这里是obj1调用的,于是obj1.say()得到的就是obj1.name和obj1.age的值。
6. 构造函数的性能优化(利用原型对象)
前面我们已经提到,对于同一个构造函数的相同方法,不同的对象会开辟不同的内存空间,但是明明是一样的方法,我们为什么要开辟不同的内存空间来占内存呢?所以我们要使用一种最好的性能优化方法——将共享方法放在构造函数的原型对象里
如下所示
function Person(myName, myAge){
this.name = myName;
this.age = myAge;
}
Person.prototype = {
say:function (){
console.log("hi");
}
}
let obj1 = new Person("ccc", 23);
let obj2 = new Person("hhh");
输出结果为
可以看到两个对象obj1和obj2都可以使用say方法,当它们找不到方法时,就会通过原型链去寻找方法,他们寻找的都是Person构造函数的原型对象的方法,所以这这两个对象的方法都是一样的。
特别注意的是,对于obj2对象创建的时候,我并没有给age传入参数,也可创建实例对象,它的值为undefined。同样的,不给name传入值也是可以的,也都会找到say方法。
如果有原型链不清楚的可以看我的这篇博客
7.拓展
思考如下代码的结果
function Person(myName, myAge){
this.name = myName;
this.age = myAge;
this.say = function (){
console.log("persay");
}
}
Person.prototype = {
say:function (){
console.log("hi");
}
}
let obj1 = new Person("ccc", 23);
let obj2 = new Person();
let obj3 = Person();
obj1.say = function (){
console.log("objsay");
}
console.log(obj1);
console.log(obj2);
console.log(obj3);
obj1.say();
obj2.say();
obj3.say();
对于第一个obj1.say方法,它自己重新更改过自己的行为,所以打印行为时,既不从构造函数里寻找,也不从构造寒素的原型对象里寻找,而是它自己修改的。
对于第二个obj2.say方法,自己没有重新更改方法,于是先从构造函数里寻找,发现有,那就用构造函数里的。
对于第三个obj,这是重点,因为创造这个对象的方法直接是Person(),所以是没有我们前面讲到的步骤let obj = new Object(); let this = obj; return this; 所以并没有返回值赋值给obj,于是obj的值是undefined,想要打印出undefined的say方法,那必然是会出错的。
结果如我们所说