原型和原型链浅析

一.前言

javascript与其它面向对象语言不同,在ES6没有引入class概念之前,javascript并不是通过类而是直接创建构造函数来创建实例.在javascript中,类不能描述对象可以干什么事,对象可以直接定义它自己的行为.因为根本不存在类! javascript一切皆对象,把js中的对象分为普通对象函数对象.函数在js中是一等公民,所以把函数对象给区分出来.

javascript是一种直译式脚本语言,是一种动态类型.弱类型,基于原型的语言

二.属性:Prototype(原型)

每个函数对象(Function.prototype除外)都有一个prototype属性(这个属性指向一个对象,也就是原型对象)

prototype原型是函数的一个默认属性,在函数的创建过程中由JS编译器自动添加

//函数表达式
var fn1=function(){}
//函数声明
function fn2(){}
//实例化函数对象
var fn3=new Function();
console.log(fn1.prototype) //Object{} {constructor: ƒ}
console.log(fn2.prototype) //Object{}
console.log(fn3.prototype) //Object{}

这里打印的Object对象 {constructor: ƒ} 实际上就是我们说的原型,它是一个对象,也叫原型对象

我们再来解释下Function.prototype,为什么说它要除外呢?我们来打印下:

console.log(Boolean.prototype);
console.log(String.prototype);
console.log(Number.prototype);
console.log(Function.prototype);
console.log(Function.prototype.prototype);

在这里插入图片描述

可以看到内置构造函数Boolean,String,Number等,它们的原型指向一个普通对象Boolean,String,Number,而Function的原型则指向函数对象 ƒ () { [native code] },就是原生代码,二进制编译的.所以说,这个函数对象(Function.prototype)是没有原型属性的,所以它的prototype返回undefined

1.函数的原型的作用

共享属性方法和继承

先来举个例子,我们不使用原型的情况下,在构造函数中,创建了一个方法sayHello,但是当我们实例化这个构造函数时,P1和P2这两个实例的方法并不是一样,原因是:当实例化一个构造函数时,相当于在内存中重新开辟了一道空间,又重新生成了一个实例的方法.所以二者的方法并不是一样的! 这里如果实例化无数个,当然也会生成无数个不一样的sayHello方法,如果有的实例使用,有的不使用,难免会造成资源浪费.

function Person(name) {
    this.name = name;
    this.sayHello = function () {
        console.log('hello');
    }
}
var P1=new Person('xiaoming');
var P2=new Person('xiaohong');
console.log(P1.sayHello===p2.sayHello);//false

所以呢,我们为了解决这种资源浪费的问题,我们只需要添加一个方法就行了,在当前函数的原型上添加一个方法,供所有实例化后的对象去使用,当然了,我们也可以在prototype上添加属性.这就是共享属性方法,

Person.prototype.type='性别';
Person.prototype.eating = function () {
    console.log("吃饭");
}
console.log(p1.eating===p2.eating);//true
console.log(p1.type)
console.log(p2.type)

示例中通过给原型对象(Person.prototype)添加属性方法那么由 Person 实例出来的普通对象(p1 p2)就继承了这个属性方法(type eating)

2.constructor(构造器)

每个对象都有一个隐藏属性constructor,该属性指向对象的构造函数(类)

function Person(name) {
    this.name = name;
    this.sayHello = function () {
        console.log('hello');
    }
}

var P1=new Person('xiaoming');
var P2=new Person('xiaohong');//实例对象
var p3=Person.prototype;//原型对象
console.log(p3.constructor===p4.constructor);//true 指向同一个构造器,也就是Person类

console.log(Person);//指向当前Person的构造器
console.log(Person.prototype);//指向当前Person的原型对象
console.log(Person===Person.prototype.constructor);//true

实际上,像上面添加给原型添加属性和方法,我们可以换一种方式的写法

function Person(name) {
    this.name = name;
    this.sayHello = function () {
        console.log('hello');
    }
}

Person.prototype = {//原型对象
    type:'性别',
    eating : function () {
        console.log("吃饭");
    }
}

var p3 = new Person('xiaofang');//实例对象
var p4 = Person.prototype;//原型对象
console.log(p3.constructor);//ƒ Object() { [native code] }
console.log(p4.constructor);//ƒ Object() { [native code] }

这种写法很直观的看到原型对象是什么,但是我们可以看到,Person.prototype指向一个对象字面量方式定义的对象{},其构造器(constructor)指向了一个Object的根构造器,所以p3的构造器也指向了根构造器Object,

由此,可以得出一个结论:用constructor来做类型识别,并不可靠!

3.原型的继承
Object.prototype.type = "123";
Object.prototype.sayHello = function () {
    console.log('sayHello')
}
String.prototype.sayHi=function(){
    console.log('sayHi')
}
var num=123;
var str='abc';
num.sayHello();//sayHello
str.sayHello();//sayHello
str.sayHi();//sayHi
num.sayHi();//Uncaught TypeError: num.sayHi is not a function

从上述代码可以看出来,所有的对象都继承了Object.prototype原型上的属性方法(换句话说它们都是Object的实例) ,str还继承了String.prototype原型上的属性方法.

Object是所有对象的基类

三.属性:__proto__

这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。

与prototype不同的是,这里每个对象(除null外)都有,而prototype是只有函数(除Function)才有

1.proto概念

每个对象都有一个隐藏属性 _ _proto_ _用于指向创建它的构造函数的原型 p1.__proto__==Person.prototype

注意:属性__proto__非官方标准属性,也就是非ECMA标准,但是主流的浏览器基本都支持,目前,__proto__属性已在ES6语言规范中标准化

var str='';
console.log(str.__proto__);//指向str的构造函数的原型,String.prototype
console.log(str.__proto__===String.prototype);//true

上述代码中,str.__proto__指向的是str的构造函数的原型,也就是String.prototype,因为str是属于String的一个实例,也就是说str.__proto__===String.prototype)他们俩是相等的!

前面我们说了,js中一切皆对象,所以,除了null之外,都有这个属性__proto__上面是我们普通函数对象的情况,那现在再来看看函数对象的情况

function Person() {}
var p1=new Person();
console.log(p1.__proto__);//指向构造函数的原型
console.log(Person.prototype);//指向当前构造函数的原型
console.log(p1.__proto__===Person.prototype);//true

不出意外,函数对象同样也有这种规则,只要记住一句话:__proto__都会指向创建它的构造函数的原型

2.hasOwnProperty()

用来怕判断某个属性是否为该对象本身的一个成员

function Person(name) {
    this.name = name;
    this.sayHello = function () {
        console.log('hello');
    }
}
Person.prototype.type = "123";
Person.prototype.eating = function () {
    console.log("吃饭");
}
var p1 = new Person();
console.log(p1.hasOwnProperty('name'));//true 说明name属于p1自身的属性
console.log(p1.hasOwnProperty('sayHello'));//true 说明sayHello属于p1自身的属性
console.log(p1.hasOwnProperty('type'));//false 说明type不属于p1自身的属性
console.log(p1.hasOwnProperty('eating'));//false   说明eating不属于p1自身的属性

hasOwnProperty()这个方法就是用来判断某个属性是否为该对象本身的一个成员的.我们看代码可以看到, Person对象中自身的成员只有name,和sayhello这个两个属性,所以hasOwnProperty判断时会返回true.而type和eating这两个属性,是属于Person原型上的属性和方法.所以hasOwnProperty判断时会返回false.上面还有一种情况:

p1.abc='123';
console.log(p1.hasOwnProperty('abc'))//true

我们使用这种动态的添加属性的方式,也是可以的,动态方式添加的属性,也是当前对象实例自身的属性.所以得出一个结论,属于自身属性的,无非就两种情况:

  • 1.一种是构造函数里的属性和方法
  • 2.一种是动态添加的属性和方法

其他的,能访问到的,都不是自身的!

3.浏览器查找某对象的属性的机制
function Person(name) {
    this.name = name;
    this.sayHello = function () {
        console.log('hello');
    }
}
Person.prototype.type = "123";
Person.prototype.eating = function () {
    console.log("吃饭");
}

var p1 = new Person();
Object.prototype.type = "123";
Object.prototype.sayHi = function () {
    console.log('sayHi')
}
p1.sayHi();//sayHi

我们来看这段代码,为什么我的Person构造函数的实例,可以访问到Object原型上的方法?这关系到咱们浏览器查找某对象的属性或方法时的一种机制.也可以说是一种过程

p1.sayHi();//sayHi
console.log(p1.hasOwnProperty('sayHi')); //false 先访问自身
console.log(p1.__proto__.hasOwnProperty('sayHi')); //false
console.log(p1.__proto__.__proto__.hasOwnProperty('sayHi')); //true

在这里插入图片描述

详细讲解下这个寻找浏览器寻找p1.sayHi() 的过程:

  • 自身查找p1.hasOwnProperty(‘sayHi’))
  • 没有sayHi方法
  • 查找上层的__proto__此时指向Person.prototype,继续使用hasOwnProperty寻找
  • 没有sayHi方法
  • 继续查找上层的Person.prototype.__proto__,此时指向Object.prototype,继续使用hasOwnProperty寻找
  • 找到了sayHi方法,然后执行
  • 如果在Object.prototype没有找到的话,会继续查找Object的上层
  • Object是顶层,Object.prototype.__proto__===null

向上面这种,就是原型链:原型链是靠__proto__维护的

p1.sayHi();//sayHi
console.log(p1.__proto__===Person.prototype);//true
console.log(p1.__proto__.__proto__===Object.prototype);//true
console.log(p1.__proto__.__proto__.__proto__);//null

在这里插入图片描述

原型链的最后是null

原型链,就是在当前对象中如果自身没有该属性,则向上一层原型对象中寻找,一直到最外层null

四.面试中的原型和原型链

__proto__和prototype :

首先呢,在js中一切皆对象,然后这个对象分为普通对象和函数对象,因为函数对象是一等公民嘛,然后呢,只有函数对象才会有的这个protottype属性,但是除了构造函数Function之外,因为Function的prototype指向的是原生代码,其他的函数对象的prototype属性都会指向原型对象,函数的原型的作用就是为了共享属性方法和继承,一般我们会在某构造函数的prototype上添加公共的方法或属性.

再就是__proto__,这个是所有对象都有的一个隐藏属性,但除了null之外,而这个隐藏属性用于指向创建它的构造函数的原型,但是呢这个__proto__是非ECMA标准的,但是主流浏览器都支持,ES 6已经加入标准化了,

对比下__proto__和prototype,对象通过__proto__指向原型对象,函数对象通过prototype指向原型对象

然后在说说原型链,原型链的话,设计到一个浏览器访问某对象属性的机制,也可以说是一种过程:

首先,某实例访问属性或方法时,会先从自身寻找有没有想要的属性或方法,如果没有,就会去找上层的原型有没有,也就是当前实例的__proto__,此时这个当前实例的__proto__会指向你当前实例的构造函数的原型,也就是prototype,如果没有,那就继续寻找上层的.一直找到Object.prototype.__proto__为止,然后这就形成了一个原型链,原型链是靠__proto__维护的

最后总结一下:原型链,就是在当前对象中如果自身没有该属性,则向上一层原型对象中寻找,一直到最外层null

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值