JS面向对象及继承
目录
一、面向对象
1.面向对象特征
2.创建对象的方法
(1)通过new关键词
(2)对象字面量
(3)构造函数创建
(4)Object.create()创建
(5)ES6中的类
(6)工厂函数
(7)函数表达式与对象字面量的结合
(8)使用模板字面量创建对象(ES6+)
(9)通过解构赋值和剩余参数创建对象(ES6)
二、对象继承的方式
1.原型链继承
2.对象属性继承
3.ES6类继承
4.构造函数继承
5.组合继承
一、面向对象
1.面向对象特征
封装、继承、多态、抽象
封装:把客观事物封装成抽象的类,且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的类或者对象隐藏信息。
继承:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态:一个类实例的相同方法在不同情形下有不同的表现形式。
2.创建对象的方法
(1)通过new关键词
console.dir(Object)方法:可以显示指定的JavaScript对象的属性列表
let obj3 = new Object();
console.log(obj3);
console.dir(obj3);
(2)对象字面量
这是最简单和直接的方式,通过直接量语法创建对象。
var obj1 = {
name: 'jack',
age: 20,
sayHello: function(){
console.log('Hello, I am'+ this.name);
}
};
console.log(obj1);
(3)构造函数创建
构造函数是一个特殊的函数,用于初始化新创建的对象。使用new
关键字与构造函数一起调用,可以创建一个新的对象实例。
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log('Hello, I am'+ this.name);
}
}
var obj2 = new Person('Tom', 25);
console.log(obj2);
(4)Object.create()创建
Object.create() 创建一个新对象,并指定一个原型对象
const proto = {
greet: function() {
console.log('Hello!');
}
};
let obj4 = Object.create(proto);
console.log('obj4',obj4);
(5)ES6中的类
ES6引入了类(Class)语法,使得对象创建和继承更加直观和易于理解。
语法:class xxx{
constructor(参数){}
函数名(){}
}
class Animal {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log('Hello, I am'+ this.name);
}
}
let obj5 = new Animal('Lion', 10);
console.log(obj5);
obj5.sayHello(); // Hello, I am Lion
console.log(obj5.__proto__ === Animal.prototype); // true
console.log(obj5 instanceof Animal); // true
(6)工厂函数
工厂函数是一种创建对象的方法,它不需要使用new
关键字,而是返回一个新对象。
可与构造函数作对比
function Person(name, age) {
return {
name:name,
age:age,
sayHello:function() {
console.log('Hello, I am'+ this.name);
}
}
}
var obj6 = Person('Tom', 25);
console.log(obj6);
(7)函数表达式与对象字面量的结合
通常用于创建模块或私有变量。
const obj7=(function(){
let num=10;
return {
getNum: function() {
return num;
}
}
})();
console.log(obj7.getNum()); // 10
(8)使用模板字面量创建对象(ES6+)
ES6 对象的简写和计算属性:模板字面量主要用于字符串插值,但结合计算属性名和函数,也可以用于动态创建对象。
const key = 'name';
const value = 'haha';
const obj8 = {
[key]: value
};
console.log(obj8.name); // haha
(9)通过解构赋值和剩余参数创建对象(ES6)
解构赋值和剩余参数可以与对象字面量结合使用,以更灵活的方式创建和修改对象。
const { a, b, ...object } = { a: 1, b: 2, c: 3, d: 4 };
const newObj = { ...object, e: 5 };
console.log(newObj); // { c: 3, d: 4, e: 5 }
二、对象继承的方式
在前端领域,继承通常是指JavaScript中的继承机制。JavaScript利用原型对象的链式查找机制实现继承。每个对象都有一个原型对象,当访问一个对象的属性或方法时,如果在该对象本身找不到,就会在其原型对象中查找,依次类推,直到找到或者到达原型链的顶(Object.prototype)。
原型与原型链:原型与原型链-CSDN博客
1.原型链继承
*共享引用类型属性
原理:将子构造函数的原型对象设置为父构造函数的实例,从而实现继承。
优点:可以实现函数复用。
缺点:引用类型属性会被所有实例共享,修改一个实例的属性会影响其他实例。在创建子类型实例时,不能向父类型构造函数传递参数。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(age) {
this.age = age;
}
// 将子构造函数的原型对象设置为父构造函数的实例
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child1 = new Child(10);
const child2 = new Child(12);
child1.colors.push('black');
console.log(child1.name); // undefined,因为name属性是在Parent构造函数中定义的,未传递参数
console.log(child1.colors); // ['red', 'blue', 'green', 'black']
console.log(child2.colors); // ['red', 'blue', 'green', 'black'](共享引用类型属性)
child1.sayName(); // undefined,因为name属性是undefined
2.对象属性继承
*共享引用类型属性
通过拷贝属性实现
const parent = {
name: 'Parent',
colors: ['red', 'blue', 'green'],
sayName: function() {
console.log(this.name);
}
};
const child = Object.create(parent); // 创建一个以parent为原型的对象
child.age = 10;
console.log(child.name); // 'Parent'
child.colors.push('black');
console.log(parent.colors); // ['red', 'blue', 'green', 'black'](共享引用类型属性)
3.ES6类继承
*不共享引用类型属性
真正意义上的继承
原理:使用class关键字定义类,通过extends关键字实现继承。
优点:语法更加简洁明了,易于理解和维护。
缺点:相对于原型链继承和构造函数继承来说,是ES6引入的新特性。
class Parent {
constructor(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类的构造函数
this.age = age;
}
}
const child1 = new Child('Child1', 10);
// ...
4.构造函数继承
*不共享引用类型属性
原理:在子构造函数中调用父构造函数,并使用call或apply方法将父构造函数的作用域绑定到子构造函数中,从而继承父构造函数的属性。
优点:可以向父构造函数传递参数。可以解决引用类型属性共享的问题。
缺点:方法不能复用,每次创建实例时都会创建一遍方法。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 调用父构造函数
this.age = age;
}
// ...
5.组合继承
*不共享引用类型属性
原理:结合原型链继承和构造函数继承的优点,通过在子构造函数中调用父构造函数来继承属性,同时将子构造函数的原型设置为父构造函数的实例,从而继承方法。
优点:可以向父构造函数传递参数。既可以实现属性继承,又可以实现方法复用。
缺点:调用了两次父构造函数,一次是在设置子构造函数的原型时,一次是在子构造函数内部。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 继承父构造函数的属性
this.age = age;
}
// 继承父构造函数的原型链
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child1 = new Child('Child1', 10);
const child2 = new Child('Child2', 12);
child1.colors.push('black');
console.log(child1.name); // 'Child1'
console.log(child1.colors); // ['red', 'blue', 'green', 'black']
console.log(child2.colors); // ['red', 'blue', 'green'](不共享)
child1.sayName(); // 'Child1'
注意:在组合继承中,虽然Child.prototype = new Parent();
会调用一次Parent
构造函数,但这是为了设置原型链,属性是通过Parent.call(this, name)
在Child
构造函数中传递的,所以不会造成属性共享问题,同时方法也能复用。
若文章对你有帮助,点赞、收藏加关注吧!