JS
系列暂定 27 篇,从基础,到原型,到异步,到设计模式,到架构模式等,html
本篇是 JS
系列中最重要的一章,花费 3 分钟便可理解,若是你已了解,快速浏览便可。前端
本篇文章主讲构造函数、原型以及原型链,包括 Symbol
是否是构造函数、constructor
属性是否只读、prototype
、__proto__
、[[Prototype]]
、原型链。git
在JS中,万物皆对象,对象又分为普通对象和函数对象,其中 Object、Function 为 JS 自带的函数对象。github
let obj1 = {}; let obj2 = new Object(); let obj3 = new fun1() function fun1(){}; let fun2 = function(){}; let fun3 = new Function('some','console.log(some)'); // JS自带的函数对象 console.log(typeof Object); //function console.log(typeof Function); //function // 普通对象 console.log(typeof obj1); //object console.log(typeof obj2); //object console.log(typeof obj3); //object // 函数对象 console.log(typeof fun1); //function console.log(typeof fun2); //function console.log(typeof fun3); //function 复制代码
凡是经过 new Function()
建立的对象都是函数对象,其余的都是普通对象,Function Object 是经过 New Function()
建立的。设计模式
function Foo(name, age) { // this 指向 Foo this.name = name this.age = age this.class = 'class' // return this // 默认有这一行 } // Foo 的实例 let f = new Foo('aa', 20) 复制代码
每一个实例都有一个 constructor
(构造函数)属性,该属性指向对象自己。数组
f.constructor === Foo // true 复制代码
构造函数自己就是一个函数,与普通函数没有任何区别,不过为了规范通常将其首字母大写。构造函数和普通函数的区别在于,使用 new
生成实例的函数就是构造函数,直接调用的就是普通函数。浏览器
JS 自己不提供一个 class
实现。(在 ES2015/ES6 中引入了 class
关键字,但只是语法糖,JavaScript 仍然是基于原型的)。安全
let a = {}
实际上是 let a = new Object()
的语法糖let a = []
实际上是 let a = new Array()
的语法糖function Foo(){ ... }
实际上是 var Foo = new Function(...)
instanceof
判断一个函数是否为一个变量的构造函数Symbol 是基本数据类型,它并非构造函数,由于它不支持 new Symbol()
语法。咱们直接使用Symbol()
便可。markdown
let an = Symbol("An"); let an1 = new Symbol("An"); // Uncaught TypeError: Symbol is not a constructor 复制代码
可是,Symbol()
能够获取到它的 constructor 属性架构
Symbol("An").constructor; // ƒ Symbol() { [native code] } 复制代码
这个 constructor
其实是 Symbol 原型上的,即
Symbol.prototype.constructor; // ƒ Symbol() { [native code] } 复制代码
对于 Symbol,你还须要了解如下知识点:
Symbol()
返回的 symbol 值是惟一的Symbol("An") === Symbol("An"); // false 复制代码
Symbol.for(key)
获取全局惟一的 symbolSymbol.for('An') === Symbol.for("An"); // true 复制代码
它从运行时的 symbol 注册表中找到对应的 symbol,若是找到了,则返回它,不然,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中。
// 实现可迭代协议,使迭代器可迭代:Symbol.iterator function createIterator(items) { let i = 0 return { next: function () { let done = (i >= items.length) let value = !done ? items[i++] : undefined return { done: done, value: value } }, [Symbol.iterator]: function () { return this } } } const iterator = createIterator([1, 2, 3]); [...iterator]; // [1, 2, 3] 复制代码
// Symbol.toPrimitive 来实现拆箱操做(ES6 以后) let obj = { valueOf: () => {console.log("valueOf"); return {}}, toString: () => {console.log("toString"); return {}} } obj[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"} console.log(obj + "") // toPrimitive // hello 复制代码
// Symbol.toStringTag 代替 [[class]] 属性(ES5开始) let o = { [Symbol.toStringTag]: "MyObject" } console.log(o + ""); // [object MyObject] 复制代码
对于引用类型来讲 constructor
属性值是能够修改的,可是对于基本类型来讲是只读的。
function An() { this.value = "An"; }; function Anran() {}; Anran.prototype.constructor = An; // 原型链继承中,对 constructor 从新赋值 let anran = new Anran(); // 建立 Anran 的一个新实例 console.log(anran); 复制代码
这说明,依赖一个引用对象的 constructor 属性,并非安全的。
function An() {}; let an = 1; an.constructor = An; console.log(an.constructor); // ƒ Number() { [native code] } 复制代码
这是由于:原生构造函数(native constructors
)是只读的。
JS 对于不可写的属性值的修改静默失败(silently failed),但只会在严格模式下才会提示错误。
'use strict'; function An() {}; let an = 1; an.constructor = An; console.log(an.constructor); 复制代码
注意:null
和 undefined
是没有 constructor
属性的。
首先,贴上
图片来自于http://www.mollypages.org/tutorials/js.mp,请根据下文仔细理解这张图
在JS中,每一个对象都有本身的原型。当咱们访问对象的属性和方法时,JS 会先访问对象自己的方法和属性。若是对象自己不包含这些属性和方法,则访问对象对应的原型。
// 构造函数 function Foo(name) { this.name = name } Foo.prototype.alertName = function() { alert(this.name) } // 建立实例 let f = new Foo('some') f.printName = function () { console.log(this.name) } // 测试 f.printName()// 对象的方法 f.alertName()// 原型的方法 复制代码
全部函数都有一个 prototype
(显式原型)属性,属性值也是一个普通的对象。对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的 prototype
属性上,而非对象实例自己。
但有一个例外: Function.prototype.bind()
,它并无 prototype 属性
let fun = Function.prototype.bind(); // ƒ () { [native code] } 复制代码
当咱们建立一个函数时,例如
function Foo () {} 复制代码
prototype
属性就被自动建立了
从上面这张图能够发现,Foo
对象有一个原型对象 Foo.prototype
,其上有两个属性,分别是 constructor
和 __proto__
,其中 __proto__
已被弃用。
构造函数 Foo
有一个指向原型的指针,原型 Foo.prototype
有一个指向构造函数的指针 Foo.prototype.constructor
,这就是一个循环引用,即:
Foo.prototype.constructor === Foo; // true 复制代码
__proto__
每一个实例对象(object )都有一个隐式原型属性(称之为 __proto__
)指向了建立该对象的构造函数的原型。也就时指向了函数的 prototype
属性。
function Foo () {} let foo = new Foo() 复制代码
当 new Foo()
时,__proto__
被自动建立。而且
foo.__proto__ === Foo.prototype; // true 复制代码
即:
__proto__
发音 dunder proto,最早被 Firefox使用,后来在 ES6 被列为 Javascript 的标准内建属性。
[[Prototype]]
是对象的一个内部属性,外部代码没法直接访问。
遵循 ECMAScript 标准,someObject.[[Prototype]] 符号用于指向 someObject 的原型
__proto__
属性在 ES6
时才被标准化,以确保 Web 浏览器的兼容性,可是不推荐使用,除了标准化的缘由以外还有性能问题。为了更好的支持,推荐使用 Object.getPrototypeOf()
。
经过现代浏览器的操做属性的便利性,能够改变一个对象的
[[Prototype]]
属性, 这种行为在每个JavaScript引擎和浏览器中都是一个很是慢且影响性能的操做,使用这种方式来改变和继承属性是对性能影响很是严重的,而且性能消耗的时间也不是简单的花费在obj.__proto__ = ...
语句上, 它还会影响到全部继承来自该[[Prototype]]
的对象,若是你关心性能,你就不该该在一个对象中修改它的 [[Prototype]]。相反, 建立一个新的且能够继承[[Prototype]]
的对象,推荐使用Object.create()
。
若是要读取或修改对象的 [[Prototype]]
属性,建议使用以下方案,可是此时设置对象的 [[Prototype]]
依旧是一个缓慢的操做,若是性能是一个问题,就要避免这种操做。
// 获取(二者一致) Object.getPrototypeOf() Reflect.getPrototypeOf() // 修改(二者一致) Object.setPrototypeOf() Reflect.setPrototypeOf() 复制代码
若是要建立一个新对象,同时继承另外一个对象的 [[Prototype]]
,推荐使用 Object.create()
。
function An() {}; var an = new An(); var anran = Object.create(an); 复制代码
这里 anran
是一个新的空对象,有一个指向对象 an
的指针 __proto__
。
新生成了一个对象
连接到原型
绑定 this
返回新对象
function new_object() { // 建立一个空的对象 let obj = new Object() // 得到构造函数 let Con = [].shift.call(arguments) // 连接到原型 (不推荐使用) obj.__proto__ = Con.prototype // 绑定 this,执行构造函数 let result = Con.apply(obj, arguments) // 确保 new 出来的是个对象 return typeof result === 'object' ? result : obj } 复制代码
// 优化后 new 实现 function create() { // 一、得到构造函数,同时删除 arguments 中第一个参数 Con = [].shift.call(arguments); // 二、建立一个空的对象并连接到原型,obj 能够访问构造函数原型中的属性 let obj = Object.create(Con.prototype); // 三、绑定 this 实现继承,obj 能够访问到构造函数中的属性 let ret = Con.apply(obj, arguments); // 四、优先返回构造函数返回的对象 return ret instanceof Object ? ret : obj; }; 复制代码
__proto__
属性,属性值是一个普通的对象,该原型对象也有一个本身的原型对象(__proto__
) ,层层向上直到一个对象的原型对象为 null
。根据定义,null
没有原型,并做为这个原型链 中的最后一个环节。__proto__
(即它的构造函数的 prototype
)中寻找。每一个对象拥有一个原型对象,经过 __proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null
,这种关系被称为原型链(prototype chain)。根据定义,null
没有原型,并做为这个原型链中的最后一个环节。
原型链的基本思想是利用原型,让一个引用类型继承另外一个引用类型的属性及方法。
// 构造函数 function Foo(name) { this.name = name } // 建立实例 let f = new Foo('some') // 测试 f.toString() // f.__proto__.__proto__中寻找 复制代码
f.__proto__=== Foo.prototype
,Foo.prototype
也是一个对象,也有本身的__proto__
指向 Object.prototype
, 找到toString()
方法。
也就是
Function.__proto__.__proto__ === Object.prototype 复制代码
下面是原型链继承的例子
function Elem(id) { this.elem = document.getElementById(id) } Elem.prototype.html = function(val) { let elem = this.elem if (val) { elem.innerHTML = val return this // 链式操做 } else { return elem.innerHTML } } Elem.prototype.on = function( type, fn) { let elem = this.elem elem.addEventListener(type, fn) } let div1 = new Elem('div1') // console.log(div1.html()) div1.html('<p>hello</p>').on('click', function() { alert('clicked') })// 链式操做 复制代码
Symbol
是基本数据类型,并非构造函数,由于它不支持语法 new Symbol()
,但其原型上拥有 constructor
属性,即 Symbol.prototype.constructor
。constructor
是能够修改的,但对于基本类型来讲它是只读的, null
和 undefined
没有 constructor
属性。__proto__
是每一个实例对象都有的属性,prototype
是其构造函数的属性,在实例上并不存在,因此这两个并不同,但 foo.__proto__
和 Foo.prototype
指向同一个对象。__proto__
属性在 ES6
时被标准化,但由于性能问题并不推荐使用,推荐使用 Object.getPrototypeOf()
。__proto__
指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层向上,最终指向 null
,这就是原型链。null
)暂时就这些,后续我将持续更新
想看更过系列文章,点击前往 github 博客主页
1. 若有任何问题或更独特的看法,欢迎评论或直接联系瓶子君(公众号回复 123 便可)!
2. 欢迎关注:前端瓶子君,每日更新!