new操作符
let person = new Object();
person.name = "Nicholas";
person.age = 29;
对象字面量
对象字面量是对象定义的简写形式,目的是为了简化包含大量属性的对象的创建。
let person = {
name: "Nicholas",
age: 29
};
也可以使用字符串、数字、bool类型来定义。
let person = {
"name": "Nicholas",
"age": 29,
5: true,
true: 123,
false: 321
};
person[5]; //true
person[true]; //123
person[false]; //321
但要注意此类情况(后值覆盖前值):
let person = {
"name": "Nicholas",
"age": 29,
5: true,
true: 123,
true: 555,
name: 'jack'
};
// 最后person变为
{
5: true,
name: "jack",
age: 29,
true: 555
}
访问/设置方式
点语法
let person = {
"name": "Nicholas",
"age": 29,
5: true,
true: 123
};
person.name; //"Nicholas"
person.age; //29
person.name = 'jack';
person.age = 30;
person.true; //对吗?
person.5; //对吗?
中括号
let person = {
"name": "Nicholas",
"age": 29,
5: true,
true: 123
};
person['name']; //"Nicholas"
person['age']; //29
person['name'] = 'jack';
person['age'] = 30;
person[true]; //对吗?
person[5]; //对吗?
configurable(可配置)
表示属性是否可以通过delete
删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是 true
。
let person = {};
Object.defineProperty(person, "name", {
configurable: false,
value: undefined
});
console.log(person); //{name: undefined}
delete person.name; //false
console.log(person); //{name: undefined}
let person = {};
Object.defineProperty(person, "name", {
configurable: true,
value: undefined
});
console.log(person); //{name: undefined}
delete person.name; //true
console.log(person); //{}
let person = {};
Object.defineProperty(person,
"name", {
configurable: false,
value: "Nicholas"
});
//Uncaught TypeError: Cannot redefine property: name
Object.defineProperty(person, "name", {
configurable: true,
value: "Nicholas"
});
enumerable
表示属性是否可以通过for-in
循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是true
。
let person = {};
Object.defineProperties(person, {
name: {
enumerable: false
},
sex: {
enumerable: true
},
age: {
enumerable: true
}
});
for (let key in person) {
console.log(key);
}
// sex
// age
writable
表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的这个特性都是true
。
var obj = {};
Object.defineProperties(obj, {
sex: {
value: '男',
writable: true
},
name: {
value: '张三',
writable: false
}
});
obj.name = '李四';
obj.sex = '女';
console.log(obj); //{sex: "女", name: "张三"}
value
包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性的默认值为undefined
。
let person = {};
Object.defineProperty(person, "name", {
value: "Nicholas"
});
console.log(person.name); //Nicholas
get
获取函数,在读取属性时调用。默认值为undefined
。
let book = {
year_: 2017,
edition: 1
};
Object.defineProperty(book, "year", {
get() {
return this.year_;
}
});
console.log(book.year); // 2017
console.log(book.edition); // 1
set
设置函数,在写入属性时调用。默认值为undefined
。
let book = {
year_: 2017,
edition: 1
};
Object.defineProperty(book, "year", {
get() {
return this.year_;
},
set(newValue) {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
});
book.year = 2018;
console.log(book.year); // 2018
console.log(book.edition); // 2
获取属性描述
getOwnPropertyDescriptor
获取指定属性的属性描述符。这个方法接收两个参数:属性所在的对象和要取得其描述符的属性名。返回值是一个对象,对于访问器属性包含属性的类型。
let book = { name: '张三' };
console.log(Object.getOwnPropertyDescriptor(book, 'name')); //{value: "张三", writable: true, enumerable: true, configurable: true}
getOwnPropertyDescriptors
方法用来获取一个对象的所有自身属性的描述符。
let book = { name: '张三', age: 12 };
console.log(Object.getOwnPropertyDescriptors(book));
/**
{
age: { value: 12, writable: true, enumerable: true, configurable: true }
name: { value: "张三", writable: true, enumerable: true, configurable: true }
}
*/
合并对象
在es6
之前,通常会封装一个方法,可参考jqeury
的extend
方法。
var obj = {
a: 1,
b: 2
}
var obj1 = {
c: 3,
a: 5
}
{
a: 5,
b: 2,
c: 3
}
//一个特别简单的浅拷贝
function extend(target, source) {
target = target || {};
if (typeof target !== 'object') {
throw new Error('target不是对象');
}
if (!source) {
throw new Error('source不能为空');
}
if (typeof source !== 'object') {
throw new Error('source不是对象');
}
for (let key in source) {
target[key] = source[key]; //然后将源对象的值赋值到target中
}
return target; //最后返回target
}
var obj = extend({
a: 1,
b: 2
}, {
c: 3,
a: 5
})
/**
{
a: 5,
b: 2,
c: 3
}
*/
Object.assign
用于对象的合并,将源对象(source)
的所有可枚举属性,复制到目标对象(target)
。源对象可以是多个,后者替换前者。
let obj = Object.assign({ a: 1 }, { b: 2 }, { c: 3 }, { a: 5 }, { b: 6 });
//{a: 5, b: 6, c: 3}
出现的问题:
以上方法都是浅拷贝,如果拷贝属性是对象,那么拷贝的只是引用。
var obj = {
a: 1,
b: 'fff'
}
var obj1 = {
a: 2,
c: {
name: '李四',
age: 20
}
}
var obj2 = Object.assign({}, obj, obj1);
obj1.a = 3;
obj1.c.name = '王五';
obj1.c.age = 30;
console.log(obj2); //obj2.c会跟着obj1.c的改变而改变
深拷贝
//深拷贝
function deepClone(target, source) {
if (typeof target !== 'object' || !target) {
return source || target;
}
if (typeof source !== 'object' || !source) {
return source;
}
for (let key in source) {
var item = source[key];
if (typeof item === 'object' && item) {
target[key] = deepClone(Array.isArray(item) ? [] : {}, item);
} else {
target[key] = item;
}
}
return target;
}
var obj = {
a: 1,
b: 'fff'
}
var obj1 = {
a: 2,
c: {
name: '李四',
age: 20
}
}
var obj2 = deepClone(obj, obj1);
obj1.a = 3;
obj1.c.name = '王五';
obj1.c.age = 30;
console.log(obj2); //obj2.c不会跟着obj1.c的改变而改变
解构
// 使用对象解构
let person = { name: 'Matt', age: 27 };
//可以使用别名 personAge
//相当于 let name = person.name;
//相当于 let personAge = person.age;
let { name, age: personAge } = person;
console.log(name, personAge); //Matt 27
let person = { name: 'Matt', age: 27 };
let { job } = person; //不存在的也可以解构,其实就相当于 let job = person.job;
console.log(job); //undefined
let person = { name: 'Matt', age: 27, sex: null };
//也可以设置一个默认值,如果获取的值为undefined的话
//注:如果获取的值为null则不会取默认值,而是直接设置为null
let { name, job = 'Software engineer', sex = '男' } = person;
console.log(name); // Matt
console.log(sex); //null
console.log(job); // Software engineer
//无法解构null和undefined
let { _ } = null; // TypeError
let { _ } = undefined; // TypeError
const [n1, n2, { a }] = [1, 2, { a: 3 }]; //解构数组
console.log(n1, n2, a); // 1 2 3
const [n1,...n2] = [1,2,3,4,5,6];
console.log(n1,n2);
创建对象
千万不要一个一个对象的去定义,不要重复相同的劳动,毫无意义!!!
简单工厂模式
简单理解工厂就是有一条标准化的流水线,只要输入参数就能按照标准流程生产出需要的产品。
function createPerson(name, age, job) {
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
console.log(this.name);
};
return o;
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");
构造函数模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
};
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg
console.log(person1.constructor == Person); // true
console.log(person2.constructor == Person); // true
console.log(person2 instanceof Object); // true
console.log(person2 instanceof Person); // true
//写成函数表达式也行
let Person = function (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
};
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg
console.log(person1.constructor == Person); // true
console.log(person2.constructor == Person); // true
console.log(person2 instanceof Object); // true
console.log(person2 instanceof Person); // true
**缺陷:**每次sayName
方法都要重新定义一次,其实这个方法只需定义一次即可。
原型模式
function Person() { }
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
console.log(this.name);
};
缺陷: 1. 原型上的对象数据会被共享,一个对象改了,其他对象也会跟着变。2. 创建对象不方便。
改良
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
//将无需重复定义/共享的定义在原型上
Person.prototype.sayName = function () {
console.log(this.name);
};
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg
hasOwnProperty
hasOwnProperty()
方法用于确定某个属性是在实例上还是在原型对象上。
function Person() { }
Person.prototype.age = 12;
let person1 = new Person;
person1.age = 20;
console.log(person1.age); //20
console.log(person1.hasOwnProperty("age")); //true 来自实例
delete person1.age;
console.log(person1.age); //12
console.log(person1.hasOwnProperty("age")); //false 来自原型
in
in
操作符会在可以通过对象访问指定属性时返回true
,无论该属性是在实例上还是在原型上。
function Person() { }
Person.prototype.age = 12;
let person1 = new Person;
person1.age = 20;
console.log(person1.age); //20
console.log(person1.hasOwnProperty("age")); //true 来自实例
'age' in person1; //true
delete person1.age;
console.log(person1.age); //12
console.log(person1.hasOwnProperty("age")); //false 来自原型
'age' in person1; //true
keys
Object.keys()
方法接收一个对象作为参数,返回包含该对象所有可枚举的实例属性名称的字符串数组。
Object.keys({ a: 1, b: 2, c: 3 }); //["a", "b", "c"]
getOwnPropertyNames
Object.getOwnPropertyNames()
方法可以获取所有实例属性,无论是否可以枚举。
function Person() {
this.sex = '男';
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
console.log(this.name);
};
var p = new Person();
console.log(Object.getOwnPropertyNames(p)); //?
values
Object.values()
返回对象值的数组。
const o = {
foo: 'bar',
baz: 1,
qux: {}
};
console.log(Object.values(o)); // ["bar", 1, {}]
entries
Object.entries()
接收一个对象,返回键/值对的数组。
const o = {
foo: 'bar',
baz: 1,
qux: {}
};
console.log(Object.entries(o)); // [["foo", "bar"], ["baz", 1], ["qux", {}]]
原型
每个对象都有一个特殊的属性叫作原型(prototype)
,在原型上定义的属性和方法会被每一个实例对象共享。
function Person() { }
Person.prototype.name = "无名";
Person.prototype.children = [];
Person.prototype.sayName = function () {
console.log(`我的名字叫:${this.name}`);
}
let zhangsan = new Person();
let lisi = new Person();
console.log(zhangsan.name);
console.log(lisi.name);
console.log(zhangsan.hasOwnProperty('name'));
zhangsan.name = '张三';
console.log(zhangsan.hasOwnProperty('name'));
console.log(lisi.name);
Person.prototype.name = '有名';
console.log(lisi.name);
console.log(zhangsan.name);
delete zhangsan.name;
console.log(zhangsan.name);
lisi.name = '李四';
console.log(zhangsan.sayName());
console.log(lisi.sayName());
console.log(zhangsan.sayName === lisi.sayName);
zhangsan.children.push('张欣');
console.log(zhangsan.children);
console.log(lisi.children);
console.log(zhangsan.hasOwnProperty('children'));
console.log(zhangsan.children === lisi.children);
//?
原型链
每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个内部指针指向另一个构造函数的原型对象,这样就在实例和原型之间构造了一条原型链。
过程:
先在对象本身查找–>构造函数中查找–>对象的原型中查找–>构造函数的原型中查找–>当前原型的原型中查找
理解:
原型链就是把原型串联起来,而原型链的最顶端为null,原型是来解决对象的共享属性或者共享方法对象具有_proto_原型而函数具有prototype原型
function Person() { }
Person.prototype.constructor === ?;
Person.prototype.constructor.prototype === ?;
Person.prototype.__proto__ === ?
let zhangsan = new Person;
zhangsan.constructor === Person;
zhangsan.__proto__ === ?;
zhangsan.__proto__.__proto__ === ?
let obj = new Object();
obj.__proto__ === ?;
obj.__proto__.__proto__ === ?
面向过程
现在有个需求:创建一个人,张三、男、20岁。
let zhangsan = new Object;
zhangsan.name = '张三';
zhangsan.age = 20;
zhangsan.sex = '男';
又新来了一个需求:创建一个人,李四、男、30岁。
let lisi = new Object;
lisi.name = '李四';
lisi.age = 30;
lisi.sex = '男';
随着需求的不断增加,已经添加了1000个人。
let zhangsan = new Object;
zhangsan.name = '张三';
zhangsan.age = 20;
zhangsan.sex = '男';
let lisi = new Object;
lisi.name = '李四';
lisi.age = 30;
lisi.sex = '男';
//...........剩下的3992行代码
然后突然有一天新增了一个需求:将所有年龄>=20的标记为青年,<20的标记为少年。
let zhangsan = new Object;
zhangsan.name = '张三';
zhangsan.age = 20;
zhangsan.sex = '男';
zhangsan.type = '青年';
let lisi = new Object;
lisi.name = '李四';
lisi.age = 30;
lisi.sex = '男';
zhangsan.type = '青年';
//...........剩下的4990行代码
突然有一天,又要为每个人新增一个行为:能自我介绍。
…更多的需求
面向对象
将任何事物都想象成一个对象,并提炼出这个对象的属性、行为等,然后封装起来。
需求:创建一个人,张三、男、20岁。
此刻,首先需将人想象为一个对象,目前给出的三个属性是姓名、性别、年龄,将封装思想用起来。
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
let zhangsan = new Person('张三', 20, '男');
又新来了一个需求:创建一个人,李四、男、30岁。
let lisi = new Person('李四', 30, '男');
随着需求的不断增加,已经添加了1000个人。
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
let zhangsan = new Person('张三', 20, '男');
let lisi = new Person('李四', 30, '男');
//... 剩下的998行代码
然后突然有一天新增了一个需求:将所有年龄>=20的标记为青年,<20的标记为少年。
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
//只需在构造函数里新增一个判断即可,其他每个实例都不用动
if (this.age >= 20) {
this.type = '青年';
}
else {
this.type = '少年';
}
}
突然有一天,又要为每个人新增一个行为:能自我介绍。
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
if (this.age >= 20) {
this.type = '青年';
}
else {
this.type = '少年';
}
}
Person.prototype.say = function () {
console.log(`我是${this.name},我今年${this.age}岁了,我是个${this.sex}人,算是${this.type}了`);
}
let zhangsan = new Person('张三', 20, '男');
let lisi = new Person('李四', 30, '男');
//... 剩下的998行代码
console.log(zhangsan.say());
console.log(lisi.say());
继承
继承就是子继承父的所有属性和方法,如:父亲的基因被儿子继承,所以通过DNA(原型链)可追溯某人的向上继承关系。从技术上来讲就是继承父对象的所有属性和方法。
function Father() {
this.money = 10000000000; //百亿家产
this.houses = 10; //10栋别墅
this.cars = 100; //100辆车
}
function Son() {
Father.call(this); //继承父亲的家产
}
let son = new Son;
console.log(son.money, son.houses, son.cars); //10000000000 10 100
原型链继承
function superType() {
this.name = '张三';
this.children = ['张欣', '张宇'];
}
superType.prototype.age = 12;
function sub() {
}
sub.prototype = new superType();
var s = new sub();
console.log(s.name, s.age); //张三 12
console.log(s.hasOwnProperty('name')); //false
console.log(s.hasOwnProperty('children')); //false
var s1 = new sub();
s1.children.push('李四');
console.log(s1.children);
console.log(s.children);
console.log(s.constructor === superType); //true
console.log(s instanceof sub); //true
console.log(s instanceof superType); //true
盗用构造函数
function superType() {
this.name = '张三';
this.children = ['张欣', '张宇'];
}
superType.prototype.age = 12;
function sub() {
superType.call(this);
}
var s = new sub(); //从sub派生出来的,继承自sub
console.log(s.name); //张三
console.log(s.hasOwnProperty('name')); //true
console.log(s.hasOwnProperty('children')); //true
var s1 = new sub();
s1.children.push('李四');
console.log(s1.children);
console.log(s.children);
console.log(s.constructor === sub); //true
console.log(s instanceof sub); //true
console.log(s instanceof superType); //false
//目前为止一切看起来都正常了
console.log(s.age, s1.age);
组合继承
组合继承(有时候也叫伪经典继承)综合了原型链和盗用构造函数,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
function superType() {
console.log('superType执行了一次');
this.name = '张三';
this.children = ['张欣', '张宇'];
}
superType.prototype.age = 12;
function sub() {
superType.call(this);
}
//直接继承原型,不用再次实例化父类构造函数,防止再次执行父类构造函数
//sub.prototype = Object.create(superType.prototype, { constructor:{ value:sub, enumerable:false } });
//跟上面的一样,只是最后自己手动修改一下构造函数的指向
sub.prototype = Object.create(superType.prototype)//sub.prototype = new superType()
sub.prototype.constructor = sub;
//使用浅拷贝的方式将自身的构造函数替换掉父类原型的构造函数
//sub.prototype = Object.assign(Object.create(superType.prototype), { constructor: sub });
var s = new sub();
console.log(s.name); //张三
console.log(s.hasOwnProperty('name')); //true
console.log(s.hasOwnProperty('children')); //true
var s1 = new sub();
s1.children.push('李四');
console.log(s1.children);
console.log(s.children);
console.log(s.constructor === sub); //true
console.log(s instanceof sub); //true
console.log(s instanceof superType); //true
console.log(s.age, s1.age); //12 12
class类
与函数类型相似,定义类也有两种主要方式:类声明和类表达式。这两种方式都使用class
关键字加大括号。类其实就是语法糖。
// 类声明
class Person {}
// 类表达式
const Animal = class {};
函数可以提升,但是类不能提升。
console.log(ClassDeclaration); //ReferenceError: ClassDeclaration is not defined
class ClassDeclaration {}
//同样也受到块级作用域的限制
{
function FunctionDeclaration() {}
class ClassDeclaration {}
}
console.log(FunctionDeclaration); //FunctionDeclaration() {}
console.log(ClassDeclaration); //ReferenceError: ClassDeclaration is not defined
类的构成
类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类方法,但这些都不是必需的。空的类定义照样有效。默认情况下,类定义中的代码都在严格模式下执行。
// 空类定义,有效
class Foo {}
// 有构造函数的类,有效
class Bar {
constructor() {}
}
// 有获取函数的类,有效
class Baz {
get myBaz() {}
}
// 有静态方法的类,有效
class Qux {
static myQux() {}
}
class Person {
constructor() {}
name = '张三';
_age = 17;
children = [];
say() {
console.log(`我的名字叫:${this.name}`);
}
static isPerson(person) {
return person instanceof Person;
}
get type() {
if (this._age > 18) {
return '青年';
}
return '少年';
}
get age() {
return this._age;
}
set age(value) {
if (value > 0 && value < 120) {
this._age = value;
}
}
}
let person = new Person();
console.log(person.age, person.type);
person.age = 0;
console.log(person.age);
person.age = 20;
console.log(person.type);
console.log(Person.isPerson(person), Person.isPerson(new Object()));
console.log(person.hasOwnProperty('name'), person.hasOwnProperty('children'));
类构造函数
constructor
关键字用于在类定义块内部创建类的构造函数。方法名constructor
会告诉解释器在使用new
操作符创建类的新实例时,应该调用这个函数。
class Animal { }
class Person {
constructor() {
console.log('person ctor');
}
}
class Vegetable {
constructor(color) {
this.color = color;
}
}
let a = new Animal();
let p = new Person(); // person ctor
let v = new Vegetable('orange');
console.log(v.color); // orange
//和下面代码等价
function Person() {
console.log('person ctor');
}
function Vegetable(color) {
this.color = color;
}
let p = new Person(); // person ctor
let v = new Vegetable('orange');
console.log(v.color); // orange
super
super
表示父对象。
class Father {
constructor(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
money = 0; //总钱数
//挣钱
makeMoney(money) {
this.money += money;
console.log(`这次赚了${money},到目前为止总共已经赚了${this.money}了。`);
}
say() {
console.log(`我叫${this.name},今年${this.age}岁。`);
}
}
class Son extends Father {
constructor(name, age, sex) {
super(name, age, sex); //super作为父类构造函数只能用在子类构造函数中
}
say() {
//super(); //报错
super.say();
console.log('我是我父亲的儿子,还在读大学');
}
}
let son = new Son('张三', 20, '男');
son.makeMoney(1000);
son.makeMoney(100);
son.say();
//人(基类)基本类
class Person {
constructor(name, age, sex, skin, country) {
this.name = name;
this.age = age;
this.sex = sex;
this.skin = skin;
this.country = country;
}
//定义的函数(自我介绍)
introduction() {
console.log(`我叫${this.name},今年${this.age}岁了,性别:${this.sex},国籍:${this.country},肤色:${this.skin}`);
}
}
//黄种人
class YellowPerson extends Person {
constructor(name, age, sex, country) {
super(name, age, sex, '黄色', country);
}
}
//中国人
class ChinesePerson extends YellowPerson {
constructor(name, age, sex) {
super(name, age, sex, '中国');
}
//自我介绍
introduction() {
super.introduction();
console.log(`我们都会功夫,我们是世界第一!`);
}
}
let yellowPerson = new YellowPerson('张三', 20, '男', '新加坡');
let chinesePerson = new ChinesePerson('李四', 30, '男');
yellowPerson.introduction();
chinesePerson.introduction();
继承
ES6
类支持单继承。使用extends
关键字,就可以继承任何拥有[[Construct]]
和原型的对象。很大程度上,这意味着不仅可以继承一个类,也可以继承普通的构造函数(保持向后兼容)。
class Father {
money = 10000000000; //百亿家产
houses = 10; //10栋别墅
cars = 100; //100辆车
makeMoney(money) {
console.log(`又赚了${money}元`);
this.money += money;
}
}
class Son extends Father { }
let son = new Son();
console.log(son.money, son.houses, son.cars);
son.makeMoney(10000);
console.log(son.money, son.houses, son.cars);
console.log(son.hasOwnProperty('money'), son.hasOwnProperty('houses'), son.hasOwnProperty('cars'));
console.log(son instanceof Son);
console.log(son instanceof Father);
单继承
一次只能继承一个父类,就好比一个人只能有一个父亲。但是可以嵌套继承,就好比儿子继承父亲,父亲继承爷爷。
//爷爷
class GrandPa { }
//父亲
class Father extends GrandPa{ }
class Son extends Father, GrandPa { } //错误
class Son extends Father extends Object { } //错误
class Son extends Father, extends Object { } //错误
class Son extends Father { } //正确,同时拥有了爷爷和父亲的属性
//爷爷
class GrandPa {
house = 10;
}
//父亲
class Father extends GrandPa {
cars = 10;
tellMe() {
console.log(`我继承了我父亲的${this.house}套房子`);
}
}
//儿子
class Son extends Father {
tellMe() {
console.log(`我不用劳动就继承了我爷爷的${this.house}套房子和我父亲的${this.cars}辆车子`);
}
}
var father = new Father;
var son = new Son;
father.tellMe();
son.tellMe();
**思考:**可以无限继承吗?
**练习:**用继承实现dialog
、alert
、confirm
等效果。
function Dialog(msg) {
if (!(this instanceof Dialog)) {
return new Dialog(msg);
}
this.id = Dialog.id++;
this.Dialogs.push(this);
this.init(msg);
}
//所有的弹出框对象
Dialog.prototype.Dialogs = [];
//计算底部位置
Dialog.prototype.getHeight = function () {
return this.container.clientHeight;
}
//使用观察者模式告知删除
//接收通知
Dialog.prototype.receive = function (height) {
this.resetTop(height);
}
//通知
Dialog.prototype.notify = function () {
const height = -(this.getHeight() + 10);
this.Dialogs.forEach(item => {
if (item.id > this.id) {
item.receive(height);
}
});
}
//计算所有容器的高度总和
Dialog.prototype.calcAllHeight = function () {
let height = 0;
this.Dialogs.forEach(item => {
if (item.id !== this.id) {
height += item.getHeight();
}
});
return height;
}
Dialog.prototype.resetTop = function (top) {
if (!isNaN(top)) {
const originHeight = parseInt(this.container.style.top.replace('px')); //原始高度
top += originHeight;
} else {
top = this.calcAllHeight() + 10 * this.Dialogs.length;
}
//改变容器位置
this.container.style.top = top + 'px'
}
Dialog.prototype.init = function (content) {
this.container = document.createElement('div'); //创建一个div容器
this.container.style = `background: yellow;padding: 10px; border: #000000 1px solid; position: fixed;z-index: 99999;left: 43%;`;
this.header = document.createElement('div'); //头部容器
this.header.style = 'text-align:right;font-size:15px;';
this.closeButton = document.createElement('label'); //关闭按钮
this.closeButton.innerText = 'X';
this.closeButton.style = 'font-weight:bold;';
this.closeButton.onclick = () => {
this.remove();
};
this.header.appendChild(this.closeButton);
this.content = document.createElement('div'); //创建一个div用于显示content内容
this.content.innerHTML = content;
this.resetTop();
this.container.appendChild(this.header);
this.container.appendChild(this.content);
document.body.appendChild(this.container); //将容器加入到body最后
}
Dialog.prototype.remove = function () {
const index = this.Dialogs.findIndex(c => c.id === this.id);
this.Dialogs.splice(index, 1);
this.notify(); //通知下边的提示框已删除
this.container.parentElement.removeChild(this.container); //移除弹出框
}
Dialog.id = 0;
let d1 = new Dialog('这是第一个弹出框');
let d2 = Dialog('这是第二个弹出框');
let d5 = Dialog(`<table border="1">
<tr>
<td>姓名</td>
<td>年龄</td>
</tr>
<tr>
<td>张三</td>
<td>20</td>
</tr>
<tr>
<td>李四</td>
<td>30</td>
</tr>
</table>`);
//弹出提示框
function MyAlert(msg, type) {
Dialog.call(this, msg); //将Dialog的this指向到当前实例对象上,所以使用this相当于给当前实例对象定义属性
this.setIcon(type);
}
//提示框继承Dialog,在Dialog的原基础上新增或修改
MyAlert.prototype = Object.create(Dialog.prototype);
MyAlert.prototype.constructor = MyAlert;
MyAlert.prototype.setIcon = function (type) {
this.icon = document.createElement('label');
this.icon.innerText = type === 'fail' ? '×' : '√';
this.icon.style = 'color:red;font-size:20px';
this.container.insertBefore(this.icon, this.content); //将图标加入到内容的前面
this.okButton = document.createElement('input');
this.okButton.type = 'button';
this.okButton.value = '确定';
//把内容容器改为inline-block
this.content.style.display = 'inline-block';
//将头部隐藏,因为不需要关闭按钮了
this.header.style.display = 'none';
//改变容器位置
this.resetTop();
//点击确定按钮时隐藏
this.okButton.onclick = () => {
this.remove();
}
this.buttonsContainer = document.createElement('div'); //用于存放按钮的容器
this.buttonsContainer.appendChild(this.okButton);
this.buttonsContainer.style = 'text-align:right;';
this.container.appendChild(this.buttonsContainer);
}
let d3 = new MyAlert('这是第一个失败的弹出框', 'fail');
let d4 = new MyAlert('这是第一个成功弹出框', 'success');
//询问对话框
function MyConfirm(msg, title) {
MyAlert.call(this, msg);
this.setTitle(title);
}
//询问对话框可以继承提示框,在提示框的原基础上新增或修改
MyConfirm.prototype = Object.create(MyAlert.prototype);
MyConfirm.prototype.constructor = MyAlert;
//设置标题
MyConfirm.prototype.setTitle = function (title = '提示') {
this.title = document.createElement('div');
this.title.innerText = title;
this.title.style = 'font-size:15px;font-weight:bold;';
this.container.insertBefore(this.title, this.icon);
//改变一下icon的内容
this.icon.innerText = '?';
//改变容器位置
this.resetTop();
this.cancelButton = document.createElement('input');
this.cancelButton.type = 'button';
this.cancelButton.value = '取消';
this.cancelButton.onclick = () => {
console.log('取消');
this.remove();
};
this.buttonsContainer.appendChild(this.cancelButton);
}
let c1 = new MyConfirm('你确定要删除吗?');
let c2 = new MyConfirm('你确定要删除吗?', '请看清楚');