对象特性
对象的属性在定义时,都带有一些特征值,js通过这些特征值定义他们的行为
这些特征值描述对象属性的各种特征,成为对象属性的特性
特性是内部值,放在两对方括号中访问
特性分为数据属性和访问器属性
数据属性:Configurable、Enumerable、Writable、Value
访问器属性:Configurable、Enumerable、Get、Set
定义某个属性的特性:Object.defineProperty(对象名,对象属性名,{特性1:值,特性2:值…})
定义多个属性特性:Object.defineProperties(对象名,{属性1:{特性1:值,特性2:值…},属性2:{特性1:值….}})
读取属性:Object.getOwnPropertyDescriptor(对象名,属性名) 返回一个对象
构造对象
工厂模式
1 | function createobj(name,age){ |
缺点:无法识别对象类型
寄生构造函数
定义样子同工厂模式,只不过创建实例时用new 操作符
将包装函数叫做 构造函数
实例与构造函数及其原型没有关系1
2
3
4
5
6
7
8
9
10
11
12
13function createobj(name,age){ //构造函数
var o=new Object();
o.name = name;
o.age = age;
o.sayName = function(){
console.log(this.name)
};
return o;
}
var person1 = new createobj('gray',25);
person1.sayName()//gray
console.log(person1 instanceof createobj) //false
console.log(person1 instanceof Object) //true
应用:为无法修改构造函数的对象添加方法1
2
3
4
5
6
7
8
9
10
11//给数组增加特殊方法
function specialArray(){
var values = new Array();
values.push.apply(values,arguments);
values.toPiedString = function(str){ //特殊方法
return this.join(str);
};
return values;
}
var colors = new specialArray('red','blue');
console.log(colors.toPiedString('|'));//"red|blue"
构造函数
1 | function createobj(name,age){ |
1、构造函数的实例均指向构造函数。例:person1、person3的constructor均指向createobj)
2、不使用new 关键字调用构造函数,构造函数的方法和属性会挂到window上面。例:person2
3、定义在构造函数上的方法,创建不同实例后,不同实例各自拥有自己的该方法,不同实例之间构造函数方法不相等,不共享
使用构造函数创建对象,如果相让对象方法在不同实例实现共享,则在定义方法时,采用引用函数的方法1
2
3
4
5
6
7
8
9
10
11function createobj(name,age){
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName(){
console.log(this.name)
}
var person1 = new createobj('gray',25);
var person3 = new createobj('orange',25);
console.log(person1.sayName == person3.sayName)//true
缺点:若对象需要定义多个方法则需要创建多个全局函数供构造函数引用,失去封装性
原型模式
在对象的prototype中定义的属性和方法,可以在所有实例中共享
定义方法一1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36function createobj(name,age){}
createobj.prototype.name = 'black';
createobj.prototype.age = 26;
createobj.prototype.sayName = function(){
console.log(this.name)
}
var person1 = new createobj();
var person2 = new createobj();
person1.sayName();//black
person2.sayName();//black
console.log(person1.sayName == person2.sayName)//true
console.log(person1.constructor == createobj)//true
console.log(person2.constructor == person1.constructor)//true
console.log(person1.age) //26 来自原型
person1.age = 27; //相当于创建了一个实例属性
console.log(person1.age) //27 来自实例属性
console.log(person2.age) //26 来自原型
delete person1.age; //删除实例属性
console.log(person1.age) //26 来自原型
delete person1.age; //不能删除原型属性
console.log(person1.age) //26
console.log(person2.age) //26
console.log(person1 == createobj.prototype)//false
console.log(person1.prototype == createobj.prototype)//false
console.log(createobj.prototype.isPrototypeOf(person1))//true
console.log(person1.prototype) //undefined
console.log(Object.getPrototypeOf(person1));//createobj.prototype
console.log(person1.hasOwnProperty('name'));//false
console.log('name' in person1);//true
console.log(Object.keys(createobj.prototype));//["name", "age", "sayName"]
console.log(Object.getOwnPropertyNames(createobj.prototype));//["constructor", "name", "age", "sayName"]
1、实例的内部指针[[Prototype]] 会指向构造函数的原型对象,它是不能直接访问的
2、构造函数的prototype也会指向构造函数的原型对象,但他是构造函数的一个属性,可以直接访问
3、Object.getPrototypeOf(实例名); 返回实例的原型对象 .即[[Prototype]]指向的对象
4、实例名.hasOwnProperty(属性名字符串) ; 只检查实例的实例属性中是否有该属性,没有的话,返回false,可用于检查该属性在实例属性中,还是在原型属性中
5、在获取属性值时,实例属性中有该属性就取该实例属性的值,没有则用原型属性值,原型属性也没有的话则返回undefined
6、实例属性可用delete删除,实例无法更改原型属性的值
7、属性名字符串 in 实例名; 检查实例和原型所有属性中是否有该属性,有的话就返回true;for-in循环可枚举的属性,包括实例属性和原型属性
8、Object.keys(对象名);返回对象可枚举属性的字符串数组
9、Object.getOwnPropertyNames(对象名);返回所有对象的实例属性,无论它是否枚举
定义方法二1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function createobj(){}
createobj.prototype={
name:'seven',
sayName:function(){
console.log(this.name)
}
};
var person1 = new createobj();
person1.sayName();//seven
console.log(person1.constructor == createobj);//false
console.log(person1.constructor == createobj.prototype);//false
console.log(person1.constructor == Object);//true
console.log(person1.constructor == createobj.prototype.constructor);//true
console.log(person1.constructor);//function object(){}
console.log(person1 instanceof Object);//true
console.log(person1 instanceof createobj);//true
1、将挂在构造函数原型上的属性以一个对象的形式,一起赋给构造函数原型,即构造函数原型被赋值为一个对象
2、构造函数原型被Object创建,实例、构造函数的原型对象的构造函数为Object对象
3、若在定义原型对象时,指定原型对象的constructor,则对象实例的constructor也会跟着指到相应的值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function createobj(){}
createobj.prototype={
constructor:createobj, //明确指出,同时特性的枚举属性会被设置为true,默认为false
name:'seven',
sayName:function(){
console.log(this.name)
}
};
var person1 = new createobj();
person1.sayName();//seven
console.log(person1.constructor == createobj);//true
console.log(person1.constructor == createobj.prototype);//false
console.log(person1.constructor == Object);//false
console.log(person1.constructor == createobj.prototype.constructor);//true
console.log(person1.constructor);//function createobj(){}
console.log(person1 instanceof Object);//true
console.log(person1 instanceof createobj);//true
4、 实例中的指针指向原型,不指向构造函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function createobj(){}
var person1 = new createobj();
console.log(person1 instanceof createobj);//true
createobj.prototype={
constructor:createobj,
name:'seven',
sayName:function(){
console.log(this.name)
}
};
//person1.sayName();//person1.sayName is not a function
console.log(person1.constructor == createobj);//true
console.log(person1.constructor == createobj.prototype);//false
console.log(person1.constructor == Object);//false
console.log(person1.constructor == createobj.prototype.constructor);//true
console.log(person1.constructor);//function createobj(){}
console.log(person1 instanceof Object);//true
console.log(person1 instanceof createobj);//false
对象生成实例后,再去重写构造函数的原型对象,会将构造函数的原型对象指向后来被赋的对象,
切断与旧的原型对象之间的关系,新的原型对象与实例没有任何关系,实例仍引用旧的原型对象
原型方法可用于修改添加原生对象的属性方法
缺点:在原型对象prototype中的属性方法全都共享,当更改一个实例中的属性值时,其他实例一起变1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29//定义一
function createobj(name,age){}
createobj.prototype.name = [1,2];
createobj.prototype.sayName = function(){
console.log(this.name)
}
var person1 = new createobj();
var person2 = new createobj();
person1.sayName();//[1, 2]
person2.sayName();//[1, 2]
person1.name.push(3);
person1.sayName();//[1, 2, 3]
person2.sayName();//[1, 2, 3]
//定义二
function createobj(){}
createobj.prototype={
constructor:createobj,
name:[1,2],
sayName:function(){
console.log(this.name)
}
};
var person1 = new createobj();
var person2 = new createobj();
console.log(person1.name)//[1, 2]
console.log(person2.name)//[1, 2]
person1.name.push(3);
console.log(person1.name)//[1, 2, 3]
console.log(person2.name)//[1, 2, 3]
构造函数+原型模式
构造函数定义实例属性,
原型模式定义方法和共享属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function createobj(name,age){
this.name = name;
this.age = age;
this.friends = [1,2];
}
createobj.prototype={
constructor:createobj,
sayName:function(){
console.log(this.name)
}
};
var person1 = new createobj('halo',24);
var person2 = new createobj('jack',25);
person1.friends.push(3);
console.log(person1.friends);//[1, 2, 3]
console.log(person2.friends);//[1, 2]
是用来定义引用类型的一种默认模式
动态原型模式
1 | function createobj(name,age){ |
1、判断sayName函数是否存在的语句,只会在初次调用构造函数的时候运行,即第一次创建实例的时候运行,
运行过后,构造函数原型中就会存在sayName函数,即完成初始化,之后就不会在运行
2、对原型模式定义的方法属性能够在所有实例中立即得到放映
3、如果对构造函数原型使用字面方式重写,将切断已有实例与构造函数原型的联系,
已有实例会指向就原型对象,新建实例会指向新原型对象
4、在一个实例中重写一个原型方法,不会影响原型对象方法,其他实例和新建实例仍会调用原型对象方法
稳妥构造函数
创建对象的实例方法,不使用this
不使用new调用构造函数1
2
3
4
5
6
7
8
9
10
11
12
13
14function createobj(name,age){ //构造函数
var o=new Object();
o.name = name;
o.age = age;
o.sayName = function(){
console.log(this.name);
console.log(this == o)
};
return o;
}
var person1 = createobj('gray',25);
person1.sayName()//gray true
console.log(person1 instanceof createobj) //false
只用通过sayName函数访问传入到构造函数中的原始成员
继承
实例原型链模式
继承函数的原型被赋值为祖先函数的实例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38//祖先函数
function Superfn(){
this.pro = true;
this.color = [1,2];
this.getsuper = function(){
console.log('aaa');
}
}
Superfn.prototype.getsuper=function(){
console.log('bbb');
}
//继承函数
function Subfn(){
this.subpro = false;
this.getsuper = function(){
console.log('000');
}
}
Subfn.prototype = new Superfn();
Subfn.prototype.getsuper = function(){
console.log('ccc')
}
var ins = new Subfn();
ins.getsuper();
console.log(ins instanceof Object); //true
console.log(ins instanceof Superfn); //true
console.log(ins instanceof Subfn); //true
console.log(Object.prototype.isPrototypeOf(ins)); //true
console.log(Superfn.prototype.isPrototypeOf(ins)); //true
console.log(Subfn.prototype.isPrototypeOf(ins)); //true
var ins1 = new Subfn();
console.log(ins.color)//[1,2]
console.log(ins1.color)//[1,2]
ins1.color.push(4);
console.log(ins.color)//[1,2,4]
console.log(ins1.color)//[1,2,4]
1.继承函数实例的属性/方法调用顺序:
自己定义的->继承函数实例->继承函数prototype->祖先函数实例属性->祖先函数prototype
2.因为所有引用类型默认继承object,所以调用toString等方法时,其实调用的的是object的prototype
3.重写原型中祖先函数的方法,一定要在继承函数原型被赋值祖先函数实例之后,相当于用实例方法覆盖原型方法
但注意不能用字面量方法重写继承函数原型,这样会导致继承函数原型重新指向新对象,切断与祖先函数联系
4.因为继承函数prototype指向祖先函数实例时,祖先函数所有属性相当于继承函数prototype属性,
构造函数原型的属性会被所有实例共享,所以创建继承函数实例时,
不能像祖先函数的构造函数中传递参数,因为会导致其他实例同时共享这些参数导致的后果
借用构造函数
使用apply()或者call()方法。在继承函数构造函数中调用祖先函数构造函数,这样,
在new 一个继承函数实例时,就会去执行祖先函数中所有对象初始化代码,每个实例都会具有自己的祖先函数属性的副本1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26function Superfn(time){
this.color = [1,2];
this.time = time;
}
Superfn.prototype.getsuper=function(){
console.log('bbb');
}
function Subfn(){
Superfn.call(this,'11:23:30');
this.age = 29;
}
var ins = new Subfn();
var ins1 = new Subfn();
console.log(ins.color) //[1, 2]
console.log(ins1.color)//[1, 2]
ins1.color.push(4);
console.log(ins.color)//[1, 2]
console.log(ins1.color)//[1, 2, 4]
console.log(ins.time);//"11:23:30"
console.log(ins.age);//29
ins.getsuper();//ins.getsuper is not a function
1、可以像祖先函数构造函数中传递参数;
2、定义继承函数自己的属性必须放在调用祖先函数之后,防止祖先函数重写继承函数属性
缺点: 所有的属性只能使用构造函数模式定义,而且,继承函数实例,不能调用祖先函数原型上的方法
组合继承
在继承函数构造函数中调用祖先函数,实现属性继承
将继承函数的原型赋值为祖先函数实例,实现方法复制1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function Superfn(time){
this.color = [1,2];
this.time = time;
}
Superfn.prototype.getsuper=function(){
console.log('bbb');
}
function Subfn(){
Superfn.call(this,'11:23:30');
this.age = 29;
}
Subfn.prototype= new Superfn();
var ins = new Subfn();
var ins1 = new Subfn();
console.log(ins.color)
console.log(ins1.color)
ins1.color.push(4);
console.log(ins.color)
console.log(ins1.color)
console.log(ins.time);//"11:23:30"
console.log(ins.age);//29
ins.getsuper();//bbb
缺点:调用了两次Superfn(),初始化时,Subfn.prototype中的Superfn的实例属性会被构造函数中创建的Superfn的实例属性覆盖,
重复创建,没必要
原型式继承
Object.creat()方法原理像如下函数
function object(o){
function F(){
F.prototype = o;
}
return new F();
}
原型式继承 即使用Object.creat(祖先对象)定义实例,实例会共享祖先对象属性方法,通过添加第二个参数,覆盖祖先对象属性,或新增属性1
2
3
4
5
6
7
8
9
10
11
12
13
14Superfn={
color:['red','green'],
getsuper:function(){
console.log(222);
}
}
var test = Object.create(Superfn,{getsuper:{value:function(){console.log(111)}}});
var test1 = Object.create(Superfn,{color:{value:[1,2]},name:{value:'jack'}});
console.log(test.color);//['red','green']
console.log(test1.color,test1.name); //[1,2] jack
console.log(test.name);//undefined
test.getsuper(); //111
test1.getsuper();//222
寄生式继承
1 | function createAnthoer(oriobj){ |
最理想继承-寄生组合式继承
对比组合式继承
仅调用一次superfn函数,避免在Subfn.prototype中创建多余superfn实例属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function inhertPrototype(subfn,superfn){
var prototype = Object(superfn.prototype);//创建对象 ,仅继承祖先函数原型
prototype.constructor = subfn;//增强对象
subfn.prototype = prototype;//指定对象
}
function Superfn(name){
this.color = [1,2];
this.name = name;
}
Superfn.prototype.getsuper=function(){
console.log('bbb');
}
function Subfn(name,age){
Superfn.call(this,name); //仅继承祖先函数实例属性,不会继承祖先函数原型
this.age = age;
}
Subfn.prototype.getsuper=function(){
console.log('ccc');
}
inhertPrototype(subfn,superfn);
类
类的定义相当于es5 对象构造函数的另一种写法,一种新语法
es5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面
es6 类的继承,实质是先创造父类的实例对象this(必须先调用super方法),然后再用子类的构造函数修改this
类的定义
1 | //es5 |
1.constructor函数定义实例属性,this代表实例对象,生成对象实例时,自动调用该方法,
默认返回实例对象this,也可以指定返回另外一个对象,则生成的实例将是另外这个对象的实例
如果没有显式定义,一个空的constructor方法会被默认添加。
其他在constructor函数之外定义的函数全都挂在类的prototype对象上(没有定义在this上的全都定义在class上)
其实constructor函数也挂在prototype对象上
2.定义原型方法时,不需要加上function这个关键字,方法之间不需要逗号分隔
3.类的数据类型就是函数,类本身就指向构造函数
4.使用Object.assign()新增原型方法,定义类时定义的原型方法是不可枚举的,但Object.assign()新增的可枚举
5.可以使用变量值做原型方法名
6.定义实例仍使用new关键字,且必须使用new定义,否则会报错
7.类的所有实例共享一个原型对象,使用实例的proto属性改写原型,会改变“类”的原始定义,影响到所有实例
8.类也可以使用表达式的形式定义,采用 Class 表达式,可以写出立即执行的 Class
9.类必须先定义再使用,无论是生成实例还是进行继承
10.类的name属性总是返回紧跟在class关键字后面的类名。
11.对某个属性设置对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。存值函数和取值函数是设置在属性的 Descriptor 对象上的。
12.静态方法,该方法不会被实例继承,而是直接通过类来调用
如果静态方法包含this关键字,这个this指的是类,而不是实例
父类的静态方法,可以被子类继承。
静态方法也是可以从super对象上调用的
13.
ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
类的继承
定义
1 | class Point { |
1、使用extends关键字进行继承:class 子类名 extends 父类名{}
2、在子类构造函数constructor中使用super关键字继承父类this对象,必需在super语句之后使用this
3、Object.getPrototypeOf方法可以用来从子类上获取父类
4、子类的proto属性,表示构造函数的继承,总是指向父类。
子类prototype属性的proto属性,表示方法的继承,总是指向父类的prototype属性。
5、子类的原型的原型,是父类的原型,通过子类实例的proto.proto属性,可以修改父类实例的行为
关于super关键字
1、用作函数
作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错,代表调用父类构造函数
super()在这里相当于parent.prototype.constructor.call(this),this指子类
2、用作对象
作为对象使用时,在普通方法中指向父类原型对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28class A {
constructor() {
this.p = 2;
}
q() {
console.log(this.p)
}
}
class B extends A {
constructor() {
super();
this.p =3
super.q();
super.p = 5;
console.log(super.p); // undefined
console.log(this.p); // 5
super.tt = 6;
console.log(this.tt); // 6
console.log(super.valueOf() instanceof B); // true
}
get m() {
return super.p;
}
}
let b = new B();//3
console.log(b.m) // undefined
在普通方法中指向父类原型对象,即使用super.xxxx(),相当于使用parent.prototype.xxxx(),
所以只能调用父类原型属性方法,不能使用实例属性方法
通过super调用父类的方法时,super会绑定子类的this,
即在子类使用super.xxxx()时,xxxx()里面的this指子类
通过super对某个属性赋值,赋值的属性会变成子类实例的属性
在静态方法中使用super将指向父类,而不是父类的原型对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg);
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1 super在静态方法之中指向父类
var child = new Child();
child.myMethod(2); // instance 2 在普通方法之中指向父类的原型对象。
extends特殊对象
第一种特殊情况,子类继承Object类。1
2
3
4
5class A extends Object {
}
A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true
这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例。
第二种特殊情况,不存在任何继承。1
2
3
4
5class A {
}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true
这种情况下,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回一个空对象(即Object实例),所以A.prototype.proto指向构造函数(Object)的prototype属性。
第三种特殊情况,子类继承null。1
2
3
4
5class A extends null {
}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === undefined // true
这种情况与第二种情况非常像。A也是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回的对象不继承任何方法,所以它的proto指向Function.prototype,即实质上执行了下面的代码。1
2
3class C extends null {
constructor() { return Object.create(null); }
}
第四种情况,允许继承原生构造函数定义子类,但无法通过super方法向父类Object传参1
2
3
4
5
6
7class NewObj extends Object{
constructor(){
super(...arguments);
}
}
var o = new NewObj({attr: true});
console.log(o.attr === true) // false
Mixin 模式
使用如下mix函数将多个对象合成为一个类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27function mix(...mixins) {
class Mix {}
for (let mixin of mixins) {
copyProperties(Mix, mixin);
copyProperties(Mix.prototype, mixin.prototype);
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if ( key !== "constructor"
&& key !== "prototype"
&& key !== "name"
) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
//使用:继承返回的合成类
class DistributedEdit extends mix(Loggable, Serializable) {
// ...
}
几种创建方式对比
let a = null ===> null
let b = Object.create(null) ===> {} 但没有任何属性
let c = {} ===> {} proto指向Object
let d = new Object() ===> {} proto指向Object
let e = new Object(null) ===> {} proto指向Object
let f = Object.create(c) ===> {} proto指向c, proto.proto指向Object
!(obj4)[/image/ob4.png]
c instanceof Object , d instanceof Object ,e instanceof Object ===> true
let dd = {a:1}
let cc = new Object(dd)
let ee = Object.create(dd)
console.log(cc,ee)
!(obj3)[/image/ob3.png]
dd.isPrototypeOf(cc) ===>false
dd.isPrototypeOf(ee) ===>true