JavaScript 中的面向对象、原型、原型链、继承

2023-05-29 0 1,112

JavaScript 中说在我看来第一类,是不全然的,在 JS 中 6 种正则表达式(Undefined、Null、Number、Boolean、String、Object)中,前六种是基本上正则表达式,是原始值类别,那些值是在下层同时实现的,她们并非 object,因此没蓝本,没缺省,因此并并非像codelet那般透过缺省建立的示例。

codelet

1.采用缺省建立

var obj = new Object();

2.字面上量建立

var obj = {};

3.厂房商业模式

假如采用缺省和字面上量建立许多第一类,每一第一类这类又有许多全然相同的特性和方式不然,就会造成大批多次重复标识符,每一第一类加进特性都须要再次写一场。如三个第一类都须要加进 name、age 特性及 showName 方式:

var p1 = new Object();p1.name = 张三p1.age = 16,p1.showName = function() { return this.name}var p2 = new Object();p2.name = 李四p2.age = 18,p2.showName = function() { return this.name}

为的是化解那个难题,现代人选用了厂房商业模式,抽象化了codelet的操作过程,选用表达式PCB以某一USB(全然相同的特性和方式)codelet的操作过程。

function createPerson(name, age) { var obj = new Object(); obj.name = name; obj.age = age; obj.showName = function () { return this.name; }; return obj;}var p1 = createPerson(张三, 16);var p2 = createPerson(李四, 18);

4.内部结构商业模式

虽然厂房商业模式化解了建立多个第一类的多个全然相同特性难题,却无法判定第一类的具体类别,因为都是 Object,无法识别是 Array、或是 Function 等类别,那个时候缺省商业模式出现了。JS 中提供了像 Object、Array、Function 等这样的原生的缺省,同时也可以建立自定义的缺省,缺省是一个表达式,用来建立并初始化新建立的第一类。将厂房商业模式的例子用缺省可以重写为:

function Person(name, age) { this.name = name; this.age = age; this.showName = function() { console.log(this.name); }}var p1 = new Person(张三, 16);var p2 = new Person(李四, 18);

用 Person 代替了厂房商业模式的 createPerson 表达式,而且表达式名首字母 P 大写,这是因为按照惯例,缺省首字母应该大写,而作为非缺省的表达式首字母小写。另外可以注意到缺省内部的特点:

没显示codelet 直接在 this 上加进特性和方式 没 return

另外,还采用了 new 操作,要建立一个示例,必须采用 new 操作符,采用 new 操作符调用缺省,在调用缺省的时候经历了如下几个阶段:

建立一个第一类 把建立的第一类赋值给 this 执行表达式中的标识符,即把特性和方式加进到赋值之后的 this 返回新第一类

伪标识符

来说明上述 new Person() 的操作过程如下:

// 采用new操作符时,会激活表达式这类的内部特性[[Construct]],负责分配内存Person.[[Construct]](initialParameters):// 采用原生缺省建立示例var Obj = new NativeObject() //NativeObject为原生缺省,如Object、Array、Function等// 给建立的示例加进[[Class]]内部特性,字符串第一类的一种表示, 如[Object Array]// Object.prototype.toString.call(obj)返回值指向的就是[[Class]]那个内部特性Obj.[[Class]] = Object/Array/Function;// 给建立的示例加进[[Prototype]]内部特性,指向缺省的prototypeO.[[Prototype]] = Person.prototype;// 调用缺省内部特性[Call],将Person执行上下文中this设置为内部建立的第一类ObjResult = Person.[[Call]](initialParameters); // this = Obj;// result是假如缺省内部假如存在返回值不然,调用[[call]]时作为返回值,一般为Object类别// 调用Person.[[call]]时,执行Person中的标识符,给this第一类加进特性和方式this.name = name;this.age = age;this.showName = function() { console.log(this.name);};//假如Person.[[call]]的返回值Result为Object类型return Result// 否则return Obj;

缺省虽然化解了示例多个同名特性多次重复加进的难题,但是也存在每一示例的方式都须要再次建立一遍,因为每一方式都是 Function 的不同示例,看下面这段标识符就明白了:

function Person(name, age) { this.name = name; this.age = age; this.showName = new Function(“console.log(this.name);”);}var p1 = new Person(张三, 16);var p2 = new Person(李四, 18);console.log(p1.showName === p2.showName); //false

那个难题可以用以下办法来化解,把 showName 变成全局表达式

function Person(name, age) { this.name = name; this.age = age; this.showName = showName;}function showName() { console.log(this.name)}

但是这样假如第一类须要加进许多方式就会造成许多全局表达式,那些难题可以透过蓝本商业模式来化解

5.蓝本商业模式

什么是蓝本

当每一个表达式建立时,都会给表达式设置一个 prototype(蓝本)特性,那个特性是一个指针,指向一个第一类,那个第一类包含所有示例共享的特性和方式,在默认情况下,都会为 prototype 第一类加进一个 constructor 特性,指向该表达式。

Person.prototype.constructor = Person;

蓝本商业模式就是不必在缺省中定义示例的特性和方式,而是将特性和方式都加进到蓝本第一类中。建立自定义缺省,其蓝本第一类只会默认取得 constructor 特性,其他的属性和方式都是从 Object 承继来的。当采用缺省建立一个示例之后,会给示例加进内部特性 prototype,那个特性是一个指针,指向缺省的 prototype(蓝本)第一类,由于是内部特性,无f() 来判断建立的示例是否有指向某缺省的指针,假如存在,返回 true 假如不存在 返回 false。

function Person() {}Person.prototype.name = 张三;Person.prototype.friends = [张三, 李四];Person.prototype.showName = function() { console.log(this.name);}var p1 = new Person();var p2 = new Person()console.log(p1.__proto__ === Person.prototype) // trueconsole.log(Person.prototype.isPrototypeOf(p1)) // true

在 ECMA5 中增加了一个方式

Object.getPrototypeOf(params)

,返回值就是codelet的蓝本第一类

console.log(Object.getPrototypeOf(p1) === Person.prototype); // trueconsole.log(Object.getPrototypeOf(p1).name); //张三

蓝本商业模式虽然化解了方式共享的难题,但是对于示例共享来说是个比较大的难题,因为每一示例都须要有描述自己这类特性的专有特性,还是上面的标识符:

console.log(p1.name) // 张三 console.log(p2.name) // 张三

另外对于特性是引用类别的值来说缺点就更明显了,假如执行下面这段标识符:

p1.friends.push(王五);console.log(p1.priends); //[张三, 李四, 王五]console.log(p2.priends); //[张三, 李四, 王五]

为的是化解蓝本商业模式的难题,现代人选用了蓝本和内部结构组合商业模式,采用缺省定义示例,采用蓝本商业模式共享方式。

6 组合采用缺省商业模式和蓝本商业模式

直接上标识符:

function Person(name, age) { this.name = name; this.age = age; this.friends = [张三, 李四]; // this.friends = new Array(张三, 李四)}Person.prototype.showName = function() { console.log(this.name);};var p1 = new Person(John);var p2 = new Person(Alice);p1.friends.push(王五);console.log(p1.friends); // [张三, 李四, 王五];console.log(p2.friends); // [张三, 李四];// 因为这时候每一示例建立的时候的friends特性的指针地址不同,因此操作p1的friends特性并不会对p2的friends特性有影响console.log(p1.showName === p2.showName) // true 都指向了Person.prototype中的showName

这种缺省商业模式和蓝本商业模式组合采用,基本上上可以说是 JS 中面向第一类开发的一种默认商业模式,介绍了以上这几种常用codelet的方式,还有其他不常用的商业模式就不介绍了,接下来想说的是 JS 中比较重要的承继。

承继

什么是蓝本链

ECMA 中承继的主要方式就是透过蓝本链,主要是一个蓝本第一类等于另一个类别的示例,由于示例内部含有一个指向缺省的指针,这时候相当于重写了该蓝本第一类,此时该蓝本第一类就包含了一个指向另一个蓝本的指针,假如另一个蓝本又是另一个类别的示例,这样就形成了蓝本链的概念,蓝本链最下层为 Object.prototype.

proto

为 null。

特性查找机制

JS 中示例特性的查找,是按照蓝本链进行查找,先找示例这类有没那个特性,假如没就去查找查找示例的蓝本第一类,也就是 [[prototype]] 属性指向的蓝本第一类,一直查到 Object.prototype,假如还是没该特性,返回 undefined。所有表达式的默认蓝本都是 Object 示例。

function Parent() { this.surname = 张; this.name = 张三; this.like = [apple, banana];}var par = new Parent()function Child() { this.name = 张小三;}Parent.prototype.showSurname = function() { return this.surname}// 承继同时实现Child.prototype = new Parent();var chi = new Child();console.log(chi.showSurname()) // 张

以上标识符证明,此时 Child 示例已经可以访问到 showSurname 方式,这就是透过蓝本链承继 Parent 蓝本方式,剖析一下其操作过程:

Child.prototype = new Parent();

相当于重写了 Child.prototype,指向了父示例 par,同时也包含了父示例的 [[prototype]] 特性,此时

console.log(Child.prototype.__proto__ === par.__proto__); // trueconsole.log(Child.prototype.__proto__ === Parent.prototype); // true

执行 chi.showSurname() 时,根据特性查找机制:

先从示例 chi 这类查找,有没 showSurname,没 继续查找 chi 的蓝本第一类 Child.prototype 有没 showSurname,没 继续查找 Child.prototype 的蓝本指针 __proto__ 有没 showSurname,此时 Child.prototype.__proto__ 的指针地址指向 Parent.prototype,找到了,因此
console.log(chi.showSurname()) // 张

所有表达式默认承继 Object:

function Person() {}console.log(Person.prototype.__proto__ === Object.prototype); // true

缺省商业模式和蓝本商业模式组合承继

只透过蓝本来同时实现承继,还存在一定难题,因此 JS 中一般透过借用缺省和蓝本组合的方式来同时实现承继,也称

经典承继

,还是承继那段标识符,再贴过来把,方便阅读

function Parent() { this.surname = 张; this.name = 张三; this.like = [apple, banana];}var par = new Parent()function Child() { this.name = 张小三;}Parent.prototype.showSurname = function() { return this.surname}// 承继同时实现Child.prototype = new Parent();var chi1 = new Child();var chi2 = new Child();console.log(chi.showSurname()) // 张// 主要看承继的特性console.log(chi.like) // [apple, banana]这是因为Child.prototype指向父示例,当查找示例chi这类没like特性,就去查找chi的蓝本第一类Child.prototype,因此找到了

那么还存在什么难题呢?主要就是涉及到引用类别的特性时,引用类别数据的原初特性会被示例所共享,而示例这类的特性应该有示例自己的特性,还是以上标识符

chi.like.push(orange);console.log(chi1.like); // [apple, banana, orange]console.log(chi2.like); // [apple, banana, orange]

因此缺省和蓝本组合的经典承继出现了,也是本篇最重要的内容:

1.特性承继

在子缺省内,采用 apply() 或 call() 方式调用父缺省,并传递子缺省的 this

2.方式承继

采用上文提到的蓝本链承继,承继父内部结构器的方式上标识符:

function Parent(name) { this.name = name; this.like = [apple, banana];}Parent.prototype.showName = function() { console.log(this.name);};function Child(name, age) { // 承继特性 Parent.call(this, name); // 加进自己的特性 this.age = age;}Child.prototype = new Parent();// 子缺省加进自己的方式Child.prototype.showAge = function() { console.log(this.age);};var chi1 = new Child(张三, 16);var chi2 = new Child(李四, 18);chi1.showName(); //张三chi1.showAge(); //16chi1.like.push(orange);console.log(chi1.like); // [apple, banana, orange]console.log(chi2.like); // [apple, banana]

在子缺省 Child 中是用 call() 调用 Parent(),在 new Child() 创建示例的时候,执行 Parent 中的标识符,而此时的 this 已经被 call() 指向 Child 中的 this,因此新建的子示例,就拥有了父示例的全部特性,这就是承继特性的原理。对 chi1 和 chi2 的 like 特性,是每一示例自己的特性,二者间不存在引用依赖关系,因此操作 chi.like 并不会对 chi.like 造成影响。方式承继,就是上文讲的到的蓝本链机制承继,另外可以给子缺省加进自己的特性和方式。这就是经典承继,避免了但是采用缺省或者单独采用蓝本链的缺陷,成为 JS 中最常用的承继方式。

个人扩展补充

hasOwnProperty()

用法 obj.hasOwnProperty(prop)

采用 hasOwnProperty() 方式可以判断访问的特性是蓝本特性还是示例特性,假如是示例特性返回 true 否则返回 false

function Person() {}Person.prototype.name = 张三var p1 = new Person();var p2 = new Person();p1.name = 张三;console.log(p1.hasOwnProperty(name)) //trueconsole.log(p2.hasOwnProperty(name)) //false

重写蓝本第一类

在实际开发中,假如蓝本第一类有许多方式,往往我们可以采用字面上量的形式,重写蓝本,但是须要手工指定 constructor 特性

function Person(name, age) { this.name = name; this.age = age;}var p1 = new Person(张三, 16);Person.prototype.showName = function() { return this.name;}Person.prototype.showAge = function() { return this.age;}

假如缺省的 prototype 方式许多,可以选用字面上量方式定义

Person.prototype = { constructor: Person, showName: function() { return this.name; }, showAge: function() { return this.age; }}

注意这里面手动加了一个 constructor 特性指向 Person 缺省,这是因为采用字面上量重写蓝本第一类,那个蓝本第一类变成了一个Object的示例,蓝本第一类这类已经不存在最初表达式建立时初始化的constructor 特性,这是蓝本第一类的 [[prototype]] 指针指向了 Object.prototype

显式 prototype 和隐式 [[Prototype]] 特性

function Person() { }Person.prototype.a = 10;var p = new Person();console.log(p.a) //10Person.prototype = { constructor: Person, a: 20, b: 30}console.log(p.a) // 10console.log(p.b) // undefinedvar p2 = new Person();console.log(p2.a) // 20console.log(p2.b) // 30

因此,有的文章说 动态修改蓝本将影响所有的第一类都会拥有新的蓝本 是错误的,新蓝本仅仅在蓝本修改以后的新codelet上生效。这里的主要规则是:第一类的蓝本是第一类的建立的时候建立的,并且在此之后不能修改为新的第一类,假如依然引用到同一个第一类,可以透过缺省的显式prototype引用,第一类建立以后,只能对蓝本的特性进行加进或修改。

举报/反馈

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务