面向对象有三大特性:封装、继承、多态 (抽象)
- 封装:我们前面将属性和方法封装到一个类中,可以称之为封装的过程;
- 继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中);
- 多态:不同的对象在执行时表现出不同的形态; (抽象)把现实事物抽象成代码的过程
继承
继承可以帮助我们将重复的代码和逻辑抽取到父类中,子类只需要直接继承过来使用即可。
JavaScript当中如何实现继承呢?
不着急,我们先来看一下JavaScript原型链的机制; p再利用原型链的机制实现一下继承;
原型链(prototype chain)
从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上面获取:
那么什么地方是原型链的尽头呢?比如第三个对象是否也是有原型__proto__属性呢?
我们会发现它打印的是 [Object: null prototype] {}
事实上这个原型就是我们最顶层的原型了 p从Object直接创建出来的对象的原型都是 [Object: null prototype] {}
[Object: null prototype] {} 原型有什么特殊吗?
- 特殊一:该对象有原型属性,但是它的原型属性已经指向的是null,也就是已经是顶层原型了;
- 特殊二:该对象上有很多默认的属性和方法;
通过原型链实现继承
// 父类: 公共属性和方法
function Person() {
this.name = "why"
this.friends = []
}
Person.prototype.eating = function() {
console.log(this.name + " eating~")
}
// 子类: 特有属性和方法
function Student() {
this.sno = 111
}
var p = new Person()
Student.prototype = p
Student.prototype.studying = function() {
console.log(this.name + " studying~")
}
// name/sno
var stu = new Student()
// console.log(stu.name)
// stu.eating()
// stu.studying()
// 原型链实现继承的弊端:
// 1.第一个弊端: 打印stu对象, 继承的属性是看不到的
// console.log(stu.name)
// 2.第二个弊端: 创建出来两个stu的对象
var stu1 = new Student()
var stu2 = new Student()
// 直接修改对象上的属性, 是给本对象添加了一个新属性
stu1.name = "kobe"
console.log(stu2.name)
// 获取引用, 修改引用中的值, 会相互影响
stu1.friends.push("kobe")
console.log(stu1.friends)
console.log(stu2.friends)
// 3.第三个弊端: 在前面实现类的过程中都没有传递参数
var stu3 = new Student("lilei", 112)
继承-借用构造函数方案
// 父类: 公共属性和方法
function Person(name, age, friends) {
// this = stu
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.eating = function() {
console.log(this.name + " eating~")
}
// 子类: 特有属性和方法
function Student(name, age, friends, sno) {
Person.call(this, name, age, friends)
// this.name = name
// this.age = age
// this.friends = friends
this.sno = 111
}
var p = new Person()
Student.prototype = p
Student.prototype.studying = function() {
console.log(this.name + " studying~")
}
// name/sno
var stu = new Student("why", 18, ["kobe"], 111)
// console.log(stu.name)
// stu.eating()
// stu.studying()
// 原型链实现继承已经解决的弊端
// 1.第一个弊端: 打印stu对象, 继承的属性是看不到的
console.log(stu)
// 2.第二个弊端: 创建出来两个stu的对象
var stu1 = new Student("why", 18, ["lilei"], 111)
var stu2 = new Student("kobe", 30, ["james"], 112)
// // 直接修改对象上的属性, 是给本对象添加了一个新属性
// stu1.name = "kobe"
// console.log(stu2.name)
// // 获取引用, 修改引用中的值, 会相互影响
stu1.friends.push("lucy")
console.log(stu1.friends)
console.log(stu2.friends)
// // 3.第三个弊端: 在前面实现类的过程中都没有传递参数
// var stu3 = new Student("lilei", 112)
// 强调: 借用构造函数也是有弊端:
// 1.第一个弊端: Person函数至少被调用了两次
// 2.第二个弊端: stu的原型对象上会多出一些属性, 但是这些属性是没有存在的必要
继承-父类原型赋值给子类
// 父类: 公共属性和方法
function Person(name, age, friends) {
// this = stu
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.eating = function() {
console.log(this.name + " eating~")
}
// 子类: 特有属性和方法
function Student(name, age, friends, sno) {
Person.call(this, name, age, friends)
// this.name = name
// this.age = age
// this.friends = friends
this.sno = 111
}
// 直接将父类的原型赋值给子类, 作为子类的原型
Student.prototype = Person.prototype
Student.prototype.studying = function() {
console.log(this.name + " studying~")
}
// name/sno
var stu = new Student("why", 18, ["kobe"], 111)
console.log(stu)
stu.eating()
不正确 student和Person的原型指向同一个 给student原型加东西Person也会出现
继承-原型式继承-对象
var obj = {
name: "why",
age: 18
}
var info = Object.create(obj)
// 原型式继承函数
//方法一
function createObject1(o) {
var newObj = {}
Object.setPrototypeOf(newObj, o)
return newObj
}
//方法二
function createObject2(o) {
function Fn() {}
Fn.prototype = o
var newObj = new Fn()
return newObj
}
// var info = createObject2(obj)
//方法三
var info = Object.create(obj)
console.log(info)
console.log(info.__proto__)
继承-寄生式继承-对象
var personObj = {
running: function() {
console.log("running")
}
}
function createStudent(name) {
var stu = Object.create(personObj)
stu.name = name
stu.studying = function() {
console.log("studying~")
}
return stu
}
var stuObj = createStudent("why")
var stuObj1 = createStudent("kobe")
var stuObj2 = createStudent("james")
继承-寄生组合式继承(最终方案)
function createObject(o) {
function Fn() {}
Fn.prototype = o
return new Fn()
}
function inheritPrototype(SubType, SuperType) {
SubType.prototype = Objec.create(SuperType.prototype)
Object.defineProperty(SubType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function() {
console.log("running~")
}
Person.prototype.eating = function() {
console.log("eating~")
}
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends)
this.sno = sno
this.score = score
}
inheritPrototype(Student, Person)
Student.prototype.studying = function() {
console.log("studying~")
}
var stu = new Student("why", 18, ["kobe"], 111, 100)
console.log(stu)
stu.studying()
stu.running()
stu.eating()
console.log(stu.constructor.name)
对象的方法补充
Object.create()
使用指定的原型对象和属性创建一个新对象。
var obj = {
name: "why",
age: 18
}
var info = Object.create(obj, {
address: {
value: "北京市",
enumerable: true
}
})
//增加在info上而不是在原型增加
hasOwnProperty是否是自有属性
// hasOwnProperty方法判断
// console.log(info.hasOwnProperty("address"))
// console.log(info.hasOwnProperty("name"))
in 操作符: 不管在当前对象还是原型中返回的都是true
// console.log("address" in info)
// console.log("name" in info)
// // for in
// for (var key in info) {
// console.log(key)
// }
instanceof 用于检测构造函数的pototype,是否出现在某个实例对象的原型链上
function createObject(o) {
function Fn() {}
Fn.prototype = o
return new Fn()
}
function inheritPrototype(SubType, SuperType) {
SubType.prototype = createObject(SuperType.prototype)
Object.defineProperty(SubType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
function Person() {
}
function Student() {
}
inheritPrototype(Student, Person)
console.log(Person.prototype.__proto__)
var stu = new Student()
console.log(stu instanceof Student) // true
console.log(stu instanceof Person) // true
console.log(stu instanceof Object) // true
isPrototypeOf 用于检测某个对象,是否出现在某个实例对象的原型链上
function Person() {
}
var p = new Person()
console.log(p instanceof Person)
console.log(Person.prototype.isPrototypeOf(p))
//
var obj = {
name: "why",
age: 18
}
var info = Object.create(obj)
// console.log(p instanceof Person)//只能判断对象是否属于函数
// console.log(info instanceof obj) //错误的
//console.log(person.prototype.isPrototypeOf(info))
console.log(obj.isPrototypeOf(info))
Object.assign()创建一个新对象,使用现有的对象来提供新创建的对象
因为 Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。
const target = { a: 1, b: 2 }; const source = { c: 4, d: 5 };
// const res = Object.assign(target, source)
// 原对象target也会发生变化
// 解决方案1 const res1 = Object.assign({}, target, source)
// 解决方案2 const res2 = {...target, ...source}
//合并具有相同属性的对象
const o1 = { a: 1, b: 1, c: 1 };
const o2 = { b: 2, c: 2 };
const o3 = { c: 3 };
const obj = Object.assign({}, o1, o2, o3); console.log(obj);
// { a: 1, b: 2, c: 3 },注意属性被后续参数中具有相同属性的其他对象覆盖
//继承属性和不可枚举属性是不可拷贝的
const obj = Object.create({foo: 1},
{ // foo 是个继承属性。 bar: { value: 2 // bar 是个不可枚举属性。 },
baz: { value: 3, enumerable: true // baz 是个自身可枚举属性。 } });
const copy = Object.assign({}, obj); console.log(copy); // { baz: 3 }
//原始类型会包装为对象
const v1 = "abc"; const v2 = true;
const v3 = 10;
const v4 = Symbol("foo")
const v5=[7,8,9]
const v6=[{a1:1}
const obj = Object.assign({}, v1, null, v2, undefined, v3, v4);
// 原始类型会被包装,null 和 undefined 会被忽略。
// 注意,只有字符串的包装对象才可能有自身可枚举属性。
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
const obj = Object.assign({}, v1, null, v2, undefined, v3, v4,v5,v6);
console.log(obj); //{ '0': { a1: 1 }, '1': 8, '2': 9 }
//拷贝Symbol类型的属性
const o1 = { a: 1 };
const o2 = { [Symbol('foo')]: 2 };
const obj = Object.assign({}, o1, o2);
console.log(obj); // { a : 1, [Symbol("foo")]: 2 }
Object.getOwnPropertySymbols(obj); // [Symbol(foo)]
//异常会打断后续的拷贝
const target = Object.defineProperty({}, "foo", { value: 1, writable: false });
// target 的 foo 属性是个只读属性。
Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4}); // TypeError: "foo" is read-only // 注意这个异常是在拷贝第二个源对象的第二个属性时发生的。
console.log(target.bar);
// 2,说明第一个源对象拷贝成功了。 console.log(target.foo2);
// 3,说明第二个源对象的第一个属性也拷贝成功了。 console.log(target.foo);
// 1,只读属性不能被覆盖,所以第二个源对象的第二个属性拷贝失败了。 console.log(target.foo3);
// undefined,异常之后 assign 方法就退出了,第三个属性是不会被拷贝到的。
console.log(target.baz); // undefined,第三个源对象更是不会被拷贝到的
//拷贝访问器
const obj = {
foo: 1,
get bar() {
return 2
},
}
let copy = Object.assign({}, obj)
console.log(copy) // { foo: 1, bar: 2 } copy.bar的值来自obj.bar的getter函数的返回值
// 下面这个函数会拷贝所有自有属性的属性描述符
function completeAssign(target, ...sources) {
sources.forEach((source) => {
let descriptors = Object.keys(source).reduce((descriptors, key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key)
return descriptors
}, {})
// Object.assign 默认也会拷贝可枚举的Symbols
Object.getOwnPropertySymbols(source).forEach((sym) => {
let descriptor = Object.getOwnPropertyDescriptor(source, sym)
if (descriptor.enumerable) {
descriptors[sym] = descriptor
}
})
Object.defineProperties(target, descriptors)
})
return target
}
copy = completeAssign({}, obj)
console.log(copy)
// { foo:1, get bar() { return 2 } }
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
const object1 = {};
Object.defineProperty(object1, 'property1', {
value: 42,
writable: false
});
object1.property1 = 77;
// throws an error in strict mode
console.log(object1.property1);
// expected output: 42
Object.defineProperties(obj, props) 方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
var obj = {};
Object.defineProperties(obj, {
'property1': {
value: true,
writable: true
},
'property2': {
value: 'Hello',
writable: false
}
// etc. etc.
});
Object.entries() 方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。
const object1 = {
a: 'somestring',
b: 42
};
for (const [key, value] of Object.entries(object1)) {
console.log(`${key}: ${value}`);
}
// expected output:
// "a: somestring"
// "b: 42"
Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。
const obj = {
prop: 42
};
Object.freeze(obj);
obj.prop = 33;
// Throws an error in strict mode
console.log(obj.prop);
// expected output: 42
原型继承关系
var obj = {
name: "why"
}
console.log(obj.__proto__)
// 对象里面是有一个__proto__对象: 隐式原型对象
// Foo是一个函数, 那么它会有一个显示原型对象: Foo.prototype
// Foo.prototype来自哪里?
// 答案: 创建了一个函数, Foo.prototype = { constructor: Foo }
// Foo是一个对象, 那么它会有一个隐式原型对象: Foo.__proto__
// Foo.__proto__来自哪里?
// 答案: new Function() Foo.__proto__ = Function.prototype
// Function.prototype = { constructor: Function }
// var Foo = new Function()
function Foo() {
}
console.log(Foo.prototype === Foo.__proto__)//false
console.log(Foo.prototype.constructor)//Foo
console.log(Foo.__proto__.constructor)//Function
var foo1 = new Foo()
var obj1 = new Object()
console.log(Function.prototype ===Function.__proto__)true
console.log(Object.getOwnPropertyDescriptors(Function.__proto__))true