十二、JavaScript 对象
(一)什么是对象?
现实生活中:万物皆对象,对象是一个具体的事物,看得见摸得着的实物。例如,一本书、一辆汽车、一个人可以是“对象”,一个数据库、一张网页、一个与远程服务器的连接也可以是“对象”。
在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
对象是由属性和方法组成的。
- 属性:事物的特征,在对象中用属性来表示(常用名词)
- 方法:事物的行为,在对象中用方法来表示(常用动词)
(二)创建对象的三种方式
- 利用 new Object 创建对象
- 利用字面量创建对象
- 利用构造函数创建对象
1.new Object创建对象
语法
var 对象 = new Object();
对象.属性 = 值;
案例
<script>
let person = new Object();
person.name = "鸣人";
person.sex = "男";
person.age = 19;
person.skill = "影分身之术";
console.log(person);
</script>
2.字面量创建对象
对象字面量:就是花括号 { } 里面包含了表达这个具体事物(对象)的属性和方法 { } 里面采取键值对的形式表示
- 键:相当于属性名
- 值:相当于属性值,可以是任意类型的值(数字类型、字符串类型、布尔类型,函数类型等)
案例
<script>
var star = {
name: 'Tom',
age: 18,
sex: '男',
sayHi: function () {
alert('大家好啊~');
}
};
</script>
3.构造函数创建对象
构造函数 :是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 运算符一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
在 js 中,使用构造函数要时要注意以下两点:
- 构造函数用于创建某一类对象,其首字母要大写
- 构造函数要和 new 一起使用才有意义
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.sayHi = function() {
alert('我的名字叫:' + this.name + ',年龄:' + this.age + ',性别:' + this.sex);
}
}
var bigbai = new Person('大白', 100, '男');
var smallbai = new Person('小白', 21, '男');
console.log(bigbai.name);
console.log(smallbai.name);
注意
- 构造函数约定首字母大写。
- 函数内的属性和方法前面需要添加 this ,表示当前对象的属性和方法。
- 构造函数中不需要 return 返回结果。
- 当我们创建对象的时候,必须用new 来调用构造函数。
(三)遍历对象属性
for…in 语句用于对数组或者对象的属性进行循环操作。
其语法如下:
for (变量 in 对象名字) {
// 在此执行代码
}
语法中的变量是自定义的,它需要符合命名规范,通常我们会将这个变量写为 k 或者 key。
for (var k in obj) {
console.log(k);// 这里的 k 是属性名
console.log(obj[k]); // 这里的 obj[k] 是属性值
}
总结:
- 对象可以让代码结构更清晰
- 对象复杂数据类型Object。
- 本质:对象就是一组无序的相关属性和方法的集合。
- 构造函数泛指某一大类,比如苹果,不管是红色苹果还是绿色苹果,都统称为苹果。
- 对象实例特指一个事物,比如这个苹果。
- for…in 语句用于对对象的属性进行循环操作(遍历)。
(四)JavaScript 类-混合模式创建对象
1.prototype的概念
在函数中,它是一个引用,指向一个原型对象。
每个函数都会创建一个 prototype 属性,这个属性是一个对象,包含特定引用类型的实例共享的属性和方法。
2.prototype的用法
意思就是我们可以为new创建的实例对象动态添加成员变量,而无需在函数对象中定义所有的属性和方法。
function Person1(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
};
}
let person1 = new Person1("小明", 18, "学生");
person1.sayName();
let person2 = new Person1("小明", 18, "学生");
person2.sayName();
console.log(person1.name == person2.name); //true
console.log(person1.sayName == person2.sayName); //false
function Person2(name, age) {
this.name = name;
this.age = age;
}
Person2.prototype.job = "学生";
Person2.prototype.sayName = function () {
console.log(this.name);
};
let person3 = new Person2("小明", 18);
person3.sayName();
let person4 = new Person2("小明", 18);
person4.sayName();
console.log(person3.job == person4.job); //true
console.log(person3.name == person4.name); //true
console.log(person3.sayName == person4.sayName); //true
与构造函数模式不同,使用这种原型模式定义的属性和方法是由所有实例共享的。因此 person1 和 person2 访问的都是相同的属性和相同的 sayName() 函数。
(五)JavaScript 继承 —借助构造函数(及call方法)
继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码
在子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能
案例
<script>
//父类
function People(name, age) {
this.name = name;
this.age = age;
this.eat = function () {
console.log(this.age + "岁的" + this.name + "爱吃饭");
};
}
var people = new People("小绿", 18);
console.log(people);
people.eat();
//子类
function Worker(name, age, skill) {
//本来,this指向父构造函数的对象实例的,但是用了call 后, 里面的this,是把父里的this,修改为子里的this,借用父构造函数继承属性
People.call(this, name, age); // 此时this指向Worker
console.log(this);
this.skill = skill;
this.tianfu = function () {
console.log(this.age + "岁的" + this.name + "的天赋是" + this.skill);
};
}
var worker = new Worker("张三", 18, "打游戏");
console.log(worker);
worker.eat();
worker.tianfu();
</script>
(六)JavaScript 原型链
在下面实例中,People是一个基类,XiaoMing是一个继承自 People 的子类,XiaoMing.prototype 使用 Object.create(People.prototype) 来创建一个新对象,它继承了 People.prototype 的方法和属性,通过将 XiaoMing.prototype.constructor 设置为 XiaoMing,确保继承链上的构造函数正确。
<script>
//父类1
function People(name, age) {
this.name = name;
this.age = age;
this.sing = function () {
console.log(this.age + "岁的" + this.name + "正在唱歌");
};
}
People.prototype.say = function () {
console.log(this.age + "岁的" + this.name + "正在讲话");
};
//子类
function XiaoMing(name, age, gender) {
People.call(this, name, age);
this.gender = gender;
}
// 建立原型链,让 xiaoming 继承 People
XiaoMing.prototype = Object.create(People.prototype);
XiaoMing.prototype.constructor = XiaoMing;
/* 在JavaScript中,当你使用构造函数创建一个对象时,这个对象的constructor属性默认会指向创建该对象的构造函数。
然而,当你通过继承修改一个对象的原型时,这个constructor属性不会自动更新以反映新的构造函数。
因此,当你创建一个继承自另一个构造函数的新对象时,它的constructor属性可能仍然指向原始的构造函数。
例如,在上面的代码中,XiaoMing继承自People。
如果不手动设置XiaoMing.prototype.constructor,那么XiaoMing的实例的constructor属性将指向People,而不是XiaoMing。
这行代码的目的是将XiaoMing的原型对象的constructor属性设置为XiaoMing,这样当你查看一个XiaoMing实例的constructor属性时,它将正确地指向XiaoMing函数。
*/
XiaoMing.prototype.run = function () {
console.log(this.age + "岁的" + this.name + "正在跑步");
};
var xiaoming = new XiaoMing("小明", 18, "男");
xiaoming.run();//18岁的小明正在跑步
xiaoming.sing();//18岁的小明正在唱歌
xiaoming.say();//18岁的小明正在讲话
</script>
(七)继承和原型链结合一起实现完美的组合继承
组合继承既具有原型链继承能够复用函数的特性,又有借用构造函数方式能够保证每个子类实例能够拥有自己的属性以及向超类传参的特性
<script>
function Parent(name, age) {
this.name = name;
this.age = age;
}
Parent.prototype.sayName = function () {
console.log("我的名字是" + this.name + "今年" + this.age);
};
var p1 = new Parent("小王", 18);
p1.sayName();
console.log(p1);
function Child(name, age, aihao) {
Parent.call(this, name, age); // 继承属性
this.aihao = aihao;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.sayAiHao = function () {
console.log(
"我叫" + this.name + "我今年" + this.age + "我的爱好是" + this.aihao
);
};
var p2 = new Child("小李", 19, "打篮球");
console.log(p2);
p2.sayAiHao();
p2.sayName();
</script>
-
组合继承优点
组合继承既具有原型链继承能够复用函数的特性,又有借用构造函数方式能够保证每个子类实例能够拥有自己的属性以及向超类传参的特性
-
组合继承缺点
- 父类构造函数的重复调用执行,会造成浪费,如果父类构造函数共有属性极多,会导致运行速度减慢。
- 因为要执行Child.prototype = new Parent();所以会执行一遍父类的构造函数,此时并没有传递参数,当赋值之后,所以会出现无用属性在子类的原型对象上