ES5 的构造函数原型链继承

2019-10-09 admin

构造函数

构造函数,就是专门用来生成实例对象的函数。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。

function Person(name){
    this.name = name;
}

为了与普通函数区别,构造函数名字的第一个字母通常大写。

构造函数的特点有两个:

  • 函数体内部使用了 this 关键字,代表了所要生成的对象实例。
  • 生成对象的时候,必须使用 new 命令。

new 命令

基本用法

new 命令的作用,就是执行构造函数,返回一个实例对象。

let a = new Person('dora');
a.name      // dora

new 命令本身就可以执行构造函数,所以后面的构造函数可以带括号,也可以不带括号,但为了表明是函数调用,推荐使用括号表示更明确的语义。

new 命令的原理

使用 new 命令时,它后面的函数依次执行下面的步骤:

  1. 创建一个空对象,作为将要返回的对象实例。let o = new Object();
  2. 将这个空对象的原型,指向构造函数的prototype属性。 Object.setPrototypeOf(o,Foo.prototype);
  3. 将构造函数的 this 绑定到新创建的空对象上。Foo.call(o);
  4. 始执行构造函数内部的代码。

如果构造函数内部有 return 语句,而且后面跟着一个对象,则 new 命令会返回 return 语句指定的对象;否则,就会不管 return 语句,返回 this 对象,且不会执行 return 后面的语句。

function Person(name) {
  this.name = name;
  if (name == undefined) {
    return {};
  }else if(typeof name != 'string'){
    return '姓名有误';
  }
  console.log(111);
}

new Person();        //  {}
new Person(123);     //  {name: 123}

new Person('dora');  
// {name:'dora'}
// 111

new.target

如果当前函数是 new 命令调用的,在函数内部的 new.target 属性指向当前函数,否则为 undefined

function F() {
  console.log(new.target === F);
}

F()       // false
new F()   // true

使用这个属性,可以判断函数调用时,是否使用了 new 命令。

function F() {
  if (!new.target) {
    throw new Error('请使用 new 命令调用!');
  }
}

F()       // false
new F()   // true

强制使用 new 命令

如果不使用 new 命令执行构造函数就会引发一些意想不到的结果,所以为了保证构造函数必须与 new 命令一起使用,除了 new.target 之外也可以有以下两个解决办法。

1. 构造函数内部使用严格模式

在构造函数内部第一行加上 use strict。这样的话,一旦忘了使用 new 命令,直接调用构造函数就会报错。

function Person(name, age){
  'use strict';
  this.name = name;
  this.age = age;
}

Person()
//  TypeError: Cannot set property 'name' of undefined

报错原因是因为不加 new 调用构造函数时,this 指向全局对象,而严格模式下,this 不能指向全局对象,默认等于 undefined,给 undefined 添加属性肯定会报错。

2. 在构造函数内部通过 instanceof 判断是否使用 new 命令

function Person(name, age) {
  if (!(this instanceof Person)) {
    return new Person(name, age);
  }

  this.name = name;
  this.age = age;
}

Person('dora', 18).name          // dora
(new Person('dora', 18)).age     // 18

在构造函数内部判断 this 是否是构造函数的实例,如果不是,则直接返回一个实例对象。

prototype 原型

任何函数都有一个 prototype 属性,这个属性称为函数的“原型”,属性值是一个对象。只有函数有原型属性

function f(){}
typeof f.prototype   // "object"

对于普通函数来说,原型没有什么用。但对于构造函数来说,通过 new 生成实例的时候,该属性会自动成为实例对象的原型对象。

prototype 属性的作用

用来定义所有实例对象共用的属性和方法。

如果将对象的方法写入构造函数中,则 new 多少个实例,方法将会被复制多少次,虽然复制出来的函数是一样的,但分别指向不同的引用地址,不利于函数的复用。

function Person(){
  this.name = function(){
      console.log('dora');
  }
}
let p1 = new Person();
let p2 = new Person();

p1.name === p2.name     // false

因此,将所有的属性都定义在构造函数里,所有的方法都定义在构造函数的原型中。这样,实例的方法都指向同一个引用地址,内存消耗小很多。

constructor 属性

函数原型 prototype 对象的属性,指向这个原型所在的构造函数,可以被所有实例对象继承,指向构造自己的构造函数。

Person.prototype.constructor.name   //  "Person"
p1.constructor.name                 //  "Person"

函数的 name 属性返回函数名。

给 prototype 添加属性

1. 点语法追加属性

Person.prototype.sayHi = function(){
    console.log('Hi,I am Dora');
}

2. 覆盖原型对象

Person.prototype = {
  sayHi: function(){
      console.log('Hi,I am Dora');
  }
}

这样用对象字面量直接覆盖,会让 constructor 与构造函数失联,可以手动补上这个属性。

Person.prototype = {
  constructor: Person,
  sayHi: function(){
      console.log('Hi,I am Dora');
  }
}

定义构造函数原型中的方法时尽量不要相互嵌套,各方法最好相互独立。

proto 原型对象

任何一个对象都有 __proto__ 属性,这个属性称为对象的“原型对象”,一个对象的原型对象就是它的构造函数的 prototype

function Person(){}
let p1 = new Person();

p1.__proto__ === Person.prototype  // true

Object.getPrototypeOf(obj)

__proto__ 并不是语言本身的属性,这是各大浏览器厂商添加的私有属性,虽然目前很多浏览器都可以识别这个属性,但依旧不建议在生产环境下使用,避免对环境产生依赖。

生产环境下,我们可以使用 Object.getPrototypeOf(obj) 方法来获取参数对象的原型。

Object.getPrototypeOf(p1) === Person.prototype   // true

原型链机制

当访问对象的属性时,如果这个对象没有这个属性,系统就会查找这个对象的 __proto__ 原型对象,原型对象也是个对象,也有自己的 __proto__ 原型对象,然后就会按照这个原型链依次往上查找,直到原型链的终点 Object.prototype

Object() 是系统内置的构造函数,用来创建对象的, Object.prototype 是所有对象的原型链顶端,而Object.prototype 的原型对象是 null

Object.getPrototypeOf(Object.prototype) // null

如果对象自身和它的原型都定义了一个同名属性,那么优先读取对象自身的属性。

继承

继承的核心是 子类构造函数的原型是父类构造函数的一个实例对象。

首先继承父类的属性

// 1\. 父类构造函数
function Super(data){
  this.data = data;
};
Super.prototype.funName = function(){};

// 2\. 子类构造函数
function Sub(){
  // 用来继承父类的参数和属性
  Super.apply(this, arguments);
}

其次继承父类的方法

  1. 整体继承

    Sub.prototype = Object.create(Super.prototype);
    
    // or
    
    Sub.prototype = new Super();
    
  2. 单个方法的继承

    Sub.prototype.funName = function(){
      Super.prototype.funName.call(this);
      // some other code
    }
    

最后需要改变 constructor 指向

此时子类实例的 constructor 指向父类构造函数 Super,需手动改变。

Sub.prototype.constructor = Sub;

多重继承

ES5 没有多重继承功能,即不允许一个对象同时继承多个对象,但可通过变通方法实现这个功能。

function S(){
  M1.call(this);
  M2.call(this);
};
S.prototype = Object.create(M1.prototype); // 继承 M1
Object.assign(S.prototype,M2.prototype);   // 继承链上加入 M2
S.prototype.constructor = S;               // 指定构造函数。

实例验证

instanceof 运算符

instanceof 运算符返回一个布尔值,表示对象是否为某个构造函数的实例。继承的子类实例也是父类的实例,因此继承的也为 true

let d = new Date();
d instanceof Date    // true
d instanceof Object  // true

Object.prototype.isPrototypeOf()

实例对象可继承 isProtorypeOf()方法,用来判断该对象是否为参数对象的原型对象。

只要实例对象处在参数对象的原型链上,isPrototypeOf() 方法都返回 true

let o1 = {};
let o2 = Object.create(o1);
let o3 = Object.create(o2);

o2.isPrototypeOf(o3)   // true
o1.isPrototypeOf(o3)   // true
o2.isPrototypeOf(o2)   // false

[转载]原文链接:https://segmentfault.com/a/1190000020629155

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-76968.html

文章标题:ES5 的构造函数原型链继承

相关文章
html5+JavaScript教程-微信打飞机小游戏源码
js // JavaScript Document var c = document.getElementById("dotu"); var cxt = c.getContext("2d&q...
2015-11-12
js性能优化 如何更快速加载你的JavaScript页面
确保代码尽量简洁 不要什么都依赖JavaScript。不要编写重复性的脚本。要把JavaScript当作糖果工具,只是起到美化作用。别给你的网站添加大量的JavaScript代码。只有必要的时候用一下。只有确实能改善用户体验的时候用一下。 ...
2015-11-12
10个强大的纯CSS3动画案例分享
我们的网页外观主要由CSS控制,编写CSS代码可以任意改变我们的网页布局以及网页内容的样式。CSS3的出现,更是可以让网页增添了不少动画元素,让我们的网页变得更加生动有趣,并且更易于交互。本文分享了10个非常炫酷的CSS3动画案例,希望大家...
2015-11-16
2015年JavaScript或“亲库而远框架”
2014年过去了,作为一个JavaScript开发者很难满怀信心的去“挽回”一个特定的库或技术,即便是强大的Angular,似乎也因为最近的一些事情而动摇。 2014年10月的ng-europe会议上,Angular开发者团队透露了一个关于...
2015-11-12
v-charts | 饿了么团队开源的基于 Vue 和 ECharts 的图表工具
在使用echarts生成图表时,经常需要做繁琐的数据类型转化、修改复杂的配置项,v-charts的出现正是为了解决这个 痛点。基于Vue2.0和echarts封装的v-charts图表组件,只需要统一提供一种对前后端都友好的数据格式 设置简...
2018-05-24
国内外html5游戏引擎排行
一个好的HTML5游戏引擎,能够大大简化游戏的开发实现。 排名列表: Construct 2 ImpactJS EaselJS pixi.js Phaser GameMaker Three.js PlayCanvas Turbulenz ...
2015-11-12
详解HTML5游戏玩家流失原因
对任何类型的电子游戏来说,玩家流失都是一个无法回避的问题。玩家为什么离开游戏?近日,HTML5游戏设定师纳森·洛维托(Nathan Lovato)在游戏开发者网站Gamasutra撰写文章,解读了玩家离开游戏的16个理由。 HTML5游戏 ...
2015-11-12
YouTube正式默认使用HTML5视频播放器
YouTube视频网站现在默认使用HTML5播放器,这意味着更好的性能、 稳定性、 电池寿命和甚至是更好的安全性。现在用户通过Chrome、IE 11、Safari 8和Beta版本的Firefox进行浏览的时候都默认使用HTML5视频播放...
2015-11-12
从2014年的发展来展望JS的未来将会如何
<font face="寰�杞�闆呴粦, Arial, sans-serif ">2014骞达紝杞�浠惰�屼笟鍙戝睍杩呴€燂紝鍚勭�嶈��瑷€灞傚嚭涓嶇┓锛屼互婊¤冻鐢ㄦ埛涓嶆柇鍙樺寲鐨勯渶姹傘€傝繖浜涜��...
2015-11-12
12个你未必知道的CSS小知识
虽然CSS并不是一种很复杂的技术,但就算你是一个使用CSS多年的高手,仍然会有很多CSS用法/属性/属性值你从来没使用过,甚至从来没听说过。 1.CSS的color属性并非只能用于文本显示 对于CSS的color属性,相信所有Web开发人员...
2015-11-12
回到顶部