目录
js对象模型:... 1
定义类:... 1
ES6之前:... 2
ES6中的class:... 4
重写方法:... 5
静态属性:... 6
静态方法:... 6
this的坑:... 7
解决对象中函数返回this指针不一样问题:... 9
1、显式传入,通过主动传入对象,就避开了this的问题:... 9
2、ES3(ES-262第三版)引入了apply、call方法:... 9
3、ES5引入了bind方法:... 10
4、ES6引入支持this的箭头函数:... 11
高阶对象(高阶类,或称mixin模式);... 12
js对象模型:
js是一种基于prototype原型的面向对象语言,而不是基于类的面向对象语言;
C++和JAVA有class和instance的概念,是一类事物的抽象,而实例是类的实体;
js是基于prototype的语言,它只有原型对象的概念,原型对象就是一个模板,新的对象从这个模板构建从而获取最初的属性,任何对象在运行时可动态的增加属性,且任何一个对象都可作为另一个对象的原型,这样后者可共享前者的属性;
定义类:
字面式声明方式(字面值创建对象):
js1.2开始支持,类似py的字典,另稍变化就是json;
var obj = {
property_1: value_1,
property_2: value_2,
...,
"property_n": value_n
}
property may be an identifier,or a number,or a string
例:
var obj = {
a:'abc',
'b': 'bcd'
};
console.log(obj.a);
console.log(obj.b);
输出:
abc
bcd
例:
function A(x) {
console.log('A class');
this.x = x; //parameters,借助this创建对象属性
}
console.log(typeof(A));
a = A(100); //普通函数
console.log(a);
a = new A(100); //借助new关键字,把A函数作为原型构建出实例,A函数即构造器
console.log(a);
输出:
function
A class
undefined
A class
A { x: 100 }
ES6之前:
定义一个函数(构造器)对象,使用this定义属性;
用new关键字和构造器创建一个新对象;
用new构建一个新的通用对象,new操作符会将新对象的this值传递给Point3D函数,函数为这个对象创建z属性;
new后得到一个对象,用这个对象的this来调用构造器;
如何执行基类的构造方法,用call()方法,传入子类的this,即Point3D对象的this来执行Point3D的构造器;
例:
function Point(x,y) {
this.x = x;
this.y = y;
this.show = () => console.log(this,this.x,this.y);
}
console.log(Point);
p1 = new Point(4,5); //类的实例化;若用p1=Point(4,5),可理解为py中返回值为None,None.show报错
console.log(p1);
p1.show();
function Point3D(x,y,z) {
Point.call(this,x,y); //继承,用call()
this.z = z;
console.log('Point3D...');
}
console.log(Point3D);
p2 = new Point3D(7,8,9); //类的实例化
console.log(p2);
p2.show(); //继承的show
输出:
[Function: Point]
Point { x: 4, y: 5, show: [Function] }
Point { x: 4, y: 5, show: [Function] } 4 5
[Function: Point3D]
Point3D...
Point3D { x: 7, y: 8, show: [Function], z: 9 }
Point3D { x: 7, y: 8, show: [Function], z: 9 } 7 8
ES6中的class:
从ES6开始,提供了class关键字,使得创建对象更加简单、清晰;
1、类定义使用class关键字,创建的本质上还是函数,是一个特殊的函数;
2、一个类只能拥有一个名为constructor的构造器方法,类似py的__init__(),如果没有显式的定义一个构造方法,则会添加一个默认的constructor方法;
3、继承使用extends关键字;
4、一个构造器可使用super关键字来调用一个父类的构造函数;
5、类没有私有属性,class语句块中不允许使用let、var等关键字定义私有变量;
例:
class Point { //class语句块中不允许使用let、var等关键字定义私有变量
constructor (x,y) {
this.x = x;
this.y = y;
}
show() {
console.log(this,this.x,this.y);
}
}
p1 = new Point(4,5);
console.log(p1);
p1.show();
class Point3D extends Point {
constructor (x,y,z) {
super(x,y);
this.z = z;
}
// show() { //重写
// console.log(this,this.x,this.y,this.z);
// }
show() { //重写,方式1
super.show();
console.log(this.z);
}
}
p2 = new Point3D(7,8,9);
console.log(p2);
p2.show()
输出:
Point { x: 4, y: 5 }
Point { x: 4, y: 5 } 4 5
Point3D { x: 7, y: 8, z: 9 }
Point3D { x: 7, y: 8, z: 9 } 7 8
9
重写方法:
子类Point3D的show方法需要重写;
方式1:
用方法方式定义,函数名同名覆盖即可,和参数没关系;
如果需要使用父类的方法,用super.method()的方式调用,如super.show();
方式2:
用属性方式定义,用箭头函数方式;
父类中若用属性定义,子类应和父类一致;
若两种方式都有,优先使用属性定义的;
若父类和子类中只用方法定义了,则子类的show()覆盖了父类的show();
总结:
父类、子类使用同一种方式的类定义方法,子类覆盖父类;
若父类使用属性方式,子类使用方法方式,则使用父类的属性方式;
若父类使用方法方式,子类使用属性方式,则使用子类的属性方式;
一句话,优先使用属性;
例:
class Point {
constructor (x,y) {
this.x = x;
this.y = y;
}
show() {
console.log(this,this.x,this.y);
}
}
class Point3D extends Point {
constructor (x,y,z) {
super(x,y);
this.z = z;
this.show = () => console.log('show',this.x,this.y,this.z);
}
show() {
super.show();
console.log('===', this.z);
}
}
p2 = new Point3D(7,8,9);
console.log(p2);
p2.show()
输出:
Point3D { x: 7, y: 8, z: 9, show: [Function] }
show 7 8 9
静态属性:
目前还没得到很好的支持;
静态方法:
在方法名前加上static;
实例不能直接访问静态方法,和C++、java一致;
实例要通过constructor访问静态方法,如p2.constructor.print();;
静态的概念与py的静态不同,相当于py的类变量;
例:
class Point3D extends Point {
constructor (x,y,z) {
super(x,y);
this.z = z;
this.show = () => console.log('show',this.x,this.y,this.z);
}
show() {
super.show();
console.log('===', this.z);
}
static print() {
return 'Point3D';
}
}
p2 = new Point3D(7,8,9);
console.log(Point3D.print());
// console.log(p2.print()); //X,实例访问静态方法要通过constructor,p2.constructor.print()
console.log(p2.constructor.print());
// Point3D.show() //X
输出:
Point3D
Point3D
this的坑:
js、c、java一样有this,但js的表现是不同的;
c、java是静态编译型语言,this是编译期绑定;
js是动态语言,运行期绑定;
js中,函数执行时,会开启新的执行上下文环境ExecutionContext;
创建this属性,具体this是什么要看函数是怎么调用了;
1、myFunction(1,2,3),普通函数调用方式,this是指向global对象,nodejs的global或browser的window;
2、myObject.myFunction(1,2,3),对象方法的调用方式,this指向包含该方法的对象;
3、call和apply方法调用,要看第一个参数是谁;
例:
先用function定义函数,不要用箭头函数;
var school = {
name:'magedu',
getNameFunc:function() {
console.log(this.name); //此处的this相当于py的self(替代构建出的实例本身),相当于school
console.log(this);
return function() {
console.log(this == global); //true,此处的this与school无关,两处的this指针不一样了,是普通函数调用,普通函数调用和对象的调用是两码事;解决1,在call()方法中指定school对象;解决2,显式传参
return this.name;
};
}
};
console.log(school.getNameFunc()());
输出:
magedu
{ name: 'magedu', getNameFunc: [Function: getNameFunc] }
true
undefined
例:
method = school.getNameFunc;
func = method.call(school);
console.log(func()) //这三句等价于console.log(school.getNameFunc()());
method = school.getNameFunc();
console.log(method()) //这2句等价于console.log(school.getNameFunc()());
例:
method = school.getNameFunc; //这样会把this丢了
func = method();
console.log(func.call(school));
例:
method = school.getNameFunc();
console.log(method.call(school));
输出:
magedu
{ name: 'magedu', getNameFunc: [Function: getNameFunc] }
false
magedu
解决对象中函数返回this指针不一样问题:
bind方法常用;
1、显式传入,通过主动传入对象,就避开了this的问题:
var school = {
name:'magedu',
getNameFunc:function () {
console.log(this.name);
console.log(this);
return function (that) {
console.log(that == global);
return that.name;
};
}
};
console.log(school.getNameFunc()(school));
输出:
magedu
{ name: 'magedu', getNameFunc: [Function: getNameFunc] }
false
magedu
2、ES3(ES-262第三版)引入了apply、call方法:
apply、call方法都是函数对象的方法,第一参数都是传入对象引入的;
查看apply、call、bind源码;
apply传其它参数需要使用数组,如func.apply(school,[1,2,3]);;
call传其它参数需要使用可变参数收集,如func.call(school,1,2,3);;
var school = {
name:'magedu',
getNameFunc:function () {
console.log(this.name);
console.log(this);
return function () {
console.log(this == global);
return this.name;
};
}
};
// console.log(school.getNameFunc().call(school));
console.log(school.getNameFunc().apply(school));
输出:
magedu
{ name: 'magedu', getNameFunc: [Function: getNameFunc] }
false
magedu
例:
function Print() {
this.print = function (x,y) {console.log(x+y)};
}
p = new Print(1,2);
p.print(10,20);
p.print.call(p,10,20);
p.print.apply(p,[10,20]);
输出:
30
30
30
3、ES5引入了bind方法:
bind方法来设置函数的this值;
bind后返回一个新的函数,要再次调用,像py的partial返回function;
apply、call,调用时第一个参数传入this,之后的传入的其它参数不同,apply要求是数组,call要求是可变参数;
bind是先绑定this,调用时直接用;
var school = {
name:'magedu',
getNameFunc:function () {
console.log(this.name);
console.log(this);
return function () {
console.log(this == global);
return this.name;
};
}
};
// console.log(school.getNameFunc().bind(school)); //X,此种写法不对,bind方法绑定后会返回新的函数
var method = school.getNameFunc();
// console.log(method);
var boundmethod = method.bind(school); //bind绑定后返回新的函数
// console.log(boundmethod);
console.log(boundmethod());
输出:
magedu
{ name: 'magedu', getNameFunc: [Function: getNameFunc] }
false
magedu
4、ES6引入支持this的箭头函数:
var school = {
name:'magedu',
getNameFunc:function () {
console.log(this.name);
console.log(this);
return () => {
console.log(this == global);
return this.name;
};
}
};
console.log(school.getNameFunc()());
// method = school.getNameFunc(); //等价于console.log(school.getNameFunc()());
// console.log(method());
输出:
magedu
{ name: 'magedu', getNameFunc: [Function: getNameFunc] }
false
magedu
ES6新的定义方式:
class school {
constructor () {
this.name = 'magedu';
}
getNameFunc() {
console.log(this.name);
console.log(this);
return () => {
console.log(this == global);
return this.name;
};
}
}
console.log(new school().getNameFunc()()); //class定义的必须用new实例化
// method = new school(); //等价于console.log(new school().getNameFunc()());
// func = method.getNameFunc();
// console.log(func());
输出:
magedu
school { name: 'magedu' }
false
magedu
高阶对象(高阶类,或称mixin模式);
mixin混合模式,这是一种不用继承就可复用的技术,主要为解决多重继承的问题,多继承的继承路径是个问题;
js是基于对象的,类和对象都是对象模板;
mixin指将一个对象的全部或部分拷贝到另一个对象上去,其实就是属性了;
可将多个类或对象混合成一个类或对象;
例,继承实现:
class Serialization {
constructor () {
console.log('serialization constructor');
if (typeof(this.stringify) !== 'function') {
throw new ReferenceError('should define stringify');
}
}
}
class Point extends Serialization {
constructor (x,y) {
super();
console.log('point constructor');
this.x = x;
this.y = y;
}
stringify () {
return `
}
}
class Point3D extends Point {
constructor (x,y,z) {
super(x,y); //必须显式的调用父类的构造器,惯例,子类的constructor的第一句就是super();这点与java不一样,java中如果不写此句会隐式的调用父类的构造器
this.z = z;
}
stringify () {
return `
}
}
// s = new Serialization(); //父类中无stringify,报错
p1 = new Point(4,5);
console.log(p1.stringify());
p2 = new Point3D(7,8,9);
console.log(p2.stringify());
输出:
serialization constructor
point constructor
serialization constructor
point constructor
例,高阶对象实现:
将类的继承,构建成箭头函数;
class Ojbect {
constructor () {
console.log('object constructor');
}
}
class A extends Ojbect {}; //普通继承
console.log(A);
const B = class extends Object { //匿名继承
constructor () {
super();
console.log('B constructor');
}
}
console.log(B);
b = new B(); //实例化
// const C = function Sup() {
// return class extends Sup { //等价于return X = class extends Sup {};
// constructor () {
// super();
// console.log('C construcotr');
// }
// };
// };
// const C = Sup => {
// return class extends Sup {
// constructor () {
// super();
// console.log('C construcotr');
// }
// };
// };
const C = Sup => class extends Sup { //箭头函数,参数是类,返回值也是类;注意以上几种形式的变化
constructor () {
super();
console.log('C constructor');
}
};
c = console.log(C);
cls = C(Object); //C本身是一普通函数,在调用后才返回一个带constructor的类,才可new,所以c = new C(Object)会报错;
c = new cls();
console.log(c);
输出:
[Function: A]
[Function: B]
B constructor
[Function: C]
C constructor
{}
例:
const Serialization = Sup => class extends Sup {
constructor (...args) {
super(...args);
console.log('serialization constructor');
if (typeof(this.stringify) !== 'function') {
throw ReferenceError('should define stringify');
}
}
};
class Point {
constructor (x,y) {
console.log('point constructor');
this.x = x;
this.y = y;
}
stringify () {
return `
}
}
class Point3D extends Serialization(Point) { //Serialization(Point)是一个匿名箭头函数调用,返回一个新的类型,高阶类,Point3D继承这个新的匿名类型,增强了功能,React框架大量采用了这种mixin技术
constructor (x,y,z) {
super(x,y);
console.log('point3d constructor');
this.z = z;
}
stringify () {
return `
}
}
p2 = new Point3D(4,5,6);
console.log(p2.stringify());
输出:
point constructor
serialization constructor
point3d constructor