1. 什么是对象

在JavaScript中,对象是属性的无序集合,每个属性存放一个原始值、对象或函数。

1.1 创建对象

在JavaScript中创建对象的两种方法:

① 字面上:

var myObj = {
key: value,
// ...
};

② 面向对象:

var myObj = new Object();
myObj.key = value;

注意:在对象中,属性名永远都是字符串。如果你使用string(字面量)以外的其他值作为属性名,那它首先会被转换为一个字符串。

1.2 内置对象

JavaScript中的内置对象分为构造器对象和普通对象。

构造器对象:

  • Object
  • Boolean
  • String
  • Number
  • Function
  • Array
  • RegExp
  • Date
  • Error

其他对象:

  • Math
  • JSON
  • 全局对象

内置对象,其实也叫内置构造器,它们可以通过new的方式创建一个新的实例对象。

2. 原型链

先上图。

2.1 __proto__prototypeconstructor

在JavaScript中,每个对象都拥有一个原型对象prototype,而指向该原型对象的内部指针则是__proto__属性。

__proto__属性前后各两个下划线,表示它本质是一个内部属性,而不是一个正式的对外的API。

2.1.1 通过{}创建的对象

var myObj = {};
console.log(myObj.__proto__ === Object.prototype); // true

2.1.2 函数对象

JavaScript中的函数也是对象,它的原型是Function.prototype

var Foo = function() {}
Foo.__proto__ === Function.prototype // true

通过函数构造的实例对象,其原型指针__proto__会指向该函数的prototype属性。

function Foo(){}
var a = new Foo();
console.log(a.__proto__ === Foo.prototype); // true
console.log(Foo.prototype.__proto__ === Object.prototype); // true
// constructor
console.log(Foo.prototype.constructor === Foo); // true

原型对象都包含一个指向构造函数的指针constructor

注意constructor属性并不是实例对象的属性,而是构造函数的原型对象的constructor属性。

console.log(a.constructor === Foo); // true
console.log(a.constructor === Foo.prototype.constructor); // true console.log(a.hasOwnProperty("constructor")); //false
console.log(Foo.prototype.hasOwnProperty("constructor")); //true

a.constructor通过原型链往上找到其原型对象Foo.prototypeconstructor属性。

2.1.3 内置对象

var a = new Array();
a.__proto__ === Array.prototype // true

Array.prototype本身也是一个对象,也有继承的原型:

a.__proto__.__proto__ === Object.prototype  // true
// 等同于 Array.prototype.__proto__ === Object.prototype

那么Object的原型指向的是谁呢?

a.__proto__.__proto__.__proto__ === null  // true
// 等同于 Object.prototype.__proto__ === null

总结:

原型链的意义在于,当读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。以此类推,如果直到最顶层的Object.prototype还是找不到,则返回undefined。(所有对象的原型链的顶端是Object.prototype

3. ES6的Class(类)

3.1 概述

JavaScript语言的传统方法是通过构造函数,定义并生成新对象。下面是一个例子。

function Point(x, y) {
this.x = x;
this.y = y;
} // 下面覆盖了原型链上Object.prototype.toString方法
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
}; var p = new Point(1, 2);
console.log(p.toString()); // (1, 2)

ES6引入了Class这个概念,通过class关键字可以定义类。

ES6的class可以看作是一种语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
} toString() {
return '(' + this.x + ', ' + this.y + ')';
}
} console.log(typeof Point); // "function"
console.log(Point === Point.prototype.constructor); // true var p = new Point(1, 2);
console.log(p.toString()); // (1, 2)

上面代码表明,类的数据类型就是函数,构造函数的prototype属性,在ES6的"类"上面继续存在。

事实上,类的所有方法都定义在类的prototype属性上面。

class Point {
constructor() {}
toString() {}
toValue(){}
} // 等同于 Point.prototype = {
toString(){},
toValue(){}
};

3.2 constructor方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

constructor() {}

constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。

class Foo {
// constructor() {}
} console.log(new Foo() instanceof Foo); // true
class Foo {
constructor() {
return Object.create(null);
}
} console.log(new Foo() instanceof Foo); // false

3.3 实例对象

类必须使用new来调用,从而生成类的实例对象。

实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。

//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
} toString() {
return '(' + this.x + ', ' + this.y + ')';
}
} var point = new Point(2, 3); console.log(point.hasOwnProperty('x')); // true
console.log(point.hasOwnProperty('y')); // true
console.log(point.hasOwnProperty('toString')); // false
console.log(point.__proto__.hasOwnProperty('toString')); // true

类的所有实例共享一个原型对象。这也意味着,通过实例的__proto__属性可以为原型对象添加方法。

var p1 = new Point(2,3);
var p2 = new Point(3,2); console.log(p1.__proto__ === p2.__proto__); // true p1.__proto__.printName = function () { return 'Oops' };
p1.printName() // "Oops"
p2.printName() // "Oops"

上面代码使用实例的__proto__属性改写原型,必须相当谨慎,不推荐使用,因为这会改变Class的原始定义,影响到所有实例。

3.4 继承

Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
} toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}

上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this

注意:在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
} class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}

3.5 类的__proto__prototype

类同时有__proto__属性和prototype属性,同时存在两条继承链。

  1. 子类的__proto__属性,表示构造函数的继承,总是指向父类。
  2. 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class A {
} class B extends A {
} B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

这样的结果是因为,类的继承是按照下面的模式实现的。

class A {
} class B {
} // B的实例继承A的实例
Object.setPrototypeOf(B.prototype, A.prototype); // B继承A的静态属性
Object.setPrototypeOf(B, A); Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}

这里只介绍JavaScript的原型链相关的内容,如果想了解关于ES6 Class的更多内容请阅读ES6文档。

参考:

理解JavaScript的原型链的更多相关文章

  1. 玩转JavaScript OOP[3]——彻底理解继承和原型链

    概述 上一篇我们介绍了通过构造函数和原型可以实现JavaScript中的“类”,由于构造函数和函数的原型都是对象,所以JavaScript的“类”本质上也是对象.这一篇我们将介绍JavaScript中 ...

  2. JavaScript的原型链继承__propt__、prototype、constructor的理解、以及他们之间相互的关系。

    回想自己已经工作了有一段时间了,但是自己对JavaScript的原型链.和继承的理解能力没有到位,最近他们彻底的整理并且复习了一遍. 本案例中部分文案来自网络和书籍,如有侵权请联系我,我只是把我的理解 ...

  3. Javascript的原型链图

    90%的前端或者js程序员或者老师们对Javascript懂得不比这个多 给手机看的 但是这个图里的所有褐色单向箭头链就是Javascript的原型链(颜色标注对理解js原型链很关键) 这图中的各个_ ...

  4. javaScript系列 [04]-javaScript的原型链

    [04]-javaScript的原型链 本文旨在花很少的篇幅讲清楚JavaScript语言中的原型链结构,很多朋友认为JavaScript中的原型链复杂难懂,其实不然,它们就像树上的一串猴子. 1.1 ...

  5. javascript prototype原型链的原理

    javascript prototype原型链的原理 说到prototype,就不得不先说下new的过程. 我们先看看这样一段代码: <script type="text/javasc ...

  6. javascript的原型链那些事

    如果你对javascript的原型链还有任何疑问,请看这篇文章 进入主题 前言 原型链的规则不百分百适用于所有情况 显式原型:prototype,是一个对象{} 隐式原型:__proto__,是一个对 ...

  7. javaScript(原型链)

    在了解javaScript的原型链之前,我们得先来看一下原型是什么. 在javaScript中,所有的函数都会有着一个特别属性:prototype(显示原型属性):当我们运行如下代码时: functi ...

  8. JavaScript扩展原型链浅析

    前言 上文对原型和原型链做了一些简单的概念介绍和解析,本文将浅析一些原型链的扩展. javaScript原型和原型链 http://lewyon.xyz/prototype.html 扩展原型链 使用 ...

  9. 三张图较为好理解JavaScript的原型对象与原型链

    最近从网上看到别人详细得讲解了js的原型对象和原型链,看完感觉是看得最清晰的一个,于是,摘录到自己博客里 对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与_ ...

随机推荐

  1. 数据可视化之 图表篇(三)体验Power BI最新发布的AI图表:分解树

    在刚刚发布的11月更新中,PowerBI界面全新改版,采用和Office套件相似的Ribbon风格,除了这个重大变化,还发布了一个AI黑科技图表:分解树(Decomposition Tree). 无论 ...

  2. javascript基础(四): 操作表单

    表单是什么?form-----DOM树 文本框----text 下拉框----select 单选框----radio 多选框----checkbox 隐藏域----hidden 密码框----pass ...

  3. 数据可视化之PowerQuery篇(七)Power Query应用技巧:批量更改列名

    https://zhuanlan.zhihu.com/p/130460772 ​今天分享一个PowerQuery的小技巧,导入到PowerBI中的数据,如果想要更改数据的列名,可以在PQ编辑器中直接双 ...

  4. 前端01 /HTML简单简绍

    前端01 /HTML简单简绍 目录 前端01 /HTML简单简绍 1.web服务本质 2.浏览器的工作流程 3.HTML是什么 4.web服务本质 5.HTML文档结构 6.HTML注释 6.标签语法 ...

  5. C与lua交互--lua调用栈分析(2)

    0 预备知识: 至少对Lua手册C API有简单的了解.lua5.3手册中文 1  Lua虚拟机的栈,如图: 假设虚拟机的栈有n个数据: 解释: 两种解释方式: A,栈顶开始, -1 ...-n B, ...

  6. [Qt2D绘图]-05绘图设备-QPixmap&&QBitmap&&QImage&&QPicture

    这篇笔记记录的是QPainterDevice(绘图设备,可以理解为一个画板) 大纲:     绘图设备相关的类:QPixmap QBitmap QImage QPicture     QPixmap ...

  7. java 两个数组相减结果

    public static void main(String[] args) { String[] a = new String[] { "1", "5", & ...

  8. ASP.Net Core 3.1 With Autofac ConfigureServices returning an System.IServiceProvider isn't supported.

    ASP.Net Core 3.1 With Autofac ConfigureServices returning an System.IServiceProvider isn't supported ...

  9. 题解 CF1359B 【New Theatre Square】

    题意 有一个 n×m 的广场,其中一部分要铺地砖,地砖有两种, 1 × 1 和 1×2 的,后者只能横着铺,其中, 1 × 1的单价为 x , 1 × 2 的单价为 y , 输入这个广场," ...

  10. Redis中的Scan命令踩坑记

    1 原本以为自己对redis命令还蛮熟悉的,各种数据模型各种基于redis的骚操作.但是最近在使用redis的scan的命令式却踩了一个坑,顿时发觉自己原来对redis的游标理解的很有限.所以记录下这 ...