构造函数
new 操作符用于创建一个给定构造函数的实例对象
调用方式
构造函数通过 new 操作符调用,创建出来的实例对象可以访问构造函数中的属性,也可以访问原型链中的属性。
new 操作背地里创建一个连接到该函数的 prototype 到新对象上,同时 this 也会被绑定到新对象上,并将该对象作为返回值返回,该对象有自己的 this,通过对象调用构造函数中的方法或属性。
new 的过程
- 隐式的创建了一个新对象(obj),
- 将对象与构造函数通过原型链连接起来
- 将构造函数中的this绑定到新建的对象obj上
- 根据构造函数返回类型作判断,如果是原始值则被忽略仍然返回this,如果是返回对象,直接返回
function Person(name, age){
this.name = name;
this.age = age;
}
const person1 = new Person('Tom', 20)
console.log(person1) // Person {name: "Tom", age: 20}
person1.sayName() // 'Tom'
实例创建的流程图:
构造函数的返回值
- 没有 return 默认返回 新创建的对象。
- 若 return 基础数据类型, 无效,仍然将 this 返回。
- 若 return 引用数据类型,则返回引用数据类型地址。
function Person(name,age){
this.name = name
this.age = age
this.sayName = function(){
console.log('my name is ',this.name)
}
}
// 通过 new 实例化一个 person 对象
const person1 = new Person('Tom',20)
// 构造函数的函数名与类名相同,即是函数名又是类名
console.log(person1.constructor === Person ) // ====>> true
// 用instanceof 可以检查一个对象是否是一个类的实例
console.log(person1 instanceof Person) // ====>> true
// 所有对象都是Object对象的后代,所以任何对象和Object做instanceof都会返回true
console.log(person1 instanceof Object) // ====>> true
手写实现 new
function myNew(Func,...args){
// 1. 创建新对象
let obj ={}
// 2. 将新对象的原型与构造函数的原型对象进行关联
obj.__proto__ = Func.prototype
// 3.将构建函数的 this 指向新对象
let result = Func.apply(obj,args)
// 4. 判断构造函数的返回值类型
return result instanceof Object ? result : obj
}
// ============= 测试代码 ====================
function Person(name, age) {
this.name = name;
this.age = age;
return 1
}
Person.prototype.say = function () {
console.log(this.name)
}
let p = myNew(Person, "huihui", 20)
console.log(p) // Person {name: "huihui", age: 20}
p.say() // huihui
普通函数
调用方式
可直接调用,由 window 调用,没有自己的 this
返回值:
- 没有return 默认return undefined
- 有 return 则返回函数体中 return 的内容
function person {
console.log('Tom')
}
// 直接调用,this 指向 window
person()
小结
函数在定义时是无法区分是否为构造函数, 只有在调用时才可以区分出来
任何函数只要通过 new 操作符调用的就可作为构造函数
构造函数首字母大写,普通函数采用驼峰命名
原型
每个对象都有__proto__属性,但只有函数对象才有 prototype 属性
prototype(原型对象)
每个函数都有一个 prototype 属性,该属性为一个指针,指向原型对象,该对象用于存放所有实例共有的属性和方法。
person1.__proto__ === Person.prototype
// 返回对象的原型
Object.getPrototypeOf(person1) === Person.prototype
constrcutor
所有的原型对象都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的构造函数(Person)。
每个对象都能找到其对应的构造函数,这个 constructor 可能是对象自己本身显式定义的或者通过 **proto** 在原型链中找到的。
通过函数 new 创建的实例对象即使自己没有constructor 属性,它也能通过 **proto** 找到对应的 prototype上的 constructor,所以任何对象最终都可以找到其构造函数
Person.prototype.constructor === Person
// person1 本身并不具备 constructor 属性,通过原型链继承而来
person1.constructor === Person
person1.hasOwnProperty('constructor') // false
person1.constructor === Person.prototype.constructor
__proto__
每个实例化对象中都有一个隐藏的 proto 属性,该属性指 向构造函数的原型对象(即父类对象)。实例与构造函数之间没有直接的关系,通过__proto__ 与原型对象创建关系
虽然实例中不包含属性和方法, 但通过原型链的查找实现了该功能
原型链 ( __proto__ 与prototype)
每个对象通过__proto__属性指向它的原型对象,这个原型对象又有自己的原型,直到某个对象的原型为 null 为止,这种一级一级的链结构就称为原型链。
- 原型就是我们的prototype
- 链就是__proto__,它让整个链路连接起来。
显示原型
利用函数上的prototype属性查找原型。
隐式原型
利用对象上的__proto__ 属性查找原型,这个属性指向当前对象的构造函数的原型对象,
__proto__这个属性是对象上的属性,所以可以在实例对象上面使用。
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层Object为止。
Object是JS中所有对象数据类型的基类(最顶层的类),Object.prototype. __proto__ === null
由上图可知存在三个构造函数 Person()、 Function()、 Object()
函数皆由 Function() 构造而来,Function()本身也是函数,所以Function()也是自己的实例
所有函数都是 Function 的实例(对象)
所有引用类型都继承了 Object,而这个继承也会通过原型链实现,
因此函数的默认原型都是 Object 的实例,默认原型都会包含一个内部指针,指向 Object.prototype。
所以函数的 __proto__ 指向了 函数的原型 Function.prototype。
构造函数实例化的对象上存在__proto__属性并指向 Object.prototype。
Person.prototype = function.prototype
function.prototype.__proto__ = Object.prototype
object.prototype.__proto__ =null // object 原型的尽头是null
Person() 构造函数是由函数而来函数的 prototype 指向 function.prototype (函数的原型)
所以由此得出一下结论:
// 原型对象也是对象,所以它也有原型即 Object.prototype
Person.prototype.__proto__ === Object.prototype
- Person 函数是属于Function 这个构造函数的,由Function 构造而来Person 的,为 Function() 的实例对象
- 构造函数作为实例对象时
__proto__指向 Function 的原型(Function.prototype) - Function的原型本身为一个对象,对象的
__proto__指向原型:Object.prototype。 - Object() 作为构造函数时本身也是一个函数,
相应地,构造函数 Object() 的 prototype 属 性指向原型对象Object.prototype;
实例对象 Person.prototype 的 proto 属性一样指向原型对象 Object.prototype。
原型的判断方法
获取对象原型
除了使用 .__proto__ 方式访问对象的原型,还能通过 Object.getPrototypeOf 方法来获取对象的原型 ,以及 Object.setPrototypeOf 方法来重写对象的原型。
hasOwnProperty()
我们可以使用对象的 hasOwnProperty() 来检查对象自身中是否含有该属性,hasOwnProperty() 方法存在于原型对象中的__proto__中。
Object.hasOwnProperty(propertyName) // true/false
in()
使用 in 检查对象中是否含有某个属性时,会沿着原型链查找,如果对象中没有但是原型中有,也会返回true。
instanceof
我们常用 typeof 来判断数据的类型,但是 typeof 只能用于判断基础数据类型,当判断引用数据类型的时无论什么类型的变量,都会返回 Object。
所以这里引入了 instanceof 来判断引用数据类型。
instanceof 也可以用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
通过判断参数对象的原型链上是否能找到构造函数的 prototype,来确定 instanceof 的返回值。
实现 instanceof 方法,有三种:
-
Object.getPrototypeOf(obj)——返回指定对象的原型(内部 [[Prototype]] 属性的值)
-
Object.prototype.isPrototypeOf(obj)——测试一个对象是否存在于另一个对象的原型上
-
obj.proto——使用非标准的 proto 的伪属性
-
使用 Object.getPrototypeOf(obj)
function instance_of(o, c) {
let op = Object.getPrototypeOf(o);
const cp = c.prototype;
while(true) {
if (!op) {
return false
}
if (op === cp) {
return true
}
op = Object.getPrototypeOf(op);
}
}
- Object.prototype.isPrototypeOf(obj)
function instance_of(o, c) {
return c.prototype.isPrototypeOf(o);
}
- obj.proto
function instance_of(o, c) {
let op = o.__proto__;
const cp = c.prototype;
while(true) {
if (op === null) {
return false
}
if (op === cp) {
return true
}
op = op.__proto__;
}
}
万物起源 null
一切皆为对象,却不知,JavaScript的世界中的对象,追根溯源来自于一个 null。
null 也是做为一个对象存在,基于它继承的子子孙孙,当属对象。乍一看,null 像是上帝,而 Object 和 Function 犹如JavaScript世界中的亚当与夏娃
顺着原型链查找: person1 ---> Person.prototype ---> Object.prototype ---> null
Object.prototype 的原型指向null,由 null 作为原型链的终点,
null 在某种意义上来说也是一种对象,为表示一个为“空”的对象。
person1.__proto__ === Person.prototype
person1.__proto__.constructor === Person
Person.prototype.__proto__ === Object.prototype // 原型对象的原型链 指向 原型对象
person1.__proto__.__proto__=== Object.prototype
Person.__proto__ === Function.prototype // 函数对象的原型指向函数的原型对象
Function._ proto_ === Function.prototype // Function的原型指向函数原型对象
Object._ proto_ === Function.prototype //Object的原型指向函数原型对象
Function.prototype._ proto_ === Object.prototype
//Function的原型对象的原型指向Object的原型对象
person1.__proto__ === Person.prototype //person1的原型指向构造函数Person的原型对象
Person.prototype._ proto_ === Object.prototype //Person的原型对象的原型指向Object的原型对象
Object.prototype._ proto_ === null //Object的原型对象的原型指向null【原型链顶层】
Object 、Function
Function 对象比较特殊,它的构造函数就是它自己
Function 既可以看成是一个函数,也可以是一个对象,所有函数和对象最终都是由 Function 构造函数得来,任何函数均可以看作是经过 Function() 构造函数的new实例化的结果。
// Function类本身也是Function的一个实例
Function.prototype === Function.__proto__
// 它是它自己的构造函数
Function === Function.constructor
//Function 的本质是个对象 函数的prototype属性,自己是一个由Object构造的实例对象
Function.prototype.__proto__ === Object.prototype
// 函数和对象最终都是由 Function 构造函数得来
Object.__proto__ === Function.prototype
// Object 本身是Function 的一个实例
以下四者绝对相等
- Funtion.prototype (Function的__proto__ 指向其构造函数Function的prototype)
- Function.
__proto__( Function() 本身是由Function构造而来 ) - Object.
__proto__(Object() 函数对象,本身是Function 的实例,由Function构造而来) - Person.
__proto__( Person() 构造函数本身是函数 是 Function 的一个实例)
Object.__proto__ === Function.prototype
Function.__proto__ === Function.prototype
若是把函数Person当成实例对象的话,其构造函数是Function(),其原型对象是Function.prototype;相似地,函数Object的构造函数也是Function(),其原型对象是Function.prototype。
Object作为一个构造函数(是一个函数对象),所以他的__proto__指向Function.prototype;
Function.prototype.__proto__ === Object.prototype
而 Function.prototype (原型对象)本质为一个对象,对象的__proto__ 为 Object.prototype,
所有原型对象的原型链最终都指向Object.prototype,而Object.prototype的__proto__指向null(尽头);
先有鸡还是先有蛋
Function.prototype.__proto__ === Object.prototype, 可知 Function.prototype 是一个 Object 实例,那么应当是先有 Object 再有 Function。
可是Object.__proto__ === Function.prototype。 这样看来,没有 Function,Object也不能建立实例。
这就产生了一种「先有鸡仍是先有蛋」的经典问题,究竟是先有的 Object 还是先有的 Function 呢?
自盘古开天辟地,js中并不是就有了Object,而是Object.prototype。
js中的万物(原始值除外)都是继承自Object,唯独一个对象例外,那就是Object.prototype。
Object instanceof Object; //true
Object.prototype instanceof Object; // false
全局下的 Object 构造自 Function.prototype,而 Function.prototype构造自Object.prototype。
Object.getPrototypeOf(Object) === Function.prototype
Object.getPrototypeOf(Function.prototype) === Object.prototype
所以,是先有的Object.prototype,再有的Function.prototype,再有的Function和Object。
产生步骤:
- Object.prototype先于Object出现
- 然后用这个prototype构造Function.prototype
- 有了Function.prototype再构造出Function、Object这几个构造器
- 然后把Object.prototype挂到Object上,Function.prototype挂到Function上
伪代码大致是这样,create元操作的含义是使用给定的对象作为原型构造一个新的对象。
var ObjectPrototype = create( ); // 开天辟地
var FunctionPrototype = create( ObjectPrototype );
//FunctionPrototype(后被赋值给了Function.prototype)是Object类型的
//因为其原型是ObjectPrototype
var Function = create( FunctionPrototype );
Function.prototype = FunctionPrototype;
// Function是Function类型的,也是Object类型的
//言外之意,Function对象 原型链上有Function.prototype和Object.prototype
Object = create( FunctionPrototype );
Object.prototype = ObjectPrototype;
//Object是Function类型的,也是Object类型的
//言外之意Object对象的原型链上有Function.prototype和Object.prototype
Function对象、Object对象的原型链上有Function.prototype和Object.prototype
总结
- prototype 属性是函数所独有的;
__proto__和 constructor 属性是对象所独有的。
函数js中的一等公民,也是一种对象,所以函数也拥有 **proto** 和 constructor 属性。 - 原型对象 prototype用于存放实例公有属性和方法,原型对象都是Object()的实例。constructor 指向对象的构造函数。
- 原型链又叫隐式原型链,是由__proto__属性串联起来,
原型链的尽头是 Object.prototype.proto==>null
- 构造函数是使用了new关键字的函数,用来创建对象,所有函数都是Function()的实例
所以函数(此时看成了对象)的构造函数都指向 Function。
- Object 构造自 Function.prototype,而 Function.prototype 构造自Object.prototype
参考文章
- 【一张图理解js原型】www.javashuo.com/article/p-v…
- 【javascript原型等概念】www.javashuo.com/article/p-w…
- 【js中先有Function,还是先有Object】zhuanlan.zhihu.com/p/44724367