JS中对象的原型与函数的原型

引言

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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值