0%

JavaScript 的原型链

JavaScript 的原型链

JavaScript 的原型链关系到js 的对象,方法实例化的原理,弄懂原型链就可以明白 js 封装继承的原理.对理解js 的面向对象有很大的帮助.在 ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型链的.
原型链主要是由 2 个对象实现的: __proto__prototype

__proto__和 prototype

JavaScript 的所有对象都有__proto__这个属性,其实__proto__就是一个指针, 他指向实例化他这个对象的原始对象的 prototype 对象.

1
2
3
4
class Book {} // function Book() {}
const mathBook = new Book();
// 这里mathBook.__proto__ === Book.prototype
// 这就是__proto__指向实例化他的原始对象的 prototype.

prototype 是所有的函数对象都有的属性,当一个函数对象被 new 实例化时,他的 prototype 里的属性和方法都会被复制一份,放到实例化的对象里,并且 prototype 这个对象也有__proto__对象,实例化的时候还会把__proto__指向的对象的 prototype 里的属性和方法也复制一份.也就实现了继承.

1
2
3
4
class Book {}
const mathBook = new Book();
console.log(Book.prototype.__proto__ === Object.prototype) // true
// 但是 mathBook 是没有 prototype 属性的,因为他是一个实例化对象,而不是一个函数,js 中的类是用函数模拟出来的,全依赖这个 prototype

JavaScript 的基本数据类型

JavaScript 的基本数据类型也是通过这种方法实现的.

1
2
const a = 1; // const a = new Number(1);
const str = 'String'; // const str = new String(String);

这个时候 a 和 str 就是 Number 和 String 函数(方法,类)的实例化对象.

1
2
a.__proto__ === Number.prototype;
str.__proto__ === String.prototype;

而这些数据类型又都是 Object 对象的子类.

1
Number.prototype.__proto__ === Object.prototype

如果你写一个类继承于 Number,例如 class Int extend Number{}
那么这个类的实例化对象就会有

1
2
3
4
5
class Int extend Number{}
const a = new Int()
a.__proto__ === Int.prototype
a.__proto__.__proto__ === Number.prototype
a.__proto__.__proto__.__proto__ === Object.prototype

经过测试

1
2
3
4
Function.prototype.__proto__ === Object.prototype; // true
Number.prototype.__proto__ === Object.prototype; // true
String.prototype.__proto__ === Number.prototype.__proto__; // true
Object.__proto__ === null; // true

所以 JavaScript 的数据类型都是 Object 类型的子类,所以说 JavaScript 的所有数据都是对象
而原型链的终点就是 null

关于函数: JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。
由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。

construction 方法

构造方法,也就是实例化对象时(new)调用的方法.这个方法其实就是一个类的名字指向的地址.

1
Number.prototype.constructor === Number; // true

而实例化之后的对象也拥有 construction 方法,他指向他的类

1
2
const a = 1;
a.constructor === Number; // true

这样就可以更容易判断一个对象他由哪个类实例化而来了

new 运算符

new运算符的原理:

  1. 创建一个空对象,作为将要返回的对象实例。
  2. 将这个空对象的原型,指向构造函数的prototype属性。
  3. 将这个空对象赋值给函数内部的this关键字。
  4. 开始执行构造函数内部的代码。
1
2
3
4
5
6
7
8
9
10
11
12
function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {
// 将 arguments 对象转为数组
var args = [].slice.call(arguments);
// 取出构造函数
var constructor = args.shift();
// 创建一个空对象,继承构造函数的 prototype 属性
var context = Object.create(constructor.prototype);
// 执行构造函数
var result = constructor.apply(context, args);
// 如果返回结果是对象,就直接返回,否则返回 context 对象
return (typeof result === 'object' && result != null) ? result : context;
}

Object.create()

构造函数作为模板,可以生成实例对象。但是,有时拿不到构造函数,只能拿到一个现有的对象。我们希望以这个现有的对象作为模板,生成新的实例对象,这时就可以使用Object.create()方法。

1
2
3
4
5
6
7
8
9
10
11
12
var person1 = {
name: '张三',
age: 38,
greeting: function() {
console.log('Hi! I\'m ' + this.name + '.');
}
};

var person2 = Object.create(person1);

person2.name // 张三
person2.greeting() // Hi! I'm 张三.

原型链的作用

在面向对象编程中,子类会继承父类的方法和属性,当一个属性在子类的实例化对象中找不到时,就会去找他父类的属性.JavaScript 的原型链也是这样工作的,查找一个对象的属性或者方法,就会去他的__proto__指向的prototype 中找,如果还没有,就会去 prototype.__proto__指向的 prototype 中找,一直找到 Object.prototype.这也就实现了类的继承.