1. 原型
prototype
:每个构造函数都有一个原型对象 prototype- prototype 属性是一个指针,指向一个对象,该对象包含所有实例共享的属性和方法。
constructor
:原型对象都包含一个指向构造函数的指针 constructor__proto__
:当调用构造函数创建一个新实例,该实例的内部将包含一个 __proto__ 属性,指向构造函数的原型对象- 所有的引用类型(数组、对象、函数)都有一个 __proto__ 属性
2. 使用构造函数创建对象
创建自定义类型最常见的方式,就是组合使用构造函数模式与原型模式。
构造函数模式
用于定义实例属性,而原型模式
用于定义方法和共享的属性。- 每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。
1
2function Person(name, age) {
3 this.name = name;
4 this.age = age;
5 this.friends = ["Bob"];
6}
7
8
9Person.prototype = {
10 constructor: Person,
11 sayName: function () {
12 alert(this.name);
13 },
14};
15
16
17Person.statciFunc = function () {
18 console.log("我是静态方法,与实例无关");
19};
20
21var person1 = new Person("Tom", 29);
22var person2 = new Person("Mary", 32);
23
24person1.friends.push("Van");
25alert(person1.friends);
26alert(person2.friends);
27alert(person1.friends === person2.friends);
28alert(person1.sayName === person2.sayName);
3. new 的实现原理
new 操作符用于创建对象的实例。
new 关键字会进行如下的操作:
- 创建一个空的 JavaScript 对象(即{});
- 为空对象添加属性 __proto__,将该属性指向构造函数的原型对象;
- 将空对象作为 this 的上下文;
- 如果构造函数的返回值为对象,则返回构造函数的返回值,否则返回新对象。
如果你没有使用 new 运算符,构造函数会像其他的常规函数一样被调用,并不会创建一个对象。在这种情况下,this 的指向也是不一样的。
1function myNew(func, ...args) {
2
3 const obj = {};
4
5
6 obj.__proto__ = func.prototype;
7
8
9 const result = func.call(obj, ...args);
10
11
12 const isObject = typeof result === "object" && result !== null;
13 const isFunction = typeof result === "function";
14 if (isObject || isFunction) {
15 return result;
16 }
17
18
19 return obj;
20}
1function Person(name, age) {
2 this.name = name;
3 this.age = age;
4}
5
6Person.prototype.sayHello = function () {
7 console.log(this.name);
8};
9
10const xiaoming1 = new Person("xiaoming", 12);
11console.log(xiaoming1);
12
13const xiaoming2 = myNew(Person, "xiaoming", 12);
14console.log(xiaoming2);
4. 原型链
实例对象都有 __proto__ 属性,指向它的原型对象;原型对象也是对象,也有 __proto__ 属性,指向原型对象的原型对象,直到最顶层对象 Object 的原型 Object.prototype.__proto__===null
,这样一层一层形成的链式结构,称为原型链。
当获取一个对象的某个属性时,如果这个对象本身没有这个属性,就会沿着原型链往上查找,直到 Object.prototype。
原型与实例的关系可以通过两种方式来确定:
- instanceof 操作符:如果一个实例的原型链中出现过相应的构造函数,则 instanceof 返回 true;
- isPrototypeOf() 方法:用于检查一个原型对象是否存在于另一个对象的原型链中,只要原型链中包含这个原型对象,这个方法就返回 true。
1function Person(name) {
2 this.name = name;
3}
4const person = new Person("yc");
5console.log(person instanceof Person);
6console.log(person instanceof Object);
7console.log(Person.prototype.isPrototypeOf(person));
8console.log(Object.prototype.isPrototypeOf(person));
5. 继承
继承,即让一个引用类型继承另一个引用类型的属性和方法。
原型链继承和构造函数继承了解即可,主要是组合继承和寄生式组合继承。
5.1 原型链继承
- 优点:无
- 缺点:
- 创建子类实例时,不能向父类的构造函数中传递参数
- 父类中所有引用类型的属性会被所有子类实例共享,也就说一个子类实例修改了父类中的某个引用类型的属性时,其他子类实例也会受到影响
1function Parent() {
2 this.name = "parent";
3 this.hobby = ["sing", "rap"];
4}
5
6function Child() {
7 this.type = "child";
8}
9Child.prototype = new Parent();
10
11let child1 = new Child();
12let child2 = new Child();
13child1.name = "Bob";
14console.log(child1.name);
15console.log(child2.name);
16child1.hobby.push("basketball");
17console.log(child1.hobby);
18console.log(child2.hobby);
5.2 构造函数继承(经典继承)
- 优点:
- 在子类构造函数中可以向父类的构造函数中传递参数
- 避免了父类中的引用类型属性在子类中共享的问题
- 缺点:父类原型对象上的方法子类继承不到
1function Parent(age) {
2 this.name = "parent";
3 this.age = age;
4 this.hobby = ["sing", "rap"];
5}
6Parent.prototype.sayHi = function () {
7 console.log("Hi");
8};
9
10function Child(age) {
11 Parent.call(this, age);
12 this.type = "child";
13}
14
15let child1 = new Child(15);
16let child2 = new Child(15);
17child1.hobby.push("basketball");
18console.log(child1.name);
19console.log(child1.age);
20console.log(child1.type);
21console.log(child1.hobby);
22console.log(child2.hobby);
23child1.sayHi();
5.3 组合继承(原型链继承+构造函数继承)
使用最多的继承模式是组合继承,使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
- 优点:
- 在子类构造函数中可以向父类的构造函数中传递参数
- 避免了父类中的引用类型属性在子类中共享的问题
- 父类原型对象上的方法子类也可以继承到
- 缺点:父类构造函数被调用了两次
SubType.prototype 上会有两个属性:name 和 colors。它们都是 SuperType 的实例属性,但现在成为了 SubType 的原型属性。在调用 SubType 构造函数时,也会调用 SuperType 构造函数,这一次会在新对象上创建实例属性 name 和 colors。这两个实例属性会遮蔽原型上同名的属性。
1function SuperType(name) {
2 this.name = name;
3 this.colors = ["red", "blue", "green"];
4}
5SuperType.prototype.sayName = function () {
6 console.log(this.name);
7};
8
9function SubType(name, age) {
10
11 SuperType.call(this, name);
12
13 this.age = age;
14}
15
16
17SubType.prototype = new SuperType();
18
19
20SubType.prototype.constructor = SubType;
21
22
23SubType.prototype.sayAge = function () {
24 console.log(this.age);
25};
将子类的原型指向父类的实例,子类原型不仅拥有父类实例所拥有的全部属性和方法,其内部还有 __proto__
指针指向父类的原型。
1function Parent(age) {
2 this.name = "parent";
3 this.age = age;
4 this.hobby = ["sing", "rap"];
5}
6Parent.prototype.sayHi = function () {
7 console.log("Hi");
8};
9
10function Child(age) {
11 Parent.call(this, age);
12 this.type = "child";
13}
14Child.prototype = new Parent();
15Child.prototype.constructor = Child;
16
17let child1 = new Child(15);
18let child2 = new Child(15);
19child1.hobby.push("basketball");
20console.log(child1.name);
21console.log(child1.age);
22console.log(child1.type);
23console.log(child1.hobby);
24console.log(child2.hobby);
25child1.sayHi();
5.4 寄生式组合继承(实现继承最有效的方式!)
- 优点:实现继承最有效的方式!
- 缺点:无
1function Parent(age) {
2 this.name = "parent";
3 this.age = age;
4 this.hobby = ["sing", "rap"];
5}
6Parent.prototype.sayHi = function () {
7 console.log("Hi");
8};
9
10function Child(age) {
11 Parent.call(this, age);
12 this.type = "child";
13}
14Child.prototype = Object.create(Parent.prototype);
15Child.prototype.constructor = Child;
16
17let child1 = new Child(15);
18let child2 = new Child(15);
19child1.hobby.push("basketball");
20console.log(child1.name);
21console.log(child1.age);
22console.log(child1.type);
23console.log(child1.hobby);
24console.log(child2.hobby);
25child1.sayHi();
Object.create() 方法用于创建一个新对象,使用参数对象来作为新创建对象的原型(prototype)。原理如下:
1function object(o) {
2 function F() {}
3 F.prototype = o;
4 return new F();
5}
6. 类 class
6.1 class 的基本用法
constructor() 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法,该方法默认返回实例对象 this。
实例属性现在除了可以定义在 constructor()方法里面的 this 上面,也可以定义在类内部的最顶层。
类的属性和方法,除非显式定义在 this 上,否则都是定义在原型上。
1class Point {
2 count = 0;
3
4 constructor(x, y) {
5 this.x = x;
6 this.y = y;
7 }
8
9 toString() {}
10
11 toValue() {}
12
13 static staticProp = 1;
14
15 static staticMethod() {
16
17 }
18
19 #privateProp = 0;
20
21 #privateMethod() {
22
23 }
24}
6.2 class 的继承
1class ColorPoint extends Point {
2 constructor(x, y, color) {
3 super(x, y);
4 this.color = color;
5 }
6
7 toString() {
8 return this.color + " " + super.toString();
9 }
10}
7. instanceof 的实现原理
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
1function myInstanceof(obj, className) {
2
3 if (!obj || !className || !obj.__proto__ || !className.prototype) {
4 return false;
5 }
6
7 let curProto = obj.__proto__;
8
9 while (curProto) {
10
11 if (curProto === className.prototype) {
12 return true;
13 }
14 curProto = curProto.__proto__;
15 }
16
17 return false;
18}
1function SubType(name, age) {
2 this.name = name;
3 this.age = age;
4}
5const instance = new SubType("yc", 18);
6console.log(myInstanceof(instance, SubType));
7console.log(myInstanceof(instance, Object));
8. 函数的原型
- 函数实例(不是实例对象)的
__proto__
指向Function.prototype
Function.prototype.__proto__
指向Object.prototype
- 函数实例的原型的
__proto__
指向Object.prototype
,函数实例的原型就是构造函数创建对象的原型
1function func() {}
2console.log(func.__proto__ === Function.prototype);
3console.log(Function.prototype.__proto__ === Object.prototype);
4console.log(func.prototype.__proto__ === Object.prototype);
5
6console.log(Object.__proto__ === Function.prototype);
7console.log(Array.__proto__ === Function.prototype);
8console.log(Date.__proto__ === Function.prototype);
9
10f = new func();
11console.log(f.__proto__ === func.prototype);