鸿蒙应用开发-JS原型和原型链

基本概念

JS对象

  1. JS对象是动态的属性(指 其自有属性)包,可以自由添加、更改、删除上面的属性
  2. JS对象分为 函数对象普通对象,每个对象都有__proto__属性,只有函数对象才有prototype属性
  3. Object、Function 都是JS内置的函数, 类似的还有Array、RegExp、Date、Boolean、Number、String等
  4. proto__属性的值是一个对象,它有两个属性,constructor和__proto
  5. 原型对象prototype有一个constructor属性,用于记录实例是由哪个构造函数创建
  • 隐式操作:指不是由开发者亲自创建、操作
  • 显示操作:指可以由开发者亲自创建、操作
js 复制代码
const o1 = {}; 
const o2 =new Object(); 
const o3 = new f1();

function f1(){}; 
const f2 = function(){}; 
const f3 = new Function('str','console.log(str)'); 

console.log(typeof Object); //function 
console.log(typeof Function); //function 

console.log(typeof f1); //function
console.log(typeof f2); //function 
console.log(typeof f3); //function

console.log(typeof o1); //object 
console.log(typeof o2); //object 
console.log(typeof o3); //object

o1 o2 o3 为普通对象,f1 f2 f3 为函数对象

  1. 凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象
  2. f1,f2归根结底都是通过 new Function()的方式进行创建的
  3. function Object 也都是通过 new Function()创建的

引用类型的四个规则

  • 具有对象特性,即可自由扩展属性,对属性进行增删改查
js 复制代码
const obj = {} 
const arr = [] 
const fn = function () {} 
// 可以自由添加属性
obj.a = 1 
arr.a = 1 
fn.a = 1 
console.log(obj.a) // 1
console.log(arr.a) // 1 
console.log(fn.a) // 1
  • 有一个隐式原型 __proto__ 属性,属性值是一个普通的对象
js 复制代码
const obj = {}; 
const arr = [];
const fn = function() {} 
console.log('obj.__proto__', obj.__proto__); 
console.log('arr.__proto__', arr.__proto__); 
console.log('fn.__proto__', fn.__proto__);
  • 隐式原型 __proto__ 的属性值指向它的构造函数的显式原型 prototype 属性值
js 复制代码
const obj = {}; 
const arr = []; 
const fn = function() {} 
obj.__proto__ == Object.prototype // true 
arr.__proto__ === Array.prototype // true 
fn.__proto__ == Function.prototype // true
  • 当你试图访问一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型 __proto__(也就是它的构造函数的显式原型 prototype)中寻找
js 复制代码
const obj = { a:1 }
obj.toString 
// ƒ toString() { [native code] } 
// obj 对象并没有 toString 属性,之所以能获取到 toString 属性,是遵循了第四条规则,从它的构造函数 Object 的 prototype 里去获取

Object

  1. 在JavaScript中,Object构造函数创建一个对象包装器。Object是通过Function创建出来的,它是一种特殊的Function
  2. 如果给定值是null或undefined,将会创建并返回一个空对象,否则,将返回一个与给定值对应类型的对象
  3. 当以非构造函数(即,没有使用new)的方式被调用时,Object函数将转换为对象。它可以将任何值转换为对象。这种方式通常被用于将基本数据类型(如数字、字符串和布尔值)转换为相应的对象
  4. 只要是一个普通对象object,就可以用 new Object() 来实例化(Object() 是一个内置的构造函数 ),也就是说,所有的对象字面量都是 Object() 的实例
  5. Object 作为构造函数,Object.prototype 指向一个具体的 原型对象 ,该 原型对象 作为对象的实例,它的 __proto__ 值为 null,因而 Object.prototype.__proto__ = null 时也就走到了 原型的尽头

Function

  1. JS已经内置了 Function() 构造函数 ,Function是所有函数的构造函数,因而 所有函数 都算作是 Function实例对象
  2. JS中的所有引用类型都是通过Function创建出来的
  3. 无论是Object还是自定义的函数,甚至Function本身都是通过Function函数创建出来的
  4. Function是JS中的创造神
js 复制代码
function fun() {}
function Fun() {}

我们使用function关键字来定义函数,但是有的时候函数名看起来更像类名。一般函数名第一个字母大写的时候,我们是想把这个函数当成构造函数使用

constructor 构造函数

构造函数是用于创建和初始化一个对象的特殊方法,通常我们会使用new关键字来调用构造函数,创建新的对象实例

JS中没有类的概念,设计者于是设计了构造函数来实现继承机制

js 复制代码
// 构造函数 
function Person(name, age) { 
    this.name = name; 
    this.age = age; 
}
// 生成实例 
const p = new Person('zhangsan', 18);

上面代码通过 new Person 生成了实例p,在Person这个构造函数内赋值给this的name和age属性是每个实例独有的,无法共享公共属性和公共方法。于是又设计了原型对象来实现共享属性和方法的功能

实例化

构造函数 使用 new 关键字可以创建出不同的 实例(实例的本质就是一个对象)

new 创建对象的过程

  1. 创建一个新对象
  2. 将构造函数的作用域赋值给新对象(这样this就指向了新对象)
  3. 执行构造函数中的代码(为新对象添加实例属性和实例方法)
  4. 返回新对象

prototype 原型

**JS的每个函数都有一个prototype属性**指向一个对象 ,这个对象就叫原型对象,也叫显示原型原型对象有个属性为constructor指向该构造函数

每个对象中有一个属性叫__proto__,它叫隐式原型

构造函数的prototype指向原型对象,原型对象的constructor指向构造函数

csharp 复制代码
function Person() {};
var p = new Person();

构造函数的prototype属性的值(Person.prototype),也可以说成通过调用构造函数而创建出来的那个实例对象的原型对象(p.__proto__)

  1. 只要是函数就有 prototype 属性,即函数的原型属性(由于对象是由函数创建的,因此对象也有prototype属性)
  2. 函数的原型属性也是对象(因此,这个对象也有对应的prototype属性)
  3. 由构造函数创建出来的对象会默认链接到其构造函数的这个属性上(constructor)
  4. 构造函数的 prototype 属性的作用是:实现数据共享(继承)

原型链

js 复制代码
// 构造函数 
function Preson(name, age) { 
    this.name = name; 
    this.age = age; 
} 
// 所有实例共享的公共方法 
Preson.prototype.say = function (word) { 
    console.log(`${this.name}说:${word}`); 
} 
const p1 = new Preson('张三', 18); // 创建一个Person实例对象 p1.hasOwnProperty('say') // false 说明不是定义在其本身上的 
p1.say('hello world'); // 调用公共方法 打印:张三说:hello world

p1上是没有say属性的,但是我们却可以调用say这个方法,而say这个方法是存在于Person的原型对象上面。 之所以能够调用到say方法,就涉及到__proto__属性了

proto

  1. JS中的每个对象(object)都有一个私有属性__proto__指向原型(prototype) 对象
  2. 原型对象也是对象,也有__proto__指向自己的原型,这样层层向上直到一个对象的原型为 null
  3. null 没有原型,它是 原型链(prototype chain)查找的终点
  4. 当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型以及原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾返回undefined
  • 当我们访问p1的say属性时,会先在p1本身的属性里查找,没有找到
  • 然后通过p1的__proto__隐式属性,找到它的构造函数原型对象Person.prototype。然后在这个原型对象的属性里查找say属性
  • 如果还没有找到就会在其构造函数prototype__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链

可以通过__proto__修改对象的原型

  1. 如果通过p1实例对象__proto__属性赋值,则会改变其构造函数原型对象,从而被所有实例所共享
  2. 我们在开发的时候,要注意不要通过实例对象去改变其构造函数原型对象,这样会对其他通过该构造函数生成的实例对象造成影响
js 复制代码
// 构造函数 
function Preson(name, age) { 
    this.name = name;
    this.age = age; 
} 
// 所有实例共享的公共方法 
Preson.prototype.say = function (word) { 
    console.log(`${this.name}说:${word}`); 
}
const p1 = new Preson('张三', 18); // 创建一个Person实例对象 
const p2 = new Preson('李四', 20); // 新创建一个Proson实例对象 
p1.say('hello world'); // 调用公共方法 
p1.hasOwnProperty('say') // false 说明不是定义在其本身上的 

p1.__proto__.do = function () {
    console.log('往原型对象中添加方法'); 
}
p2.do(); // 打印出了-往原型对象中添加方法

instanceof操作符

instanceof 用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链上

更形象来说,对于 A instanceof B来说,它的判断规则是:沿着A的__proto__这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。不理解没关系,下面会结合图例分析

javascript 复制代码
function Person() {}
var p = new Person();
console.log(p instanceof Object);//true
console.log(p instanceof Person);//true

图解原型链

"铁三角关系"(重点)实例对象的隐式原型 === 构造函数的显式原型

csharp 复制代码
function Person() {};
var p = new Person();

这个图描述了构造函数,实例对象和原型三者之间的关系,是原型链的基础:

(1)实例对象由构造函数new产生;

(2)构造函数的原型属性实例对象的原型对象均指向原型

(3)原型对象中有一个属性constructor指向对应的构造函数

原型链:p --> Person.prototype

描述:实例对象能够访问到 Person.prototype 中不同名的属性和方法

验证:

ini 复制代码
p instanceof Person; // true
p.__proto__ === Person.prototype; // true
Person.prototype.constructor === Person; // true

原型也是构造函数

原型链:p --> Person.prototype --> Object.prototype --> null

描述:

(1)构造函数的原型也是对象,因此:它也有原型对象,指向Object.prototype

(2)构造函数的原型的原型也是对象,因此:它也有原型对象,指向null(特例)

验证:

javascript 复制代码
p instanceof Person; // true
p instanceof Object; // true
Person.prototype instanceof Object; // true

Function构造函数

原型链1:p --> Person.prototype --> Object.prototype --> null

原型链2:Person --> Function.prototype --> Object.prototype --> null

描述:

(1)构造函数Person作为实例对象时,Person = new Function()隐式调用,因此Person --> Function.prototype

(2)Function.prototype也是对象,Function.prototype = new Object()隐式调用,因此Function.prototype --> Object.prototype

验证:

javascript 复制代码
Person instanceof Function; // true
Person instanceof Object; // true
Function.prototype instanceof Object; // true

原型链3:Function --> Function.prototype --> Object.prototype --> null

描述:

构造函数Function作为实例对象时,Function = new Function()隐式调用,因此Function --> Function.prototype

Function 这条原型链是最为特殊的"铁三角关系",理解Function = new Function()就非常好理解了

验证:

javascript 复制代码
Function.__proto__ === Function.prototype; // true
Function instanceof Function; // true
Function instanceof Object; // true

完整的原型链

原型链1:p --> Person.prototype --> Object.prototype --> null

原型链2:Person --> Function.prototype --> Object.prototype --> null

原型链3:Function --> Function.prototype --> Object.prototype --> null

原型链4:Object --> Function.prototype --> Object.prototype --> null

图中新增了Object = new Function()的逻辑

验证:

javascript 复制代码
Object instanceof Function;// true

几个结论:

(1)对象都有原型对象,对象默认继承自其原型对象

(2)所有的函数都是 Function 的实例

(3)所有的原型链尾端都会指向Object.prototype

原型链改写(重点)

当实例对象被创建时,其原型链就已经确定了,当其对应的原型属性指向改变时,也无法改变原型链

ini 复制代码
function Person({name="小A", age=21}={}) {
  this.name = name;
  this.age = age;
};

// 情况1:在修改原型属性前实例化对象
var p1 = new Person();

// 添加原型属性(方法)
Person.prototype.sayName = function() {
  console.log(this.name);
}

// 情况2:在修改原型属性后实例化对象
var p2 = new Person();

p1.sayName(); // "小A"
p2.sayName(); // "小A"

实例对象p1和实例对象p2的原型链相同,为 p1(p2) --> Person.prototype --> Object.prototype

由于是在原有原型对象上添加的方法,相当于对象的扩展,故两个实例对象均能执行该方法

ini 复制代码
function Person({name="小A", age=21}={}) {
  this.name = name;
  this.age = age;
};

// 情况1:在修改原型属性前实例化对象
var p1 = new Person();

// 重写原型对象
Person.prototype = {
  sayName: function() {
    console.log(this.name);
  }
}

// 情况2:在修改原型属性后实例化对象
var p2 = new Person();

p2.sayName(); // "小A"
p1.sayName(); // p1.sayName is not a function

重写原型对象的方式,会改变实例对象的原型链,如下图所示:

但是,为什么p1的原型链没有变,而p2的原型链变了呢?

当实例对象被创建时,其原型链就已经确定了,当其对应的原型属性指向改变时,也无法改变原型链

原型链是以实例对象为核心的,不能被原型对象的改变而误导

对象与函数(重点)

官方定义: 在Javascript中,每一个函数实际上都是一个函数对象

javascript 复制代码
function fn() {};
var obj = {};

fn instanceof Object; // true
fn instanceof Function; // true

obj instanceof Object; // true
obj instanceof Function; // false

原型链解释:

fn对应的原型链:fn --> Function.prototype --> Object.prototype

obj对应的原型链:obj --> Object.prototype

从函数的定义来说: 在javascript中一切函数实际都是函数对象,但对象不一定是函数

javascript 复制代码
Function instanceof Object; // true
Object instanceof Function; // true

Function instanceof Function; // true

原型链解释:

Function对应的原型链(Function作为实例对象):Function --> Function.prototype --> Object.prototype

Object对应的原型链(Object作为实例对象):Object --> Function.prototype --> Object.prototype

由于Function和Object都是构造函数,在内置对象中,均会调用new Function()的方法

结论:

(1)函数一定是对象,但是对象不一定是函数

(2)对象都是由函数来创建的

针对第一点,这两个原型链可验证:

fn --> Function.prototype --> Object.prototype

obj --> Object.prototype

针对第二点,可这样验证:

ini 复制代码
var obj = { a: 1, b: 2}
var arr = [2, 'foo', false]

// 实际过程
var obj = new Object()
obj.a = 1
obj.b = 2

var arr = new Array()
arr[0] = 2
arr[1] = 'foo'
arr[2] = false

//typeof Object === 'function'  
//typeof Array === 'function

属性搜索原则和属性来源判断

属性搜索原则

  1. 当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。
  2. 搜索先从对象实例本身开始,如果在实例中找到了具有给定名字的属性,则返回该属性的值;
  3. 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回这个属性,
  4. 如果没有找到,则继续在这个原型对象的原型对象中查找,直到找到这个属性,否则返回undefined

简言之,沿着对象的原型链查找属性,返回最近的属性,这就是属性搜索原则

ini 复制代码
function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
  alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.name = "Greg";
alert(person1.name); //"Greg" 来自实例
alert(person2.name); //"Nicholas" 来自原型

同样的,这也是属性屏蔽的原则

scss 复制代码
// 接着上面的例子
delete person1.namel;
alert(person1.name); // "Nicholas" 来自原型

hasOwnProperty()方法与in操作符

使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是在原型中,这个方法只在给定属性存在于对象实例中时,才会返回true

ini 复制代码
function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
  alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

alert(person1.hasOwnProperty("name"));  //false

person1.name = "Greg";
alert(person1.name); //"Greg" 来自实例 
alert(person1.hasOwnProperty("name")); //true

alert(person2.name); //"Nicholas" 来自原型
alert(person2.hasOwnProperty("name")); //false

delete person1.name;
alert(person1.name); //"Nicholas" 来自原型
alert(person1.hasOwnProperty("name")); //false

有两种方式使用in操作符:单独使用和在for-in循环中使用。在单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中

因此,同时使用hasOwnProperty()和in操作符,就可以确定某个属性到底是存在于对象中还是存在于原型中

php 复制代码
function hasPrototypeProperty(object, name){
  return !object.hasOwnProperty(name) && (name in object);
}

顺便一提,由于in操作符会在整个原型链上查找属性,处于性能考虑,在使用for-in循环时,建议多加一层判别

ini 复制代码
function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;

var p = new Person();
p.sex = "fale";

for(key in p) {
  console.log(key); // sex name age 
}

// 实际上,我们一般只是在查找实例中的属性
for(key in p) {
  if(p.hasOwnProperty(key)) {
    console.log(key); // sex 屏蔽了原型中的属性
  }
}

JS对象布局图

代码

js 复制代码
function Foo() {} 
const f1 = new Foo(); 
const o1 = new Object(); 
// 可以把Foo.prototype理解成是Foo的类,而Foo只是一个构造函数 
// 把__proto__理解成OC中的isa指针 

// f1实例的__proto__指向它的构造函数的原型对象。
f1 = new Foo() 
console.log("f1.__proto__===Foo.prototype", f1.__proto__===Foo.prototype); 
// Foo构造函数的原型对象的constructor指向Foo函数 
console.log("Foo.prototype.constructor===Foo", Foo.prototype.constructor===Foo); 
// Foo.prototype的__proto__是Object.prototype 
console.log("Foo.prototype.__proto__===Object.prototype", Foo.prototype.constructor===Foo); 

// 每一个JS函数都可以理解成是Function的一个实例。即Foo = new Function(...)。所以Foo实例的__proto__指向Function.prototype 
console.log("Foo.__proto__===Function.prototype", Foo.__proto__===Function.prototype); 
// Function原型对象的constructor指向Function函数 
console.log("Function.prototype.constructor===Function", Function.prototype.constructor===Function); 
// Function本身也是通过Function创建的一个实例。即Function = new Function(...),所以Function.__proto__指向Function.prototype
console.log("Function.__proto__===Function.prototype", Function.__proto__===Function.prototype);

// Function原型对象的__proto__指向Object.prototype
console.log("Function.prototype.__proto__===Object.prototype", Function.prototype.__proto__===Object.prototype); 
// Object.prototype的constructor指向Object
console.log("Object.prototype.constructor===Object", Object.prototype.constructor===Object); 
// Object也是一个实例,它的__proto__指向Function.prototype。即Object = new Function(...) 
console.log("Object.__proto__===Function.prototype", Object.__proto__===Function.prototype); 
// Object.prototype.__proto__指向null 
console.log("Object.prototype.__proto__===null", Object.prototype.__proto__===null);

// o1实例的__proto__指向Object.prototype 
console.log("o1.__proto__===Object.prototype", o1.__proto__===Object.prototype);

神图

细剖

在这里 隐式原型指__proto__的指向,显示原型指 prototype的指向

实例对象 f1

  1. f1是function Foo()的实例对象,function Foo()是构造它的函数。f1的隐式原型指向function Foo()的显式原型。实例对象的隐式原型等于构造函数的显式原型
  2. function Foo()的显式原型指向Foo.prototype
  3. 所以实例对象f1的隐式原型也指向Foo.prototype

Foo.prototype

  1. 原型对象的constructor指向它的构造函数,所以Foo.prototype的constructor指向function Foo()
  2. Foo.prototype也是一个实例对象,function Object()是构造它的函数,Foo.prototype的隐式原型指向function Object()的显式原型
  3. function Object()的显示原型是Object.prototype
  4. 所以Foo.prototype的隐式原型也指向Object.prototype

构造函数 function Foo()

  1. function Foo()的显示原型指向Foo.prototype
  2. 构造函数也是对象,是function Function()创建出来的实例对象,它的隐式原型会指向function Function()函数的显式原型
  3. function Function()的显示原型是Function.prototype
  4. 所以function Foo()的隐式原型也指向Function.prototype。万物皆对象

构造函数 function Object()

  1. function Object()是构造函数,它的显示原型指向Object.prototype
  2. function Object()也是一个对象,function Function()是构造它的函数。function Object()的隐式原型指向function Function()的显示原型
  3. function Function()的显示原型是Function.prototype
  4. 所以function Object()的隐式原型也指向Function.prototype

实例对象 o1

  1. o1是通过function Object()的实例对象,function Object是构造它的函数。 o1的隐式原型指向function Object()的显式原型
  2. function Object()的显式原型指向Object.prototype
  3. 所以o1的隐式原型也指向Object.prototype
  4. function Object()和function Function()函数是系统已经封装好的函数

Object.prototype

  1. Object.prototype没有构造函数,也就没有构造函数的显式原型
  2. 同样的,Object.prototype的隐式原型就是null

解释

  1. 代码中的f1和o1的生成代码 const f1 = new Foo(); const o1 = new Object();
  2. 函数Foo的显式原型为Foo.prototype,函数Foo的隐式原型是_proto_
  3. f1实例的__proto__指向它的构造函数的原型对象。f1 = new Foo()
  4. Foo.prototype的__proto__是Object.prototype
  5. 每一个JS函数都可以理解成是Function的一个实例。即Foo = new Function(...)。所以Foo实例的__proto__指向Function.prototype
  6. Function本身也是通过Function创建的一个实例。即Function = new Function(...),所以Function.__proto__指向Function.prototype
  7. o1实例的__proto__指向Object.prototype
  8. Object也是一个实例,它的__proto__指向Function.prototype。即Object = new Function(...)
  9. 实例对象的隐式原型 === 构造函数的显式原型

从上面的图我们也可以得出结论

  1. JS中所有的函数是通过Function函数构造出来的
  2. Function本身也是一个对象,也是通过Function函数构造出来的
  3. Object也是一个对象,也是通过Function函数构造出来的

总结

  1. 函数实例的__proto__指向它的构造函数的原型对象,比如f1的__proto__指向Foo.prototype,o1的__proto__指向Object.prototype
  2. 所有构造函数的__proto__都指向Function.prototype,比如Foo、Function、Object的__proto__都指向Function.prototype
  3. 除Object.prototype外的原型对象的__proto__都指向Object.prototype,比如Foo.prototype、Function.prototype的__proto__都指向Object.prototype
  4. Object.prototype的__prototype__指向null,这样就形成了一个闭环

iOS中的继承图

学过iOS的同学,应该对这张图比较了解

  • instance对象(实例对象)的isa指向class对象
  • class对象(类对象)的isa指向类的MetaClass对象
  • MetaClass对象(元类对象)的isa指向NSObject MetaClass对象,NSObject MetaClass对象的isa指向自己
  • NSObject MetaClass的superClass是基类NSObject,这样就形成了一个闭环

看起来是不是和JavaScript的上古神图很相似,可以对比理解。

App启动之后 iOS中的类和元类只有一份,JS中的Function.prototype和Object.prototype也是只有一份。都可以简单的理解成是在环境准备的过程中底层生成的。

JS对象在解释器启动过程的布局

JS解释器启动时会创建JS上下文环境以及最顶层的Object和Function并让这些代码对JS代码可见

继承

原型链实现

js 复制代码
// 父类型 
function Super(){
    this.flag = 'super';
} 
// 父类型原型上的方法 
Super.prototype.getFlag = function(){ 
    return this.flag;
}
// 子类型 
function Sub(){ 
    this.subFlag = 'sub';
}
// 实现继承。将子类的prototype设置为父类的一个实例 
Sub.prototype = new Super(); 
Sub.prototype.getSubFlag = function(){ 
    return this.subFlag; 
} 
const instance = new Sub(); 
console.log(instance.subFlag); // sub 
console.log(instance.flag); // super

使用这种方式实现继承,存在一些问题

一、 原型对象上的属性 会被所有实例共享

在通过原型链来实现继承时,原型对象上的属性被会所有实例共享,一旦一个实例修改了原型对象的属性值,会立刻反应到其他实例上。由于基本类型不是共享的,所以彼此不会影响

js 复制代码
Sub.prototype.flag = 'hahahahah'; 
// 这里修改了原型对象Sub.prototype上的flag属性后,所有实例的flag都变成了这个值

二、创建子类型的实例时,不能向父类型的构造函数传递参数

实际上,应该说是 没有办法在不影响所有对象实例的情况下,给父类型的构造函数传递参数,我们传递的参数会成为所有实例的属性

借用构造函数

基本思想:apply()call() 方法,在子类型构造函数的内部调用父类型的构造函数,使得子类型拥有父类型的属性和方法

js 复制代码
// 父类型 构造函数,可以传递参数 
function Super(properties){ 
    this.properties = [].concat(properties); 
    this.colors = ['red', 'blue', 'green']; 
} 
function Sub(properties){ 
    // 继承了 Super,传递参数,互不影响 
    Super.apply(this, properties);
}
var instance1 = new Sub(['instance1']); 
instance1.colors.push('black'); 
console.log(instance1.colors); // 'red, blue, green, black' 
console.log(instance1.properties[0]); // 'instance1' 

var instance2 = new Sub(); 
console.log(instance2.colors); // 'red, blue, green' 
console.log(instance2.properties[0]); // 'undefined'
  1. 借用构造函数解决了上面提到的两个问题:现在 实例间不会共享属性了,也可以向父类型传递参数了
  2. 但是这种方法任然存在一些问题:子类型无法继承父类型原型中的属性。我们只是在子类型的构造函数中调用了父类型的构造函数,没有做其他的,子类型和父类型的原型也就没有任何联系

组合继承

基本思想:使用原型链实现对原型属性和方法的继承,借用构造函数来实现对实例属性的继承

这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性,从而发挥二者之长

js 复制代码
function Super(properties){ 
    this.properties = [].concat(properties); 
    this.colors = ['red', 'blue', 'green'];
}

Super.prototype.log = function() { 
    console.log(this.properties[0]); 
} 
function Sub(properties){ 
    // 继承了 Super,传递参数,互不影响 
    Super.apply(this, properties);
} 
// 继承了父类型的原型 
Sub.prototype = new Super(); 
// isPrototypeOf() 和 instance 能正常使用
Sub.prototype.constructor = Sub; 

var instance1 = new Sub(['instance1']);
instance1.colors.push('black'); 
console.log(instance1.colors); // 'red,blue,green,black' 
instance1.log(); // 'instance1' 

var instance2 = new Sub();
console.log(instance2.colors); // 'red,blue,green' 
instance2.log(); // 'undefined'

组合继承看起来很不错,但是也有它的缺点:无论什么情况下,组合继承都会调用两次父类型的构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部

寄生组合式继承

基本思想:不必为了指定子类型的原型而调用父类型的构造函数,我们所需要的无非就是父类型原型的一个副本而已

通过借用构造函数来继承属性,通过借用临时构造函数来继承原型

js 复制代码
// 用于继承的函数 
function inheritPrototype(child, parent) { 
    var F = function () {} 
    F.prototype = parent.prototype; 
    child.prototype = new F(); 
    child.prototype.constructor = child;
}

// 父类型 function Super(name) { 
    this.name = name; 
    this.colors = ["red", "blue", "green"];
} 
Super.prototype.sayName = function () { 
    console.log(this.name); 
}; 
// 子类型 function Sub(name, age) { 
    // 继承基本属性和方法 
    SuperType.call(this, name); 
    this.age = age;
} 
// 继承原型上的属性和方法 
inheritPrototype(Sub, Spuer);
Sub.prototype.log = function () { 
    console.log(this.age); 
};

参考资料

  1. MDN-JavaScript高级-继承与原型链 developer.mozilla.org/zh-CN/docs/...
  2. 原型与继承 developer.mozilla.org/zh-CN/docs/...
  3. JavaScript对象布局图解 www.mollypages.org/tutorials/i...www.mollypages.org/tutorials/j...
  4. 了解 JavaScript 的继承与原型链 keguigong.org/post/unstan...
  5. 一文搞懂JS原型与原型链(超详细,建议收藏)juejin.cn/post/698467...
  6. prototype, proto , constructor, instanceof的渊源 www.ayqy.net/blog/protot...
  7. [翻译]Javascript Object Layout phphp.net/2018/07/jav...
  8. 面不面试的,你都得懂原型和原型链 juejin.cn/post/693449...
  9. JavaScript 世界万物诞生记 zhuanlan.zhihu.com/p/22989691
  10. 轻松理解JS 原型原型链 juejin.cn/post/684490...
  11. 快速理解JS中原型+原型链 juejin.cn/post/712204...
  12. 10分钟搞懂 js 原型 juejin.cn/post/728752...
  13. 深入理解 JavaScript 原型 juejin.cn/post/684490...
  14. 了解 JavaScript 的继承与原型链 keguigong.org/post/unstan...
  15. 简单剖析javascript------原型*原型链 juejin.cn/post/711986...
  16. 深入理解JavaScript------JavaScript 中的始皇 juejin.cn/post/714277...
  17. 进阶必读:深入理解 JavaScript 原型 juejin.cn/post/690149...
  18. 深入理解原型链与继承https://juejin.cn/post/6940056609250869278
  19. JavaScript原型与原型链最全面的详解https://juejin.cn/post/7233228344418238522
  20. 你可能不太理解的JavaScript - 原型与原型链https://juejin.cn/post/7254443448563040311
  21. 《javascript高级程序设计》笔记:原型图解 segmentfault.com/a/119000001...
  22. 《javascript高级程序设计》笔记:继承 segmentfault.com/a/119000001...
  23. 图解javascript原型链 juejin.cn/post/684490...
相关推荐
dawn3 小时前
鸿蒙ArkTS中的获取网络数据
华为·harmonyos
桃花键神3 小时前
鸿蒙5.0时代:原生鸿蒙应用市场引领开发者服务新篇章
华为·harmonyos
鸿蒙自习室3 小时前
鸿蒙多线程开发——并发模型对比(Actor与内存共享)
华为·harmonyos
JavaPub-rodert4 小时前
鸿蒙生态崛起:开发者的机遇与挑战
华为·harmonyos
帅比九日6 小时前
【HarmonyOS Next】封装一个网络请求模块
前端·harmonyos
yilylong7 小时前
鸿蒙(Harmony)实现滑块验证码
华为·harmonyos·鸿蒙
baby_hua7 小时前
HarmonyOS第一课——DevEco Studio的使用
华为·harmonyos
HarmonyOS_SDK8 小时前
融合虚拟与现实,AR Engine为用户提供沉浸式交互体验
harmonyos
- 羊羊不超越 -9 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
长弓三石11 小时前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙