目录

js对象模型:... 1

定义类:... 1

ES6之前:... 2

ES6中的class... 4

重写方法:... 5

静态属性:... 6

静态方法:... 6

this的坑:... 7

解决对象中函数返回this指针不一样问题:... 9

1、显式传入,通过主动传入对象,就避开了this的问题:... 9

2ES3ES-262第三版)引入了applycall方法:... 9

3ES5引入了bind方法:... 10

4ES6引入支持this的箭头函数:... 11

高阶对象(高阶类,或称mixin模式);... 12

 

 

 

js对象模型:

 

js是一种基于prototype原型的面向对象语言,而不是基于类的面向对象语言;

C++JAVAclassinstance的概念,是一类事物的抽象,而实例是类的实体;

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中返回值为NoneNone.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语句块中不允许使用letvar等关键字定义私有变量;

 

例:

class Point {   //class语句块中不允许使用letvar等关键字定义私有变量

    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

 

 

 

重写方法:

子类Point3Dshow方法需要重写;

 

方式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,实例访问静态方法要通过constructorp2.constructor.print()

console.log(p2.constructor.print());

// Point3D.show()   //X

输出:

Point3D

Point3D

 

 

 

 

this的坑:

jscjava一样有this,但js的表现是不同的;

cjava是静态编译型语言,this是编译期绑定;

js是动态语言,运行期绑定;

 

js中,函数执行时,会开启新的执行上下文环境ExecutionContext

 

创建this属性,具体this是什么要看函数是怎么调用了;

1myFunction(1,2,3),普通函数调用方式,this是指向global对象,nodejsglobalbrowserwindow

2myObject.myFunction(1,2,3),对象方法的调用方式,this指向包含该方法的对象;

3callapply方法调用,要看第一个参数是谁;

 

 

例:

先用function定义函数,不要用箭头函数;

var school = {

    name:'magedu',

    getNameFunc:function() {

        console.log(this.name);   //此处的this相当于pyself(替代构建出的实例本身),相当于school

        console.log(this);

        return function() {

            console.log(this == global);   //true,此处的thisschool无关,两处的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

 

 

 

2ES3ES-262第三版)引入了applycall方法:

applycall方法都是函数对象的方法,第一参数都是传入对象引入的;

查看applycallbind源码;

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

 

 

 

3ES5引入了bind方法:

bind方法来设置函数的this值;

bind后返回一个新的函数,要再次调用,像pypartial返回function

 

applycall,调用时第一个参数传入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

 

 

 

4ES6引入支持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 `<point ${this.x}.${this.y}>`;

    }

}

 

class Point3D extends Point {

    constructor (x,y,z) {

        super(x,y);   //必须显式的调用父类的构造器,惯例,子类的constructor的第一句就是super();这点与java不一样,java中如果不写此句会隐式的调用父类的构造器

        this.z = z;

    }

    stringify () {

        return `<point3D ${this.x}.${this.y}.${this.z}>`;

    }

}

 

// 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

<point 4.5>

serialization constructor

point constructor

<point3D 7.8.9>

 

 

例,高阶对象实现:

将类的继承,构建成箭头函数;

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 `<Point ${this.x}.${this.y}>`;

    }

}

 

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 `<Point3d ${this.x}.${this.y}.${this.z}>`;

    }

}

 

p2 = new Point3D(4,5,6);

console.log(p2.stringify());

输出:

point constructor

serialization constructor

point3d constructor

<Point3d 4.5.6>