一、前言

介绍构造函数,原型,原型链。比如说经常会被问道:symbol是不是构造函数;constructor属性是否只读;prototype、[[Prototype]]和__proto__的区别;什么是原型链?等等问题

二、构造函数

1、什么构造函数

构造函数就是通过new关键词生成实例的函数。

js的构造函数和其他语言不一样,一般规范都是首字母大写。

首先我们来看一下这个栗子:

// saucxs
function Parent(age) {
this.age = age;
}
var p = new Parent(30);
console.log(p); //见下图
console.log(p.constructor); // ƒ Parent(age){this.age = age;}
p.constructor === Parent; // true
p.constructor === Object; // false

这就是一个典型的构造函数,构造函数本身也是个函数,与普通区别不大,主要区别就是:构造函数使用new生成实例,直接调用就是普通函数。

2、constructor属性

返回创建实例对象的Object构造函数的引用。此属性的值对函数本身的引用,而不是一个包含函数名称的字符串。

所有对象都会从它的原型上继承一个constructor属性

var o = {};
o.constructor === Object; // true var o = new Object;
o.constructor === Object; // true var a = [];
a.constructor === Array; // true var a = new Array;
a.constructor === Array // true var n = new Number(3);
n.constructor === Number; // true

那么普通函数创建的实例有没有constructor属性呢?

// saucxs
// 普通函数
function parent2(age) {
this.age = age;
}
var p2 = parent2(50);
console.log(p2);
// undefined // 普通函数
function parent3(age) {
return {
age: age
}
}
var p3 = parent3(50);
console.log(p3.constructor); //ƒ Object() { [native code] }
p3.constructor === parent3; // false
p3.constructor === Object; // true

上面代码说明:

(1)普通函数在内部有return操作的就有constructor属性,没有return的没有constructor属性;

(2)有constructor属性的普通函数的constructor属性值不是普通函数本身,是Object。

3、symbol是构造函数吗?

MDN 是这样介绍 `Symbol` 的

The `Symbol()` function returns a value of type **symbol**, has static properties that expose several members of built-in objects, has static methods that expose the global symbol registry, and resembles a built-in object class but is incomplete as a constructor because it does not support the syntax "`new Symbol()`".

Symbol是基本数据类型,作为构造函数它不完整,因为不支持语法new Symbol(),如果要生成实例直接使用Symbol()就可以的。

// saucxs
new Symbol(123); // Symbol is not a constructor Symbol(123); // Symbol(123)

虽然Symbol是基本数据类型,但是Symbol(1234)实例可以获取constructor属性值。

// saucxs
var sym = Symbol(123);
console.log( sym ); // Symbol(123) console.log( sym.constructor ); // ƒ Symbol() { [native code] }
sym.constructor === Symbol; //true
sym.constructor === Object; //false

这里的constructor属性来自哪里?其实是Symbol原型上的,默认为Symbol函数。

4、constructor的值是只读的吗?

回答:如果是引用类型的constructor属性值是可以修改的,如果是基本类型的就是只读的。

引用类型的情况,修改这个很好理解,比如原型链继承的方案中,就是对constructor重新赋值的修正。

// saucxs
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
}; function Bar() {} // 设置 Bar 的 prototype 属性为 Foo 的实例对象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World'; Bar.prototype.constructor === Object;
//true
// 修正 Bar.prototype.constructor 为 Bar 本身
Bar.prototype.constructor = Bar; var test = new Bar() // 创建 Bar 的一个新实例
console.log(test);

对于基本类型来说是只读的,比如:1, "saucxs", true, Symbol, null, undefined。null和undefined也是没有constructor属性的。

// saucxs
function Type() { };
var types = [1, "muyiy", true, Symbol(123)]; for(var i = 0; i < types.length; i++) {
types[i].constructor = Type;
types[i] = [ types[i].constructor, types[i] instanceof Type, types[i].toString() ];
}; console.log( types.join("\n") );
// function Number() { [native code] },false,1
// function String() { [native code] },false,muyiy
// function Boolean() { [native code] },false,true
// function Symbol() { [native code] },false,Symbol(123)

为什么会这样?因为创建他们的是只读的原生构造函数(native constructors),这个栗子说明依赖一个对象的constructor属性并不安全。

三、原型

3.1 prototype属性

每一个对象都拥有一个原型对象,对象以其原型为模板,从原型集成方法和属性,这些属相和方法都在对象的构造器函数的prototype属性上,而不是对象实例本身上。

上图发现:

1、Parent对象有一个原型对象Parent.prototype,原型对象上有两个属性,分别为:constructor和__proto__,其中__proto__已被弃用。

2、构造函数Parent有一个指向原型的指针constructor;原型Parent.prototype有一个指向构造函数的指针Parent.prototype.constrcutor,其实就是一个循环引用。

3.2 __proto__属性

上图中可以看到Parent原型(Parent.prototype)上有一个__proto__属性,这是一个访问器属性(即getter函数和setter函数)。作用:通过__proto__可以访问到对象的内部[[Prototype]](一个对象或者null)

`__proto__` 发音 dunder proto,最先被 Firefox使用,后来在 ES6 被列为 Javascript 的标准内建属性。

`[[Prototype]]` 是对象的一个内部属性,外部代码无法直接访问。

// saucxs
function Parent(){};
var p = new Parent();
console.log(p);
console.log(Parent.prototype);

1、p.__proto__获取的是对象的原型,__proto__是每一个实例上都有的属性

2、prototype是构造函数的属性;

3、p.__proto__和Parent.prototype指向同一个对象。

// saucxs
function Parent() {}
var p = new Parent();
p.__proto__ === Parent.prototype
// true

所以构造函数Parent,Parent.prototype和p之间的关系,如下图所示:

注意1:`__proto__` 属性在 `ES6` 时才被标准化

以确保 Web 浏览器的兼容性,但是不推荐使用,除了标准化的原因之外还有性能问题。为了更好的支持,推荐使用 `Object.getPrototypeOf()`。

如果要读取或修改对象的 `[[Prototype]]` 属性,建议使用如下方案,但是此时设置对象的 `[[Prototype]]` 依旧是一个缓慢的操作,如果性能是一个问题,就要避免这种操作。

如果要创建一个新对象,同时继承另一个对象的 `[[Prototype]]` ,推荐使用 `Object.create()`。

// saucxs
function Parent() {
age: 50
};
var p = new Parent();
var child = Object.create(p);

这里 `child` 是一个新的空对象,有一个指向对象 p 的指针 `__proto__`。

四、原型链

每一个对象拥有一个原型对象,通过__proto__指针指向上一个原型,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层层的,最终指向null。这种关系成为原型链(prototype chain),作用:通过原型链一个对象会拥有定义在其他对象中的属性和方法

// saucxs
function Parent(age) {
this.age = age;
} var p = new Parent(50);
p.constructor === Parent; // true

p.constructor指向Parent,那么是不是意味着p实例化存在constructor属性呢?并不存在,打印一下p:

有图可以知道,实例化对象p本身没有constructor属性,是通过原型链向上查找__proto__,最终找到constructor属性,该属性指向Parent

// saucxs
function Parent(age) {
this.age = age;
}
var p = new Parent(50); p; // Parent {age: 50}
p.__proto__ === Parent.prototype; // true
p.__proto__.__proto__ === Object.prototype; // true
p.__proto__.__proto__.__proto__ === null; // true

下图展示原型链运行机制。

五、总结

1、Symbol是基本数据类型,作为构造函数并不完整,因为不支持语法new Symbol(),但是原型上拥有constructor属性,即Symbol.prototype.constructor。

2、引用类型constructor属性值是可以修改的,但是对于基本类型的是只读的,当然null和undefined没有constructor属性。

3、__proto__是每个实例上都有的属性,prototype是构造函数的属性,这两个不一样,但是p.__proto__和Parent.prototype是指向同一个对象。

4、__proto__属性在ES6时被标准化,但是因为性能问题并不推荐使用,推荐使用Object.getPropertyOf()。

5、每个对象拥有一个原型对象,通过__ptoto_指针指向上一个原型,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层已成的,最终指向null,这就是原型链。

六、参考

1、原型对象

2、Objcet.prototype.constructor

3、Object.prototype.__proto__

4、Symbol

5、原型

文章首发地址(sau交流学习社区

javascript系列--认识并理解构造函数,原型和原型链的更多相关文章

  1. 深入理解JavaScript系列(42):设计模式之原型模式

    介绍 原型模式(prototype)是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象. 正文 对于原型模式,我们可以利用JavaScript特有的原型继承特性去创建对象的方式,也就是 ...

  2. 深入理解JavaScript系列(5):强大的原型和原型链

    前言 JavaScript 不包含传统的类继承模型,而是使用 prototypal 原型模型. 虽然这经常被当作是 JavaScript 的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大.实 ...

  3. 深入理解JavaScript系列(38):设计模式之职责链模式

    介绍 职责链模式(Chain of responsibility)是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系.将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象 ...

  4. 【转向Javascript系列】深入理解Generators

    随着Javascript语言的发展,ES6规范为我们带来了许多新的内容,其中生成器Generators是一项重要的特性.利用这一特性,我们可以简化迭代器的创建,更加令人兴奋的,是Generators允 ...

  5. 【转向Javascript系列】深入理解Web Worker

    本文首发在alloyteam团队博客,链接地址http://www.alloyteam.com/2015/11/deep-in-web-worker/ 上一篇文章<从setTimeout说事件循 ...

  6. Javascript系列:总体理解

    js是一个脚本客户端(浏览器)语言.和sql html类似.多练习. 没有排错的经验,弱类型语言,浏览器解释执行,出错也不会报错 预备 <!DOCTYPE html PUBLIC "- ...

  7. 深入理解JavaScript系列

    转自http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html 深入理解JavaScript系列(1):编写高质量JavaScript代码 ...

  8. 深入理解JavaScript系列(转自汤姆大叔)

    深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaScript ...

  9. [转]深入理解JavaScript系列

    文章转自:汤姆大叔-深入理解JavaScript系列文章 深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解Ja ...

随机推荐

  1. dotnet core 之 gRPC

    dotnet core gRPC 原文在本人公众号中,欢迎关注我,时不时的会分享一些心得 HTTP和RPC是现代微服务架构中很常用的数据传输方式,两者有很多相似之处,但是又有很大的不同.HTTP是一种 ...

  2. Git分支和版本回退

    一.分支 1.分支简单介绍 简单使用: 可以将git branch new_branch和git checkout new_branch两个命令合并成一个命令: git checkout -b new ...

  3. 洛谷 P1002过河卒

    洛谷 P1002过河卒 题目描述 棋盘上AA点有一个过河卒,需要走到目标BB点.卒行走的规则:可以向下.或者向右.同时在棋盘上CC点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点 ...

  4. Mybatis逆向工程的使用。

    指定配置文件与main运行生成 public class GeneratorSqlmap { public void generator() throws Exception { List<St ...

  5. Extjs 树菜单的自动展开数据的请求

    今天在做extjs开发的时候,在树菜单上遇到了一个坑,也许是我刚接触extjs 不熟的缘故 问题描述:后台设置的树自动展开,但是在前端总是只显示一条数据,但是数据确实都请求到了. 经过几个小时不屑的努 ...

  6. 实验吧——看起来有点难(sql盲注)

    题目地址:http://ctf5.shiyanbar.com/basic/inject/ 首先当然是拿admin/admin来试试啊,多次测试发现,有两种错误提示 1.数据库连接失败! 2.登录失败, ...

  7. Centos7下安装redis并能使得外网访问

    一.安装脚本 #!/bin/bash #FileName: install_redis_centos7.sh #Date: #Author: LiLe #Contact: @qq.com #Versi ...

  8. TP5.0整合webuploader实现多图片上传功能

    在https://github.com/fex-team/webuploader 下载webuploader并解压,解压后放到public里面.其中我把解压缩后的文件夹改名为webuploader,放 ...

  9. 解决debugJDK源码看不到局部变量的值

    背景:使用的jdk1.8.0_201 问题描述:在eclispe中调试代码进入到JDK源码中,想看到某个变量的值得变化,发现此变量的值没法看到 解决方案: 1.进入到你安装本机的jdk目录下,找到sr ...

  10. Linux 如何用命令查看binlog文件的创建时间

    目录 背景 分析 方法 注意 背景 MySQL在26日 16:23:49产生了大量的慢查询,在这段时间内,binlog文件刷新的很快(查看慢日志是mysql DML并发比较多),想知道写完一个binl ...