天天看点

javascript原型和继承javascript原型和继承

javascript原型和继承

“一切都是对象”这句话的重点在于如何去理解“对象”这个概念。当然,也不是所有的都是对象,值类型就不是对象。

javascript

中判断一个变量是不是对象?

function show(x) {
    console.log(typeof x);    // undefined
    console.log(typeof );   // number
    console.log(typeof 'abc'); // string
    console.log(typeof true);  // boolean
    console.log(typeof function () { });  //function
    console.log(typeof [, 'a', true]);  //object
    console.log(typeof { a: , b:  });  //object
    console.log(typeof null);  //object
    console.log(typeof new Number());  //object
}
show();
           

以上代码列出了typeof输出的集中类型标识,其中上面的四种(

undefined

,

number

,

string

,

boolean

)属于简单的值类型,不是对象。剩下的几种情况——函数、数组、对象、

null

new Number(10)

都是对象。 他们都是引用类型。

var fn = function () { };
console.log(fn instanceof Object);  // true
           

判断一个变量是不是对象非常简单。值类型的类型判断用

typeof

,引用类型的类型判断用

instanceof

有个疑问。在

typeof

的输出类型中,

function

object

都是对象,为何却要输出两种答案呢?都叫做

object

不行吗?

函数和对象的关系

函数是一种对象,但是函数却不像数组一样——你可以说数组是对象的一种,因为数组就像是对象的一个子集一样。但是函数与对象之间,却不仅仅是一种包含和被包含的关系,函数和对象之间的关系比较复杂,甚至有一点鸡生蛋蛋生鸡的逻辑,

用案例来解答:

function Fn() {
    this.name = '小明';
    this.sex = 'male';
}
var fn1 = new Fn();
           

上面的这个例子很简单,它能说明:对象可以通过函数来创建。

var obj = new Object();
obj.a = ;
obj.b = ;

var arr = new Array();
arr[] = ;
arr[] = 'x';
arr[] = true;

Object 和 Array 都是函数:
console.log(typeof (Object));  // function
console.log(typeof (Array));  // function
           

所以,可以很负责任的说——对象都是通过函数来创建的。

有个疑问:对象是函数创建的,而函数却又是一种对象,函数和对象到底是什么关系啊???

prototype原型

函数也是一种对象。他也是属性的集合,你也可以对函数进行自定义属性,

javascript

自己就先做了表率,人家就默认的给函数一个属性——

prototype

。每个函数都有一个属性叫做

prototype

这个

prototype

的属性值是一个对象(属性的集合,再次强调!),默认的只有一个叫做

constructor

的属性,指向这个函数本身。

javascript原型和继承javascript原型和继承

SuperType是是一个函数,右侧的方框就是它的原型。

原型既然作为对象,属性的集合,不可能就只有

constructor

,肯定可以自定义的增加许多属性。例如

Object

,他的

prototype

里面,就有好几个其他属性。

javascript原型和继承javascript原型和继承

你也可以在自己自定义的方法的

prototype

中新增自己的属性

function Fn() { }
Fn.prototype.name = '小明';
Fn.prototype.getSex = function () {
    return 'male';
};

var fn = new Fn();
console.log(fn.name);
console.log(fn.getSex());
           

Fn

是一个函数,

fn

对象是从

Fn

函数

new

出来的,这样

fn

对象就可以调用

Fn.prototype

中的属性。因为每个对象都有一个隐藏的属性——“__proto__ ”,这个属性引用了创建这个对象的函数的

prototype

。 即:

fn.__proto__ === Fn.prototype

这里的

"__proto__ "

成为“隐式原型”

隐式原型

每个函数

function

都有一个

prototype

,即原型。这里再加一句话——每个对象都有一个

__proto__

,可成为隐式原型。

这个

__proto__

是一个隐藏的属性,

javascript

不希望开发者用到这个属性值,有的低版本浏览器甚至不支持这个属性值。

javascript原型和继承javascript原型和继承

上面截图看来,

obj.__proto__

Object.prototype

的属性一样!这么巧!

答案就是一样。

obj这个对象本质上是被

Object

函数创建的,因此

obj.__proto__ === Object.prototype

。我们可以用一个图来表示。

javascript原型和继承javascript原型和继承

即,每个对象都有一个

__proto__

属性,指向创建该对象的函数的

prototype

那么上图中的

“Object prototype”

也是一个对象,它的

__proto__

指向哪里?

在说明

“Object prototype”

之前,先说一下自定义函数的

prototype

。自定义函数的

prototype

本质上就是和

var obj = {}

是一样的,都是被

Object

创建,所以它的

__proto__

指向的就是

Object.prototype

但是

Object.prototype

确实一个特例——它的

__proto__

指向的是

null

javascript原型和继承javascript原型和继承

函数也是一种对象,函数也有

__proto__

吗?

函数也是被创建出来的。谁创建了函数呢?

Function

注意这个大写的

“F”

function fn(x,y) {
    return x + y;
};
console.log(fn(, ));

var fn1 = new Function('x' , 'y', 'return x + y;');
console.log(fn1(,));
           

以上代码中,第一种方式是比较传统的函数创建方式,第二种是用

new Functoin

创建。

根据上面说的一句话——对象的

__proto__

指向的是创建它的函数的

prototype

,就会出现:

Object.__proto__ === Function.prototype

。用一个图来表示。图中标出了:自定义函数

Foo.__proto__

指向

Function.prototype

Object.__proto__

指向

Function.prototype

Function.prototype

指向的对象,它的

__proto__

是不是也指向

Object.prototype

答案是肯定的。因为

Function.prototype

指向的对象也是一个普通的被

Object

创建的对象,所以也遵循基本的规则。

javascript原型和继承javascript原型和继承

介绍一下instanceof

先看例子

function Foo() {};
var f1 = new Foo();
console.log(f1 instanceof Foo); //true
console.log(f1 instanceof Object) //true 
           

Instanceof

运算符的第一个变量是一个对象,暂时称为

A

;第二个变量一般是一个函数,暂时称为

B

Instanceof

的判断规则是:沿着

A

__proto__

这条线来找,同时沿着

B

prototype

这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回

true

。如果找到终点还未重合,则返回

false

。所以

f1 instanceof Object

这句代码就是

true

javascript原型和继承javascript原型和继承

介绍一下继承

javascript

中的继承是通过原型链来体现的

function Foo() { };
var f1= new Foo();
f1.a = ;
Foo.prototype.a = ;
Foo.prototype.b = ;
console.log(f1.a);
console.log(f1.b);
           

f1

Foo

函数

new

出来的对象,

f1.a

f1

对象的基本属性,

f1.b

是怎么来的呢?——从

Foo.prototype

得来, 因为

f1.__proto__

指向的是

Foo.prototype

访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着

__proto__

这条链向上找,这就是原型链。

javascript原型和继承javascript原型和继承

访问

f1.b

时,

f1

的基本属性中没有

b

,于是沿着

__proto__

找到了

Foo.prototype.b

。有疑问可以看看上一节将

instanceof

时候那个大图

继承的演变

ES3 => ES5 => ES6 的变化过程

因为我们现在用的

react

做的项目对

ES6

语法用的比较多,所以再详细说一下:

extends

super

static

下面是

ES3

的写法

function Student(props) {
  this.name = props.name || 'Unnamed';
}  //构造函数
Student.prototype.hello = function() {
  console.log('Hello, ' + this.name + '!');
} //原型



function PrimaryStudent(props) {
  Student.call(this, props);     //拿到Student构造函数的this.name = props.name || 'Unnamed';
  this.grade = props.grade || ;
} 

// es3实现继承
function F() {}
F.prototype = Student.prototype;
PrimaryStudent.prototype = new F();
PrimaryStudent.prototype.constructor = PrimaryStudent;

PrimaryStudent.prototype.getGrade = function() {
  return this.grade;
}

var xiaoming = new PrimaryStudent({
  name: '小明',
  grade: '2',
});

console.log(xiaoming.name);  // 小明
console.log(xiaoming.grade) // 2

// 验证原型:
console.log(xiaoming.__proto__  === PrimaryStudent.prototype) // true
console.log(xiaoming.__proto__ .__proto__  === Student.prototype) // true

// 验证继承关系
console.log(xiaoming instanceof PrimaryStudent); // true
console.log(xiaoming instanceof Student); // true
           

ES5

的继承写法:

function Student(props) {
  this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function() {
  console.log('Hello, ' + this.name + '!');
}



function PrimaryStudent(props) {
  Student.call(this, props);
  this.grade = props.grade || ;
}

PrimaryStudent.prototype.getGrade = function() {
  return this.grade;
}

// es5实现继承
PrimaryStudent.prototype = Object.create(Student.prototype);
PrimaryStudent.prototype.constructor = PrimaryStudent;

var xiaoming = new PrimaryStudent({
  name: '小明',
  grade: '2',
});

console.log(xiaoming.name);  // 小明
console.log(xiaoming.grade) // 2

// 验证原型:
console.log(xiaoming.__proto__  === PrimaryStudent.prototype) // true
console.log(xiaoming.__proto__ .__proto__  === Student.prototype) // true

// 验证继承关系
console.log(xiaoming instanceof PrimaryStudent); // true
console.log(xiaoming instanceof Student); // true
           

ES6

的继承写法:

function Student(props) {
  this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function() {
  console.log('Hello, ' + this.name + '!');
}


class PrimaryStudent extends Student {
  constructor(props) {
    super(props);
    this.grade = props.grade || ;
  }
  getGrade() {
    return this.grade;
  }
}

var xiaoming = new PrimaryStudent({
  name: '小明',
  grade: '2',
});

console.log(xiaoming.name);  // 小明
console.log(xiaoming.grade) // 2

// 验证原型:
console.log(xiaoming.__proto__  === PrimaryStudent.prototype) // true
console.log(xiaoming.__proto__ .__proto__  === Student.prototype) // true

// 验证继承关系
console.log(xiaoming instanceof PrimaryStudent); // true
console.log(xiaoming instanceof Student); // true
           

extends 继承内建类的能力

因为语言内部设计原因,我们没有办法自定义一个类来继承

JavaScript

的内建类的。继承类往往会有各种问题。

ES6

extends

的最大的卖点,就是不仅可以继承自定义类,还可以继承 JavaScript 的内建类,比如这样:

class MyArray extends Array {
}
           

这种方式可以让开发者继承内建类的功能创造出符合自己想要的类。所有 Array 已有的属性和方法都会对继承类生效。这确实是个不错的诱惑,也是继承最大的吸引力。举个例子:

var a = new Array(, , )
a.length  // 3
var b = new MyArray(, , )
b.length  // 3
           

super 在 constructor 和普通方法里的不同

constructor

里面,

super

的用法是

super(..)

。它相当于一个函数,调用它等于调用父类的

constructor

。但在普通方法里面,

super

的用法是

super.prop

或者

super.method()

。它相当于一个指向对象的

__proto__

的属性。这是

ES6

标准的规定,

如果写子类的

constructor

需要操作

this

,那么

super

必须先调用!这是

ES6

的规则。所以写子类的

constructor

时尽量把

super

写在第一行。

super

是编译时确定(可以理解为定义时确定),不是运行时确定

总结:

如何记住原型链,这里和

html

事件冒泡差不多

<null>
    <Object>//原型链顶端
        <parent>
            <child></child>
        </parent>
    </Object>
</null>
           

ES6

中继承:

class Parent {
    constructor(firstName, lastName) { 
        console.log(firstName,lastName)
    }
    fullName() {
       return `${this.firstName} ${this.lastName}`
    }
}
class Child extends Parent {   //extends:不仅可以继承自定义类,还可以继承 JavaScript 的内建类;
  static findAll() {          //static: 该方法不会被实例继承, 而是直接通过类来调用
 return super.findAll()
}
  constructor(firstName, lastName, age) {
    super(firstName, lastName);     // super: Parent.call(this, firstName, lastName)相当于函数执行Parent();
    this.age = age
  }
}
           

ES5

最经典的继承方法是用组合继承的方式,原型链继承方法,借用函数继承属性,

ES6

也是基于这样的方式,但是封装了更优雅简洁的

api

,让

Javascript

越来越强大,修改了一些属性指向,规范了继承的操作,区分开了继承实现和实例构造,此外

ES6

继承还能实现更多的继承需求和场景。

继续阅读