构造函数
就是提供一个生成对象的模板并描述对象基本结构的函数。一个构造函数能够生成多个对象,这些对象都有相同的结构css
构造函数
自己就是一个函数,不过为了规范通常将其首字母大写。构造函数
和 普通函数
的区别在于使用 new
生成实例的函数就是构造函数,直接调用的就是普通函数express
生成对象实例时必须使用 new
命令来调用构造函数,因此构造函数更合理的理解应该是 函数的构造调用
json
constructor
返回建立实例对象时构造函数的引用,此属性的值是对函数自己的引用,而不是一个包含函数名称的字符串数组
function Person(age) {
this.age = age;
}
var p = new Person(18);
p.constructor === Person; // true
p.constructor === Object; // false
复制代码
那普通函数建立的实例是否是必定没有 constructor
属性呢?不必定浏览器
// 普通函数
function person(age) {
this.age = age;
}
var p = person(20); // undefined
p.constructor; // Cannot read property 'constructor' of undefined
// 普通函数
function person(age) {
return {
age: age
}
}
var p = person(20);
p.constructor === Object; // true
复制代码
MDN
是这样介绍的安全
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()"markdown
Symbol
是基本数据类型,但做为构造函数来讲它并不完整,由于它不支持语法 new Symbol()
,所以认为其不是构造函数,若要生成实例直接使用 Symbol()
便可(来自 MDN
),每一个从 Symbol()
返回的值都是惟一的cookie
new Symbol(1); // Symbol is not a constructor
Symbol(1); // Symbol(1)
复制代码
虽然是基本数据类型,但 Symbol(1)
实例能够获取 constructor
属性值数据结构
var a = Symbol(1); // Symbol(1)
console.log(a.constructor); // ƒ Symbol() { [native code] }
复制代码
这里的 constructor
属性实际上是 Symbol
原型上的,即 Symbol.prototype.constructor
返回建立实例原型的函数, 默认为 Symbol
函数闭包
对于引用类型来讲 constructor
属性值是能够修改的,但对于基本类型来讲是只读的
引用类型状况其值可修改这个很好理解,好比原型链继承方案中就须要对 constructor
从新赋值进行修正
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
// 设置 Bar 的 prototype 属性为 Foo 的实例对象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello JS';
Bar.prototype.constructor === Object; // true
var test = new Bar() // 建立 Bar 的一个新实例
console.log(test);
复制代码
// 修正 Bar.prototype.constructor 为 Bar 自己
Bar.prototype.constructor = Bar;
var test = new Bar() // 建立 Bar 的一个新实例
console.log(test);
复制代码
对于基本类型来讲是只读的,如 1
、"1"
、true
、Symbol
(null
和 undefined
是没有 constructor
属性的)
function Type() {};
const types = [1, "1", true, Symbol(1)];
for(let i = 0, len = types.length; i < len; i ++) {
types[i].constructor = Type;
types[i] = [types[i].constructor, types[i] instanceof Type, types[i].toString()];
};
console.log(types.join("\n"));
复制代码
为何呢?由于建立它们的是只读的原生构造函数 native constructors
,这个例子也说明了依赖一个对象的 constructor
属性并不安全
new 运算符建立一个用户定义的对象类型的实例或具备构造函数的内置对象的实例。 ——(来自于MDN)
当代码 new Foo(...) 执行时,会发生如下事情:
Foo.prototype
的新对象被建立Foo
并将 this
绑定到新建立的对象上(new Foo
等同于 new Foo()
,即没有指定参数列表,Foo
不带任何参数调用的状况)new
表达式的结果,若构造函数没有显式返回一个对象,则使用步骤 1 建立的对象// 初版
function createNew() {
// 建立一个空的对象
let obj = new Object();
// 得到构造函数,arguments 中去除第一个参数
const Con = [].shift.call(arguments);
// 连接到原型
// 建立一个原型为构造器的 prototype 的空对象 obj
// const obj = Object.create(constructor.prototype);
obj.__proto__ = Con.prototype;
// 绑定 this 实现继承,使用 apply 改变构造函数 this 的指向到新建的对象,这样 obj 就能够访问到构造函数中的属性
Con.apply(obj, arguments);
// 返回对象
return obj;
};
复制代码
测试一下
function Car(color) {
this.color = color;
}
Car.prototype.start = function() {
console.log(this.color + " car start");
}
var car = createNew(Car, "black");
car.color;
// black
car.start();
// black car start
复制代码
上面的代码已经实现了 80%,如今继续优化。构造函数返回值有以下三种状况:
this
失效,实例 person 中只能访问到返回对象中的属性
function Person(age, name) {
this.age = age;
return {
name: name
}
}
const person = new Person(18, "tn");
person.age; // undefined
person.name; // "tn"
复制代码
实例 person 中只能访问到构造函数中的属性,和上面彻底相反
function Person(age, name) {
this.age = age;
}
const person = new Person(18, "tn");
person.age; // 18
person.name; // undefined
复制代码
实例 person 中只能访问到构造函数中的属性,和状况 1 相反,结果至关于没有返回值
function Person(age, name) {
this.age = age;
return "new person";
}
const person = new Person(18, "tn");
person.age; // 18
person.name; // undefined
复制代码
因此须要判断下返回值是否是一个对象,如果对象则返回该对象,否则返回新建立的 obj 对象,实现代码以下
// 第二版
function createNew() {
// 建立一个空的对象
let obj = new Object();
// 得到构造函数,arguments 中去除第一个参数
const Con = [].shift.call(arguments);
// 连接到原型,obj 能够访问到构造函数原型中的属性
obj.__proto__ = Con.prototype;
// 绑定 this 实现继承,obj 能够访问到构造函数中的属性
const ret = Con.apply(obj, arguments);
// 优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj;
};
复制代码
常常看到一句话说:万物皆对象
,对象就是属性的集合(对象里面的一切都是属性,只有属性没有方法,方法也是一种属性,由于它的属性表示为键值对的形式)。而在 JavaScript 中,建立对象有几种方式,如对象字面量
、经过构造函数 new
一个对象、Object.create()
暂且先无论上面的代码有什么意义,至少能看出都是对象且却存在着差别性
其实在 JavaScript 中能够将对象分为函数对象
和普通对象
函数对象
就是 JavaScript 中用函数来模拟的类实现,如 Object
、 Function
就是典型的函数对象下述代码中 obj一、obj二、obj三、obj4 都是普通对象,fun一、fun二、fun3 都是 Function 的实例,即函数对象
function fun1() {};
const fun2 = function() {};
const fun3 = new Function('name','console.log(name)');
const obj1 = {};
const obj2 = new Object();
const obj3 = new fun1();
const obj4 = new new Function();
console.log(typeof Object); // function
console.log(typeof Function); // function
console.log(typeof fun1); // function
console.log(typeof fun2); // function
console.log(typeof fun3); // function
console.log(typeof obj1); // object
console.log(typeof obj2); // object
console.log(typeof obj3); // object
console.log(typeof obj4); // object
复制代码
所以,全部 Function
的实例都是函数对象,其余均为普通对象,其中包括 Function
实例的实例
function Foo() {}
// 这个函数是 Function 的实例对象
// function 就是一个语法糖
// 内部调用了 new Function(...)
复制代码
JavaScript 中万物皆对象,而对象皆出自构造(构造函数)
个人理解是全部对象都是由 new
操做符后跟函数调用来建立的,字面量表示法只是语法糖(即本质也是 new
,功能不变、使用更简洁),不管是 function Foo()
仍是 let a = { b : 1 }
对于建立一个对象来讲,更推荐使用字面量的方式建立,由于使用 new Object()
的方式建立对象须要经过做用域链一层层找到 Object
,可是使用字面量的方式就没这个问题
function Foo() {};
// function 就是个语法糖
// 内部等同于 new Function()
let a = { b: 1 };
// 这个字面量内部也是使用了 new Object()
复制代码
Number
、String
、Boolean
、Array
、Object
、Function
、Date
、RegExp
、Error
等都是函数,并且是内置的原生构造函数,在运行时会自动出如今执行环境中
构造函数是为了建立特定类型的对象,这些经过同一构造函数建立的对象有相同原型,共享某些方法。如:全部的数组均可以调用 push
方法,由于它们有相同原型
原型和原型链都是来源于对象而服务于对象的概念
JavaScript 常被描述为一种基于原型的语言 (prototype-based language),这个和 Java 等基于类的语言不同
每一个对象拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的 prototype
属性上,而非对象实例自己
prototype - object that provides shared properties for other objects
在规范里,prototype
被定义为:给其它对象实例提供共享属性的对象。所以 prototype
本身自己也是对象,只是被用以承担某个职能罢了
只有函数才拥有该属性,它是
function
对象的一个显式原型属性,当声明一个函数时该属性就被自动建立了,它定义了构造函数制造出来的对象实例的公共祖先,经过该构造函数产生的对象能够继承该原型上的属性和方法
基本上全部函数都有这个属性,可是也有一个例外,若用如下方法建立一个函数,可发现这个函数是不具备 prototype
属性。由于 Function.prototype
是引擎建立出来的函数对象,引擎认为不须要给这个对象添加 prototype
属性
let fun = Function.prototype.bind();
复制代码
在 prototype
上面添加属性和方法,每一个构造出来的对象实例均可继承这些属性和方法。虽然每一个对象都是独立的,但它们都有共同的祖先,当访问这个对象的属性时,若对象自己没有该属性,则会往上找到它的原型,而后在原型上访问这个属性
prototype
有个默认属性 constructor
,指向一个函数,这个函数就是该对象的构造函数
Person.prototype.constructor === Person // true
复制代码
constructor
是个公有且不可枚举属性,一旦改变了函数的 prototype
,那新对象就没有这个属性(可经过原型链取到 constructor
)
注意,每一个对象都有其对应的构造函数,自己或者继承而来。单从constructor
这个属性来说只有 prototype
对象才有。每一个函数在建立时 JavaScript 会同时建立一个该函数对应的 prototype
对象
函数建立的对象.__proto__ === 该函数.prototype
函数.prototype.constructor === 该函数自己
故经过函数建立的对象即便本身没有 constructor
属性,它也能经过 __proto__
找到对应的constructor
,因此任何对象最终均可以找到其对应的构造函数
其实这个属性能够说是一个历史遗留问题,它有两个做用
xx.constructor.method
来扩展__proto__
一、首先须要明确:
__proto__
和constructor
是对象独有的;prototype
是函数独有的
二、但在 JavaScript 中,函数也是对象,所以函数也拥有__proto__
和constructor
属性
每一个对象都有该隐式原型属性
,指向了原型(如果构造函数建立的对象,则指向建立该对象的构造函数的原型)
这里用 __proto__
获取对象的原型,__proto__
是每一个对象实例上都有的属性,prototype
是构造函数的属性,这两个并不同,但 __proto__
和 prototype
指向同一个对象
__proto__
指向了 [[prototype]](一个对象或 null)
,因 [[prototype]]
是内部属性,并不能从外部访问到,所以有些浏览器实现了 __proto__
来访问
所以,ECMAScript 规范说 prototype
应当是一个隐式引用:
Object.getPrototypeOf(obj)
访问指定对象的 prototype
对象Object.setPrototypeOf(obj, anotherObj)
设置指定对象的 prototype
对象__proto__
,使得能够经过 obj.__proto__
直接访问原型,经过 obj.__proto__ = anotherObj
直接设置原型__proto__
属性归入了规范的一部分,以确保 Web 浏览器的兼容性__proto__
属性既不能被 for...in
遍历出来,也不能被 Object.keys(obj)
查找出来
其实 __proto__
是个定义在 Object.prototype
上的访问器属性,即用 getter
和 setter
定义的属性,访问对象的 obj.__proto__
属性,默认走的是 Object.prototype
对象上 __proto__
属性的 get/set
方法
Object.defineProperty(Object.prototype,'__proto__',{
get() {
console.log('get')
}
});
({}).__proto__;
console.log((new Object()).__proto__);
// get
// get
const weakMap = new WeakMap();
Object.prototype = {
get __proto__() {
return this['[[prototype]]'] === null ? weakMap.get(this) : this['[[prototype]]'];
},
set __proto__(newPrototype) {
if (!Object.isExtensible(newPrototype)) throw new TypeError(`${newPrototype} is not extensible`);
const isObject = typeof newPrototype === 'object' || typeof newPrototype === 'function';
if (newPrototype === null || isObject) {
// 若是以前经过 __proto__ 设置成 null
// 此时再经过给 __proto__ 赋值的方式修改原型都是徒劳
// 表现就是 obj.__proto__ = { a: 1 } 就像一个普通属性 obj.xxx = { a: 1 }
if (this['[[prototype]]'] === null) {
weakMap.set(this, newPrototype);
} else {
this['[[prototype]]'] = newPrototype;
}
}
},
// ... 其它属性如 toString,hasOwnProperty 等
};
复制代码
__proto__
属性在 ES6 时被标准化,以确保 Web 浏览器的兼容性,可是不推荐使用,除了标准化的缘由以外还有性能问题,为了更好的支持,推荐使用 Object.getPrototypeOf()
若一个对象的 __proto__
属性被赋值为 null
,这时它的原型确实已经被修改成 null
,但想再经过对 __proto__
赋值的方式设置原型时是无效的,这时 __proto__
和一个普通属性没有区别,只能经过 Reflect.setPrototypeOf
或 Object.setPrototypeOf
才能修改原型。Reflect.setPrototypeOf
之因此能修改原型是由于它是直接修改对象的原型属性,即内部直接对对象的 [[prototype]]
属性赋值,而不会经过 __proto__
的 getter
const obj = { name: 'xiaoming' };
obj.__proto__ = null;
console.log(obj.__proto__); // => undefined
console.log(Reflect.getPrototypeOf(obj)); // => null
// 再次赋值为 null
obj.__proto__ = null;
console.log(obj.__proto__); // => null
obj.__proto__ = { a: 1 };
console.log(obj.__proto__); // => { a: 1 }
// __proto__ 就像一个普通属性同样 obj.xxx = { a: 1 }
// 并无将原型设置成功
console.log(Reflect.getPrototypeOf(obj)); // => null
Reflect.setPrototypeOf(obj, { b: 2 });
// __proto__ 被设置为 null 后,obj 的 __proto__ 属性和一个普通的属性没有区别
console.log(obj.__proto__); // => { a: 1 }
// 使用 Reflect.setPrototypeOf 是能够设置原型的
console.log(Reflect.getPrototypeOf(obj)); // => { b: 2 }
复制代码
经过改变一个对象的 [[Prototype]]
属性来改变和继承属性会对性能形成很是严重的影响且性能消耗的时间也不是简单的花费在 obj.__proto__ = ...
语句上,它还会影响到全部继承自该 [[Prototype]]
的对象,若关心性能就不该该修改一个对象的 [[Prototype]]
若要读取或修改对象的 [[Prototype]]
属性,建议使用以下方案,可是此时设置对象的 [[Prototype]] 依旧是一个缓慢的操做,若性能是一个考虑问题,就要避免这种操做
// 获取
Object.getPrototypeOf();
Reflect.getPrototypeOf();
// 修改
Object.setPrototypeOf();
Reflect.setPrototypeOf();
复制代码
__proto__
存在于全部的对象上,是对象所独有的且指向它的原型对象。它的做用就是当你在访问一个对象属性时,若该对象内部不存在这个属性,则会去它的 __proto__
属性所指向的对象(原型对象,原型也是对象也有它本身的原型)上查找,若原型对象依旧不存在这个属性,则去其原型对象的 __proto__
属性所指向的原型对象上去查找...以此类推,直到找到 null
,返回 undefined,这个查找的过程也就构成了咱们常说的 原型链
由于在 JS 中是没有类的概念的,为了实现相似继承的方式,经过
__proto__
将对象和原型联系起来组成原型链
,得以让对象能够访问到不属于本身的属性
以前说对象的建立方式主要有两种,一种是 new
操做符后跟函数调用,另外一种是字面量表示法
第三种就是 ES5 提供的 Object.create()
方法,该方法会建立一个新对象,第一个参数接收一个对象,将会做为与新建立对象关联的原型对象,第二个可选参数是属性描述符(不经常使用,默认是 undefined
)
日常所看到的空对象其实并非严格意义上的空对象,它的原型对象指向Object.prototype
,还能够继承 hasOwnProperty
、toString
、valueOf
等方法
若要建立一个新对象同时继承另外一个对象的 [[Prototype]]
,推荐使用 Object.create()
若想生成一个不继承任何属性的对象,可以使用 Object.create(null)
若想生成一个日常字面量方法生成的对象,须要将其原型对象指向Object.prototype
let obj = Object.create(Object.prototype);
// 等价于
let obj = {};
复制代码
const obj= Object.create(Object.prototype);
obj.__proto__ === Object.prototype; // true
const obj = Object.create(null);
obj.__proto__ === Object.prototype; // false;
console.log(obj.__proto__); // undefined
复制代码
简易模拟 Object.create
function createObj(proto) {
const F = function() {};
F.prototype = proto;
return new F();
}
复制代码
当在一个对象 obj
上访问某个属性时,若该属性不存在于 obj
上,则会经过 __proto__
去对象的原型即 obj.__proto__
上去找这个属性,如有则返回该属性,没有则继续去对象 obj 的原型的原型即 obj.__proto__.__proto__
去找 ... 重复以上步骤,一直访问到 纯对象的原型
即 Object.prototype
,没有的话继续往上找即 Object.prototype.__proto__
,即 null
,此时直接返回 undefined
console.log(new Object().__proto__.__proto__); // null
Object.prototype.__proto__ === null; // null
复制代码
这就推出了原型链之因此叫原型链而不叫原型环,说明它是善始善终的,原型链的顶层就是 null
,返回 undefined
,因此原型链不会无限的找下去
所以原型链
能够描述为由对象的 __proto__
属性将对象和原型联系起来直到Object.prototype.__proto__
为null
的链就是原型链
function Student(name, grade) {
this.name = name;
this.grade = grade;
}
const stu = new Student();
console.log(stu.gender); // => undefined
复制代码
访问 stu.gender 的整个过程以下图:
函数 Student
的原型链应该是这样的
上文介绍了 prototype
和 __proto__
的区别,其中原型对象 prototype
是构造函数的属性,__proto__
是每一个实例对象上都有的属性,这两个并不同,但指向同个对象,如上面例子 stu.__proto__
和 Student.prototype
指向同个对象
那原型链的构建是依赖于 prototype
仍是 __proto__
呢?
上图中,Student.prototype
中的 prototype
并无构建成一条原型链,其只是指向原型链中的某一处。原型链的构建依赖于 __proto__
,如上图经过 stu.__proto__
指向 Student.prototype
,stu.__proto__.__proto__
指向 Object.prototyp
,如此一层一层最终连接到 null
能够这么理解,Student 是一个 constructor 也是一个 function,它身上有着 prototype 的 reference,只要调用 stu = new Student() 就会将
stu.__proto__
指向到 Student 的 prototype 对象
不要使用相似 Bar.prototype = Foo
,由于这不会执行 Foo
的原型,而是指向函数 Foo
。所以原型链将会回溯到 Function.prototype
而不是 Foo.prototype
,所以 Foo 原型上的方法将不会在 Bar 的原型链上
function Foo() {
return 'foo';
}
Foo.prototype.getMethod = function() {
return 'method';
}
function Bar() {
return 'bar';
}
Bar.prototype = Foo; // Bar.prototype 指向到函数 Foo
const bar = new Bar();
console.dir(bar);
bar.method(); // bar.getMethod is not a function
bar.__proto__ === Foo.prototype; // false
复制代码
经过一个对象改变了原型上的引用值类型的属性,则全部对象实例的这个属性值都会随之更改
依据当自身没有这个属性时就会向上往原型查询的说法,再次删除这个属性是否是就能够删除原型上的属性了?然而事实并无,因而可知对象实例并不能删除原型上的属性
谁调用这个方法,这个方法中的
this
就指向这个调用它的对象
日常判断一个变量的类型常常会使用 typeof
运算符,但对于引用类型来讲并不能很好区分(除了函数对象会返回 function
外其余都返回 object
)
来看一下 MDN
上对于 instanceof
运算符的描述
instanceof 运算符用于测试构造函数的 prototype 属性是否出如今对象实例的原型链中的任何位置
instanceof
和 typeof
很是的相似。instanceof
用于判断对象是不是某个构造函数的实例,若 obj instanceof A
,就说明 obj
是 A
的实例 f
它的原理一句话归纳就是:
obj instanceof 构造器 A
等同于判断 A 的 prototype 是否是 obj 的原型
instanceof
操做符左边是一个对象,右边是一个构造函数,在左边对象的原型链上查找(经过 __proto__
)直到找到右边构造函数的 prototype
属性就返回 true
,或查找到顶层 null
(Object.prototype.__proto__
),就返回 false
// 定义构造函数
function C(){}
function D(){}
const o1 = new C();
o1 instanceof C; // true,由于 Object.getPrototypeOf(o1) === C.prototype
o1 instanceof D; // false,由于 D.prototype 不在 o1 的原型链上
o1 instanceof Object; // true,由于 Object.prototype.isPrototypeOf(o1) 返回 true
C.prototype instanceof Object; // true,同上
C.prototype = {};
const o2 = new C();
o2 instanceof C; // true
o1 instanceof C; // false,C.prototype 指向了一个空对象,这个空对象不在 o1 的原型链上
D.prototype = new C(); // 继承
const o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true 由于 C.prototype 如今在 o3 的原型链上
复制代码
与 typeof
方法不一样的是,instanceof
方法要求开发者明确地确认对象为某特定类型
简单模拟实现
// 第一种
// 参数 obj 表示 instanceof 左边的对象
// 参数 Constructor 表示 instanceof 右边的构造函数
function myInstanceOf(obj, Constructor) {
// 取构造函数显示原型
let rightP = Constructor.prototype;
// 取对象隐式原型
let leftP = obj.__proto__;
// 到达原型链顶层还未找到则返回 false
if (leftP === null) {
return false;
}
// 对象实例的隐式原型等于构造函数显示原型则返回 true
if (leftP === rightP) {
return true;
}
// 递归查找原型链上一层
return myInstanceOf(obj.__proto__, Constructor)
}
// 第二种
function myInstanceof(left, right) {
let prototype = right.prototype;
left = left.__proto__;
while (true) {
if (left === null) return false;
if (prototype === left) return true;
left = left.__proto__;
}
}
复制代码
如今就能够解释一些比较使人费解的结果了
let fn = function() {};
let arr = [];
fn instanceof Function; // true
fn instanceof Object; // true
// 1. fn.__proto__ === Function.prototype;
// 2. fn.__proto__.__proto__ === Function.prototype.__proto__ === Object.prototype;
arr instanceof Array; // true
arr instanceof Object; // true
// 1. arr.__proto__ === Array.prototype;
// 2. arr.__proto__.__proto__ === Array.prototype.__proto__ === Object.prototype;
Object instanceof Object; // true
// 1. Object.__proto__ === Function.prototype;
// 2. Object.__proto__.__proto__ === Function.prototype.__proto__ === Object.prototype;
Function instanceof Function; // true
Function instanceof Object; // true
// 1. Function.__proto__ === Function.prototype;
// 2. Function.__proto__.__proto__ === Object.prototype;
Foo instanceof Function; // true
Foo instanceof Foo; // false
// 1. Foo.__proto__ === Function.prototype;
// 2. Foo.__proto__.__proto__ === Function.prototype.__proto__ = Object.prototype;
// 3. Foo.__proto__.__proto__.__proto__ === Object.prototype.__proto__ = null;
// 4. null
复制代码
总结:instanceof
运算符用于检查右边构造函数的 prototype
属性是否出如今左边对象的原型链中的任何位置,其实它表示的是一种原型链继承的关系
上面提到的
Object.__proto__ === Function.prototype
和Function.__proto__ === Function.prototype
究竟是为何呢?
ECMAScript 上的定义
The value of the [[Prototype]] internal property of the Object prototype object is null, the value of the [[Class]] internal property is "Object", and the initial value of the [[Extensible]] internal property is true
Object.prototype
表示 Object
的原型对象,其 [[Prototype]]
属性是 null
,访问器属性 __proto__
暴露了一个对象的内部 [[Prototype]]
。 Object.prototype
并非经过 Object
函数建立的,为何呢?看以下代码
function Foo() {
this.value = 'foo';
}
let foo = new Foo();
foo.__proto__ === Foo.prototype; // true
复制代码
实例对象的 __proto__
指向构造函数的 prototype
,即 foo.__proto__
指向 Foo.prototype
,但 Object.prototype.__proto__
是 null
,因此 Object.prototype
并非经过 Object
函数建立的,那它如何生成的?其实 Object.prototype
是引擎根据 ECMAScript
规范创造的一个对象
因此能够说:全部实例都是对象,可是对象不必定都是实例
不考虑 null
的状况下,Object.prototype
就是原型链的顶端,全部对象实例均可以继承它的 toString
等方法和属性
ECMAScript 上的定义
The Function prototype object is itself a Function object (its [[Class]] is "Function").
The value of the [[Prototype]] internal property of the Function prototype object is the standard built-in Object prototype object.
The Function prototype object does not have a valueOf property of its own; however, it inherits the valueOf property from the Object prototype Object
Function.prototype
对象是一个函数(对象),其 [[Prototype]]
内部属性值指向内建对象 Object.prototype
。Function.prototype
对象自身没有 valueOf
属性,其从 Object.prototype
对象继承了 valueOf
属性
Function.prototype
的 [[Class]]
属性是 Function
,因此这是一个函数,但又不大同样。为何这么说呢?由于只有函数才有 prototype
属性,但并非全部函数都有这个属性,由于 Function.prototype
这个函数就没有
Function.prototype
// ƒ () { [native code] }
Function.prototype.prototype
// undefined
复制代码
下面这个函数也没有 prototype
属性
let fun = Function.prototype.bind();
// ƒ () { [native code] }
fun.prototype
// undefined
复制代码
为何没有呢?个人理解是 Function.prototype
是引擎建立出来的函数,引擎认为不须要给这个函数对象添加 prototype
属性,否则 Function.prototype.prototype…
将无休无止而且没有存在的意义
Function.prototype
不可写、不可配置、不可遍历,即它永远指向固定的一个对象且是其余全部函数的原型对象,全部函数自己的 __proto__
指向它
引擎首先建立了 Object.prototype
,而后建立了 Function.prototype
而且经过 __proto__
将二者联系了起来
JS 中 Obejct
和 Function
都是构造函数(构造函数也是函数),和 object
、function
不是一个东西,分别用于建立 对象
与 函数
实例
ECMAScript 上的定义
The value of the [[Prototype]] internal property of the Object constructor is the standard built-in Function prototype object
Object
做为构造函数时,其 [[Prototype]]
内部属性值指向 Function.prototype
,即
Object.__proto__ === Function.prototype; // true
复制代码
Object
的全貌是:function Object() { ... }
,它是普通对象的构造函数,当 var foo = {}
时至关于实例化 Object
,即 new Object()
使用 new Object()
建立新对象时,这个新对象的 [[Prototype]]
内部属性指向构造函数的 prototype
属性,对应就是 Object.prototype
固然也能够经过对象字面量等方式建立对象
[[Prototype]]
值是 Object.prototype
[[Prototype]]
值是 Array.prototype
function f(){}
函数建立的对象,其 [[Prototype]]
值是 Function.prototype
new fun()
建立的对象,其中 fun
是由 JavaScript
提供的内建构造器函数之一(Object
, Function
, Array
, Boolean
, Date
, Number
, String
等),其 [[Prototype]]
值是 fun.prototype
JavaScript
构造器函数建立的对象,其 [[Prototype]]
值就是该构造器函数的 prototype
属性// 原型链:o.__proto__ -> Object.prototype -> null
let o = {a: 1};
// 原型链:a -> Array.prototype -> Object.prototype -> null
let a = ["yo", "whadup", "?"];
// 原型链:f -> Function.prototype -> Object.prototype -> null
function f(){
return 1;
}
// 原型链:fun -> Function.prototype -> Object.prototype -> null
let fun = new Function();
// 原型链:foo -> Foo.prototype -> Object.prototype -> null
function Foo() {}
let foo = new Foo();
// 原型链:foo -> Object.prototype -> null
function Foo() {
return {};
}
let foo = new Foo();
复制代码
ECMAScript 上的定义
The Function constructor is itself a Function object and its [[Class]] is "Function". The value of the [[Prototype]] internal property of the Function constructor is the standard built-in Function prototype object
Function
构造函数是一个函数对象,其 [[Class]]
属性是 Function
。Function
的 [[Prototype]]
属性指向了 Function.prototype
,即
Function.__proto__ === Function.prototype; // true
复制代码
Function
的全貌是:function Function() { ... }
,它是函数对象的构造函数,当 function foo() {}
时至关于实例化 Function
,即 new Function()
咱们知道函数的本质是经过 new Function()
生成的,但Function.prototype
是引擎本身建立的,因此又能够得出一个结论
不是全部函数都是
new Function()
产生的
先看下面代码
Object instanceof Function; // true
Object.__proto__ === Function.prototype; // true
Function instanceof Object; // true
Function.__proto__.__proto__ === Object.prototype; // true
Object instanceof Object; // true
Object.__proto__.__proto__ === Object.prototype; // true
Function instanceof Function; // true
Function.__proto__ === Function.prototype; // true
复制代码
Object
构造函数继承了 Function.prototype
,一切函数对象
都直接继承自 Function
对象(系统内置的构造函数),函数对象
包括了 Function
、Object
、Array
、String
、Number
、RegExp
、Date
等,Function
其实不只用于构造函数,它也充当了 函数对象
的构造器
同时 Function
构造函数继承了 Object.prototype
,这里就产生了 鸡和蛋
的问题。由于 Function.prototype
和 Function.__proto__
都指向 Function.prototype
对于 Function.__proto__ === Function.prototype
这一现象有 2 种解释,争论点在于 Function
对象是否是由 Function
构造函数建立的一个实例?
YES
:按照 JavaScript
中实例的定义,a
是 b
的实例即 a instanceof b
为 true
,默认判断条件就是 b.prototype
在 a
的原型链上。而 Function instanceof Function
为 true
,本质上即 Object.getPrototypeOf(Function) === Function.prototype
,正符合此定义NO
:Function
是 built-in
的对象,即并不存在 Function 对象由 Function 构造函数建立
这样显然会形成鸡生蛋蛋生鸡的问题。实际上当直接写一个函数时(如 function f() {}
或 x => x
),也不存在调用 Function
构造器,只有在显式调用 Function
构造器时(如 new Function('x', 'return x')
)才有我的偏向于第二种解释,即先有 Function.prototype
而后有 function Function()
,因此就不存在鸡生蛋蛋生鸡问题了,把 Function.__proto__
指向 Function.prototype
,我的的理解是:其余全部的构造函数均可以经过原型链找到 Function.prototype
,且 Function
本质也是一个函数对象,事实上 Function 只是一个祖先、一个构造函数,并非一个实例出来的函数对象,因此原本不必拥有 __proto__
这个属性,但这样的话会显得 Function
很另类,因而也给它加上属性 __proto__
并指向 Function.prototype
,只是为了代表 Function
做为一个原生构造函数,自己也是一个函数对象,并且这也保证了原型链的完整,让 Function
能够获取定义在 Object.prototype
上的方法
一切函数对象(包括 Object 对象) 都直接继承自 Function 对象,Function 对象直接继承本身,最终继承自 Object 对象,Object 和 Function 是互相继承的关系
有了
Function.prototype
之后才有了function Function()
,而后其余的构造函数都是function Function()
生成的
一切对象都继承自 Object.prototype
,而一切函数对象
都继承自 Function.prototype
(Function.prototype
最终继承自 Object.prototype
),即普通对象和函数对象的区别是:普通对象直接继承了 Object.prototype
,而函数对象在中间还继承了 Function.prototype
所以能够得出如下总结:
Object
是全部对象的爸爸
,全部对象均可以经过__proto__
找到它Function
是全部函数的爸爸,全部函数均可以经过__proto__
找到它- 全部经过字面量表示法建立的普通对象的构造函数为
Object
- 全部原型对象都是普通对象,构造函数为
Object
- 全部函数的构造函数是
Function
Function.prototype
和Object.prototype
没有原型对象Function.prototype
和Object.prototype
是两个由引擎建立出来的特殊对象,除了这两个特殊对象,其余对象都是经过构造器new
出来的- 函数的
prototype
是一个对象,即原型。对象的__proto__
指向原型,__proto__
将对象和原型链接起来组成了原型链Function.prototype.__proto__ === Object.prototype
Object.__proto__ === Function.prototype
Function.__proto__ === Function.prototype
Function.__proto__ === Object.__proto__
Object.prototype.__proto__ === null
Object => Function.prototype => Object.prototype => null
Function => Function.prototype => Object.prototype => null
如果自定义的构造函数,造成的原型链以下:Foo => Function.prototype => Object.prototype => null
经过自定义构造函数实例化的对象,造成的原型链以下:obj => Foo.prototype => Object.prototype => null
JavaScript 内置类型是浏览器内核自带的,浏览器底层对 JavaScript 的实现基于 C/C++,那么浏览器在初始化 JavaScript 环境时都发生了什么?
至此全部内置类型构建完成
曾经 lodash 爆出了一个严重的安全漏洞:Lodash 库爆出严重安全漏洞,波及 400 万+项目,这个安全漏洞就是因为原型污染致使的,Lodash 库中的函数“defaultsDeep”颇有可能会被欺骗添加或修改 Object.prototype 的属性,最终可能致使 Web 应用程序崩溃或改变其行为,具体取决于受影响的用例
虽说任何一个原型被污染了都有可能致使问题,但通常提原型污染说的就是 Object.prototype
被污染
'use strict';
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const path = require('path');
const isObject = (obj) => obj && obj.constructor && obj.constructor === Object;
function merge(a, b) {
for (var attr in b) {
if (isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a;
}
function clone(a) {
return merge({}, a);
}
// Constants
const PORT = 8080;
const HOST = '127.0.0.1';
const admin = {};
// App
const app = express();
app.use(bodyParser.json());
app.use(cookieParser());
app.use('/', express.static(path.join(__dirname, 'views')));
app.post('/signup', (req, res) => {
var body = JSON.parse(JSON.stringify(req.body));
var copybody = clone(body);
if (copybody.name) {
res.cookie('name', copybody.name).json({
done: 'cookie set',
});
} else {
res.json({
error: 'cookie not set',
});
}
});
app.get('/getFlag', (req, res) => {
var аdmin = JSON.parse(JSON.stringify(req.cookies));
if (admin.аdmin == 1) {
res.send('hackim19{}');
} else {
res.send('You are not authorized');
}
});
app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);
复制代码
这段代码的漏洞就在于 merge 函数上,能够这样攻击:
curl -vv --header 'Content-type: application/json' -d '{"__proto__": {"admin": 1}}' 'http://127.0.0.1:4000/signup';
curl -vv 'http://127.0.0.1/getFlag'
复制代码
首先请求 /signup
接口,在 NodeJS
服务中,咱们调用了有漏洞的 merge
方法,并经过 __proto__
为 Object.prototype
(由于 {}.__proto__ === Object.prototype
) 添加上一个新的属性 admin
且值为 1
再次请求 getFlag
接口访问了 Object
原型上的 admin
,条件语句 admin.аdmin == 1
为 true
,服务被攻击
其实原型污染大多发生在调用会修改或扩展对象属性的函数时,如 lodash 的 defaults、jQuery 的 extend,预防原型污染最主要仍是要有防患意识,养成良好的编码习惯
Object.create(null)
Object.create(null)
建立没有原型的对象,即使对它设置 __proto__
也没有用,由于它的原型一开始就是 null
,没有 __proro__
的 setter
Object.freeze(obj)
能够经过 Object.freeze(obj)
冻结对象 obj,被冻结的对象不能被修改属性,成为不可扩展对象。不能修改不可扩展对象的原型,不然会抛 TypeError:
const obj = Object.freeze({ name: 'xiaoHong' });
obj.xxx = 666;
console.log(obj); // => { name: 'xiaoHong' }
console.log(Object.isExtensible(obj)); // => false
obj.__proto__ = null; // => TypeError: #<Object> is not extensible
复制代码
关于原型污染可阅读:最新:Lodash 严重安全漏洞背后你不得不知道的 JavaScript 知识
原型存在的意义就是组成原型链:引用类型皆对象,每一个对象都有原型,原型也是对象,也有它本身的原型,一层一层的组成了原型链
原型链存在的意义就是继承:访问对象属性时,在对象自己找不到,就在原型链上一层一层往上找,说白了就是一个对象能够访问其余对象的属性
继承存在的意义就是属性共享:好处一是代码重用(字面意思);好处二是可扩展,不一样对象可能继承相同的属性,也能够定义只属于本身的属性
将父类的实例做为子类的原型
function Parent() {
this.name = 'tn';
}
Parent.prototype.getName = function () {
console.log(this.name);
}
function Son () {};
// 关键,建立 Parent 的实例并将该实例赋值给 Son.prototype
Son.prototype = new Parent();
const son1 = new Son();
console.log(son1.getName()); // tn
// 缺点
function Parent () {
this.names = ['licy', 'tn'];
}
function Son () {}
Son.prototype = new Parent();
const son1 = new Son();
son1.names.push('yayu');
console.log(son1.names); // ["licy", "tn", "yayu"]
const son2 = new Son();
console.log(son2.names); // ["licy", "tn", "yayu"]
复制代码
prototype
对父类进行实例化实现的,所以在构建子类实例时是没法向父类传递参数的,于是在实例化父类时也没法对父类构造函数内的属性进行初始化使用父类的构造函数来加强子类实例,利用 call
和 apply
可改变 this
指向的特色,将父类构造函数内容复制给子类构造函数,因为父类中给 this
绑定属性,所以子类天然也就继承父类的共有属性,这是全部继承中惟一不涉及到 prototype
的继承
function Parent (name) {
this.books = ['js','css'];
this.name = name;
}
Parent.prototype.showBooks = function() {
console.log(this.books);
}
function Son (name) {
Parent.call(this, name);
}
const son1 = new Son('tn');
console.log(son1.name); // tn
son1.showBooks(); // TypeError: son1.showBooks is not a function
const son2 = new Son('licy');
console.log(son2.name); // licy
function SuperType(){
this.color=["red","green","blue"];
}
function SubType(){
//继承自 SuperType
SuperType.call(this);
}
const instance1 = new SubType();
instance1.color.push("black");
console.log(instance1.color); //["red", "green", "blue", "black"]
const instance2 = new SubType();
console.log(instance2.color); //["red", "green", "blue"]
复制代码
原型链继承
和借用构造函数继承
结合
用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){ console.log(this.name); };
function SubType(name, age){
// 继承属性
// 第二次调用 SuperType()
// 第二次又给子类的构造函数添加了父类的 name, colors 属性
// 使用子类建立的实例对象上的同名属性覆盖了子类原型中的同名属性,这形成了性能浪费
SuperType.call(this, name);
this.age = age;
}
// 继承方法,构建原型链
// 第一次调用 SuperType()
// 第一次给子类的原型添加了父类的 name, colors 属性
SubType.prototype = new SuperType();
// 重写 SubType.prototype 的 constructor 属性,指向本身的构造函数 SubType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){ alert(this.age); };
const instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); //["red", "blue", "green", "black"]
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
const instance2 = new SubType("Greg", 27);
console.log(instance2.colors); //["red", "blue", "green"]
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
复制代码
ES5 Object.create
的模拟实现,利用一个空对象做为中介,将传入的对象做为该空对象构造函数的原型,object()
对传入的对象执行了一次浅复制
function object(obj){
// 声明一个过渡对象
function F(){};
// 过渡对象的原型继承传入的对象
F.prototype = obj;
// 返回过渡对象的实例
return new F();
}
const person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
const anotherPerson = object(person);
const yetAnotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
console.log(anotherPerson.name) // "Greg"
console.log(yetAnotherPerson.name) // "Nicholas"
console.log(yetAnotherPerson.friends) //["Shelby", "Court", "Van", "Rob"]
复制代码
Object.create()
的方法可以代替上面的 object
方法,Object.create()
方法规范化了原型式继承function createAnother(original){
const clone = object(original); // 经过调用 object() 函数建立一个新对象
// 或
const clone = Object.create(o);
clone.sayHi = function(){
// 以某种方式来加强对象
alert("hi");
}
return clone; // 返回这个对象
}
const person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
复制代码
寄生组合式继承是寄生式继承和借用构造函数继承的组合,只调用了一次父类构造函数,解决了组合继承有会两次调用父类的构造函数形成浪费的缺点
function object(o) {
//声明一个过渡对象
function F() {}
//过渡对象的原型继承父对象
F.prototype = o;
//返回过渡对象的实例,该对象的原型继承了父对象
return new F();
}
function prototype(child, parent) {
// 复制一份父类的原型副本到变量中
const prototype = object(parent.prototype);
// 加强对象,修正由于重写子类的原型致使子类的 `constructor` 属性被修改
prototype.constructor = child;
// 设置子类原型
child.prototype = prototype;
}
// 使用时
prototype(Child, Parent);
复制代码
引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:
这种方式的高效率体现它只调用了一次 Parent
构造函数而且所以避免了在 Parent.prototype
上面建立没必要要的、多余的属性。与此同时原型链还能保持不变,所以可以正常使用 instanceof
和 isPrototypeOf
。开发人员广泛认为寄生组合式继承是引用类型最理想的继承范式,也是如今不少库实现的方法
封装
function inherit (Target, Origin) {
// 声明一个过渡对象
function F () {};
// 过渡对象的原型继承父对象,建立了父类原型的浅复制
F.prototype = Origin.prototype;
// 返回过渡对象的实例,该对象的原型继承了父对象
Target.prototype = new F();
// 修正子类原型的构造函数
Target.prototype.constructor = Target;
// 没法知道本身真正继承至谁(记住最好,也不强求)
// 为了保存一下它的父类,也用一个 uber 来记录一下父类
// 由于 super 是保留字不能使用,因此使用了 uber
Target.prototype.uber = Origin.prototype;
}
复制代码
雅虎的高端写法,采用闭包的私有化变量
var inherit = (function () {
var F = function () {};
return function (Target, Origin) {
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target;
Target.prototype.uber = Origin.prototype;
}
}());
复制代码
Object.assign
会把 OtherSuperClass
原型上的方法属性拷贝到 MyClass
原型上,使 MyClass
的全部实例均可使用 OtherSuperClass
上的方法
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 从新指定constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function() {
// do something
};
复制代码
ES6 的继承和寄生组合继承类似,本质上 ES6 继承是 ES5 继承的一种语法糖extends
关键字主要用于类声明或类表达式中,以建立一个类表示该类是另外某个类的子类
constructor
表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError
错误,若没有显式指定构造方法,则会添加默认的 constructor
方法,例子以下
class Rectangle {
// constructor
constructor(height, width) {
this.height = height; this.width = width;
}
// Getter
get area() { return this.calcArea() }
// Method
calcArea() { return this.height * this.width; }
}
const rectangle = new Rectangle(10, 20);
console.log(rectangle.area); // 输出 200
// 继承
class Square extends Rectangle {
constructor(length) {
super(length, length); // 若是子类中存在构造函数,则须要在使用“this”以前首先调用 super()。
this.name = 'Square';
}
get area() { return this.height * this.width; }
}
const square = new Square(10);
console.log(square.area); // 输出 100
复制代码
extends
继承的核心代码以下,其实现和上述的寄生组合式继承方式相似
// extends 继承的核心代码以下,其实现和上述的寄生组合式继承方式类似
function _inherits(subType, superType) {
// 建立对象,建立父类原型的一个副本
// 加强对象,弥补因重写原型而失去的默认的constructor 属性
// 指定对象,将新建立的对象赋值给子类的原型
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
value: subType,
enumerable: false,
writable: true,
configurable: true
}
});
if (superType) {
Object.setPrototypeOf ?
Object.setPrototypeOf(subType, superType) :
subType.__proto__ = superType;
}
}
复制代码
总结
ReferenceError
let p = new Rectangle();
// ReferenceError
class Rectangle {}
复制代码
Class extends
是 ES5 继承的语法糖ES5
的继承实质上是先建立子类的实例对象,而后再将父类的方法添加到 this
上
Child.prototype = new Parent() || Parent.apply(this) || Parent.call(this)
复制代码
ES6
的继承有所不一样,在 ES6 class
中,实质上是先建立父类的实例对象 this
,而后再用子类的构造函数修改 this
。子类必须在 constructor
方法中调用 super
方法,不然新建实例时会报错。这是由于子类没有本身的 this
对象而是继承父类的 this
对象,而后对其进行加工
function Page() {
return this.hosts;
}
Page.hosts = ['h1'];
Page.prototype.hosts = ['h2'];
const p1 = new Page();
const p2 = Page();
console.log(p1.hosts); // undefined
console.log(p2.hosts); // Uncaught TypeError: Cannot read property 'hosts' of undefined
复制代码
缘由分析
new
时若 return
了对象,则会直接拿这个对象做为 new
的结果,所以 p1
应该是 this.hosts
的结果,而在 new Page()
时,this
是一个以 Page.prototype
为原型的 target
对象,因此这里 this.hosts
能够访问到 Page.prototype.hosts
即 ['h2']
。所以 p1
就是等于 ['h2']
,['h2']
没有 hosts
属性因此返回 undefined
console.log(p2.hosts)
会报错是由于 p2
是直接调用 Page
构造函数,这个时候 this
指向全局对象,全局对象并没 hosts
属性,所以返回 undefined
,往 undefined
上访问 hosts
固然报错