引言
JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]](原型),这个特殊的对象它要么为 null
,要么就是对另一个对象的引用。该对象被称为“原型”。
对象的原型
每个对象都有[[prototype]],这个属性可以称之为对象的原型!!!(隐式原型) (看不到,不改,不用)
大家在浏览器里指向,可以看到这个[[prototype]]属性,颜色是灰色的。
var obj = {
name:'why'
}; //隐藏了[[prototype]]
var info = {
}; //[[prototype]]
查看对象的原型
那要如何查看这个原型呢?早期的ECMA是没有规范如何去查看[prototype]]。
有两种方式。
方式一 浏览器提供的方法
浏览器给我们的对象中提供了一个属性,可以让我们查看一下这个原型对象。不过一般在测试阶段才会这么用。
console.log(obj.__proto__); //{}
方式二 ES5提供的方法
console.log(Object.getPrototypeOf(obj));
对象的原型有什么作用?
当我们从一个对象中获取一个属性的时候,它会触发[[get]]操作。1.在当前对象中去查找对应的属性,如果找到就直接用。2.如果没有找到,那么它就会沿着它的原型(链)[[prototype]]查找。
// obj.__proto__.age = 18 往原型里加,这是为了实现继承
// obj.age = 18
console.log(obj.age);
函数的原型
在我的上一篇文章中说过了,当一个函数被new操作符操作了会经历如下的过程。
-
在内存中创建一个新的对象(空对象)
-
这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性
-
构造函数内部的this,会指向创建出来的新对象
-
执行函数的内部代码(函数体代码)
-
如果构造函数没有返回非空对象,则返回创建出来的新对象
没看过的朋友可以访问这个链接JS批量创建对象的方案与构造函数
当一个构造函数通过new来创建以后,也就是会经历如下的过程
function foo(){
var moni = {}
this = {}
this.__proto__ = foo.prototype
return this
}
......
var f1 = new foo() //new关键字会创建一个空对象,并且给该对象赋值为该构造函数的prototype属性
要注意的是,函数也是个对象,它是个特殊的对象。所以函数作为对象来说,它也是有[[prototype]]隐式原型。
console.log(foo.__proto__); //这样是可行的
但函数它因为是一个函数,所以它才会多出来一个显示原型:prototype
console.log(foo.prototype); //这是ECMA定义的
所以存在这么一个结论
f1.__proto__ === foo.prototype
某构造函数在内存中的结构
function Person(){
}
var p1 = new Person()
var p2 = new Person()
// console.log(p1.__proto__ === Person.prototype); //true
// p1.name = 'why' //改了Peroson.prototype会改
// p1.__proto__.name = 'why' //它们都是指向Person.prototype的
// Person.prototype.name = 'why' //三个指向的都是这,设定的机制就是这样
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KSCleXf0-1655361819032)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/be8c0fa7c7574e54abe80ac4168184c1~tplv-k3u1fbpfcp-watermark.image?)]
函数原型上的属性
function foo(){
}
console.log(foo.prototype); //有一个constructor的属性
//研究一下foo.prototype,直接打印是空的,但是它不是空的。这里是因为可枚举的属性设置为false
console.log(Object.getOwnPropertyDescriptors(foo.prototype));//通过这可以查到
代码运行截图
如果不明白这边的enumerable等概念的可以去看一下我之前写的文章JS中的对象属性修饰符
对象属性描述符的一些补充
这里我就不过多赘述了。
constructor这个属性不是我们手动增加的而是创建对象时,JS引擎添加的。它指向构造函数本身。
constructor保存的是一个地址,这个地址指向构造函数本身。
// console.log(foo.prototype.constructor); //指向构造函数本身
//构造函数foo中有一个name属性
//foo.prototype.constructor.name === foo.name
// console.log(foo.prototype.constructor.prototype.constructor); 可以这么写的,无限套娃嘛,都互相指着的
依仗prototype原型,我们也可以增加自己的属性
foo.prototype.name = 'why'
foo.prototype.age = 18
foo.prototype.height = 18
foo.prototype.eating = function(){
}
var f1 = new foo()
console.log(f1.name,f1.age);
console.log(f2.name,f2.age);
当然我们也可以直接修改整个的prototype对象。不过要记住__proto__也好,prototype也好,都只记录着访问函数原型对象(Person)的地址,数据其实是存储在Person原型对象中,我们通过地址找到原型对象。通过.从对应的地址访问到相对于的对象。
但是你要是直接赋值,那就是重新一个对象,里面是没有默认的constructor属性的,你要自己加。
//
function foo(....){
//....省略
}
foo.prototype = {
// constructor:foo, //添加constructor **
//constructor默认可枚举是true **不合理
name:'why',
age:18,
height:1.88
}; //指针指向新的对象
var f1 = new foo()
console.log(f1.name,f1.age,f1.height);
//真是开发中我们可以通过Object.defineProperty来添加
Object.defineProperty(foo.prototype,"constructor",{
enumerable:false,
configurable:true,
writable:true,
value:foo
}); //替换掉**的写法
function Person(name,age){
this.name = name
this.age = age
}
Person.prototype = {
run(){
console.log(this.name+'这个人在跑');
},
eat(){
console.log(this.name+'这个人在吃');
}
}
let p1 = new Person('张三',16)
p1.run() //输出结果
Object.defineProperty(Person.prototype,"constructor",{
enumerable:false,
writable:true,
configurable:true,
value:Person //函数中加()就是整个函数,不加就是表示是函数的地址
})
console.log(Object.getOwnPropertyDescriptors(Person.prototype)); //Person构造函数的属性
将对象的方法放到原型中,这样每次创建新对象并且调用方法时,就不用再到内存中开辟新的空间了。这样就解决了构造函数创建对象的缺点。
function Person(name,age,height,address){
// var moni = {} //这里没东西了
// Person.prototype.name = name 这样子是不可以的; 因为var moni没东西了 p1就直接没了
// 普通属性不能放在原型上面
this.name = name
this.age = age
this.height = height
this.address = address
}
// 函数放到原型中
Person.prototype.eating = function(){
console.log(this.name+'在吃东西'); //this的指向是动态绑定的,p1.eating()调用,这个this还是p1
}
Person.prototype.running = function(){
console.log(this.name + '在跑步');
}
var p1 = new Person('why',18,1.88,'北京市')
var p1 = new Person('kobe',20,1.98,'洛杉矶市')
p1.eating()
来自本人掘金文章https://juejin.cn/post/7109048118053715982/