JavaScripts高阶(7)原型(prototype)和原型链(__proto__)

JS中的对象和函数汇总

对象数据类型的值

{} 普通对象

[] 数组

/^$/正则

Math 数学函数

实例是对象数据类型(除了基本类型字面量创建的值)

函数的prototype属性

实例的__proto__属性(指向实例所属类的prototype)

函数也是对象数据类型的

函数数据类型值

普通函数

所有的类(包含内置和自定义的类)

typeof Object    //“function”    Object是内置类
typeof String    //“function”    String是内置类

function Fn(){}
typeof Fn    //“function”    Fn是类
复制代码

原型prototype(对象数据类型)

  • 1、所有的函数(类)都`天生自带一个属性:prototype(原型)

这个属性值是对象数据类型的值,在当前prototype对象中,存储了类需要给其实例使用的公有的属性和方法(Function基类的prototype是个函数但是没有prototype)

  • 2、prototype是个对象数据,浏览器会默认为其开辟一个堆内存,在这个堆内存中天生自带一个属性:constructor(构造函数),这个属性存储的值就是当前函数本身
  • 3、每一个对象(包含每一个类的实例、function(因为function也是对象类型数据))都天生自带一个属性:__proto__,属性值是当前实例所属类的原型(prototype),如果不能确定它是谁的实例,都是Object的实例

Function.prototype是个函数但是没有prototype

函数数据类型的__proto__都指向 Function.prototype

实例的__proto__就是所属类的prototype;只要有prototype就有constructor

原型作用:实例所使用的公有属性方法都在类的原型上

所有的对象都是Object这个类的实例

对象是它原型链上所有类的实例(不只是父级的实例,也是爷爷的实例)

  • (1)Object是对象数据类型的基类在它的原型(prototype)上的__proto__属性为null,即Object.prototype.__proto__=null,如果要指向也是指向他自己本身
  • (2)但是Object.__proto__指向Function.prototype(内置类都是函数,函数的__proto__都指向内置类Function.prototype)

prototype叫原型;原型是对象数据类型的

_proto_:原型链

在使用实例的方法或者属性时如果私有属性中没有再去公有属性中找

91619597752_.pic_hd.jpg

//结合使用`构造函数模式`和`原型模式`
function Fn(name,age){
    this.name=name;
    this.age=age;
    this.say=function(){
        console.log('my name is'+this.name+'! I am'+this.age+'years old')
    };
}

Fn.prototype.say=function(){
    console.log('hello world')
}
Fn.prototype.eat=function(){
    console.log('i like food')
}
var f1=new Fn('aaa',18);
var f2=new Fn('bbb',28);

  var f=new Fn;
console.log(f.say)  //显示私有属性;如果私有属性中没有再去公有属性中找

f1.say===f2.say   //false  都是私有分属两个实例
f1.eat===f2.eat   //true   都是公有
f1.__proto__.say===f2.say   //false   f1.__proto__跳过私有直接找公有   私有
f2.eat===Fn.prototype.eat   //true    都是公有

f1.constructor    //Fn   f1是Fn的实例是一个对象数据,通过原型链先找私有,私有没有找公有(Fn.prototype)所以能找到constructor,其实找得的就是Fn.prototype.constructor
Fn.prototype.constructor    //Fn
Fn.prototype.constructor===Fn    //true


流程解析:
//变量提升: 开辟一个Fn的堆内存空间  存储代码字符串

Fn是函数,所以自带属性prototype(prototype 对象数据类型)

Fn.prototype这个对象浏览器会默认为其开辟一个堆内存
Fn.prototype天生自带一个属性:constructor就是Fn本身
每一个对象都天生自带一个属性:\__proto__,属性值是当前对象所属类的原型(prototypethis.name=name; this.age=age; this.say=function   都是给执行主体(this)设置私有属性
 
Fn.prototype.say  手动加了一个属性saysayfunction)  公共属性
Fn.prototype.eat  手动加了一个属性eateatfunction)  公共属性

var f1=new Fn('aaa',18);
    f1Fn类的一个实例(对象数据类型)this 指向实例f1
    f1.name:aaa    f1.age:19   f1.say:function  (都是私有属性)
    f1.__proto__  每一个实例天生自带一个属性:__proto__,属性值是当前对象所属类的原型(prototype):Fn.prototype

var f2=new Fn('bbb',28);
    f2Fn类的一个实例(对象数据类型)this 指向实例f2
    f2.name:bbb    f2.age:28   f2.say:function  (都是私有属性)
    f2.__proto__  每一个实例天生自带一个属性:__proto__,属性值是当前对象所属类的原型(prototype):Fn.prototype

f1是对象数据类型  也是Object的实例
复制代码

构造函数、原型、实例的关系

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针(__proto__)

原型链:是基于__proto__向上查找的机制。

当我们操作实例的某个属性或者方法的时候,先看自己私有属性或者方法有没有,

1、找到了,结束查找,使用自己私有的即可

2、没有找到,基于__proto__找所属类的prototype,如果找到就用这个共有的,如果没有找到,基于原型上的__proto__(prototype.__proto__)一直向上找到Object基类的原型为止,如果在没有,操作的属性或者方法不存在

任何数据通过__proto__都能最终找到Object.prototype

所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针(__proto__)指向Object.prototype,这就是所有自定义类都汇集成toString,valueOf的根本原因

只要是一个函数(不管是什么类,什么类的子类),永远都是内置Function这个类的实例,也就是任何一个函数的__proto__都指向Function.prototype,包括Function自己,即Function.prototype===Function.__proto__ 为true

任何值都能用对象原型上(Object.prototype)提供的方法

只要是函数就能用call、apply、bind

Function.prototype 是一个匿名函数anonymous,但是他没有prototype,它的运行机制和普通原型对象一模一样,其__proto__指向Object.prototype(Function.prototype.__proto__ === Object.prototype)

101619597787_.pic_hd.jpg

  • Object.hasOwnProperty:通过它的__proto__找到Object.prototype.hasOwnProperty,
  • 因为Object是类(函数)Object.__proto__ 找到 Function.prototype,再通过Function.prototype.__proto__找到Object.prototype上的hasOwnProperty
function Fn(){
	this.n=100;
}
Fn.prototype.getN=function(){
	console.log(this.n)
}
Fn.AA=200;
var f=new Fn();
复制代码

Object是对象数据类型的基类,它的原型上的__proto__属性为null,即Object.prototype.__proto__=null,如果要指向也是指向他自己本身;但是Object.__proto__指向Function.prototype(类都是函数,函数的__proto__都指向内置类Function.prototype)

111619597805_.pic_hd.jpg
151619602192_.pic_hd.jpg
对象的__proto__ 指向所属类的prototype;函数的__proto__指向内置类Function的prototype

var f=new Fn(); f是Fn的实例 是new Fn()返回的那一个对象,而不是function;所以它的__proto__没指向Function.prototype

内置类原型链引发的一些底层问题思考

arr.push():ary **首先通过原型链的查找机制,找到Array原型上的push方法,然后让push方法执行**向数组末尾追加

所以arr._proto_.push()也能找到push方法(因为arr.__proto_就是Array的原型)但是push中的this就变了,this变为了arr._proto;是向arr._proto_(即Array.prototype)中添加

Array.prototype.push() 也能找到push方法是向Array.prototype中添加

121619597823_.pic_hd.jpg

某一个实例或者某一个对象之所以能操作某些属性和方法,是因为在他的原型链上能找到这些属性和方法,如果找不到则不能进行操作

首先通过原型链的查找机制,找到这个方法,然后才能让这个方法执行

数组和类数组的区别:

数组的__proto__指向Array,所以能用Array的方法(push等)

类数组的__proto__指向Object,所以不能用Array的方法(push等)

数组属于Array的一个类,类数组直属于Object的一个类不能用Array的方法

私有属性和公有属性是一个相对论,我们需要看相对于哪个对象而言:

1、相对于实例来说push是共有的

2、相对于Array.prototype来说push是私有的

凡是通过__proto__找到的属性都是公有的,反之都是私有的

var arr=[];
arr.hasOwnProperty('push')     false
'push' in arr                  true
Array.prototype.hasOwnProperty('push')   true
复制代码

arguments.__proto__=Array.prototype 这样arguments就能用数组的方法了(IE浏览器屏蔽了修改__proto__)

原型链中的this

关于原型链中提供的私有或公有方法存在的this指向问题:

  • 1、看点前面是谁,this就是谁
  • 2、把需要执行方法中的this进行替换
  • 3、替换完以后,如果想要知道结果,只需要按照原型链的查找机制去查找就行了
function Fn(name,age){
    this.name=name;
    this.age=age;
    this.say=function(){
	    console.log(this)
        console.log('my name is'+this.name+'! I am'+this.age+'years old')
    };
}

Fn.prototype.say=function(){
	console.log(this)
    console.log('hello world')
}
Fn.prototype.eat=function(){
	console.log(this)
    console.log('i like food')
}
var f1=new Fn('aaa',18);
var f2=new Fn('bbb',28);

f1.say()                //this:f1   找的是私有的属性和方法
f1.__proto__.say()      //this:f1.__proto__找的是共有的属性和方法(Fn.prototype的) 'hello world' 

Fn.prototype.say()      //this:Fn.prototype 找的是共有的属性和方法  'hello world' 
复制代码

在原型上批量扩展属性和方法

1、设置别名法

2、重新构造原型,让原型指向自己开辟的堆内存,为了保证constructor指向原来的构造函数,自定义对象必须加上constructor属性

存在一个问题:自己开辟的堆内存中没有constructor这个属性,所以实例在调取constructor的时候找到的是Object,这样不好的,此时我们应该重新设置一下constructor,保证机制的完整性;

重新做原型指向后,之前在浏览器默认开辟的堆内存中存储的属性和方法都没用了,只有在新内存中存储的才是有用的,所以在原来原型链有东西时,慎重使用;并且内置类不允许这么做(重构原型)

function Fn(name,age){
    this.name=name;
    this.age=age;
}

//加1个方法
Fn.prototype.aa=function(){

}

//批量增加

//1、设置别名法
var pro=Fn.prototype; //指向同一个堆内存
pro.aa=function(){}

//2、重新构造原型
Fn.prototype={
    //重新设置一下constructor,保证机制的完整性
     constructor:Fn,
     aa:function(){},
     bb:function(){}
}
var f=new Fn('aaa',18);
复制代码
//jQuery源码
~function(){
    var jQuery =function(selector,context){
        return new jQuery.fn.init(selector,context);
    }
    jQuery.fn=jQuery.prototype={
        constructor:jQuery,
        init:function(selector,context){

        }
    };
    window.$=window.jQuery=jQuery;
}()
复制代码

基于内置类原型扩展方法

1、我们新增加的方法最好设置一个前缀:防止我们新增加的方法和内置的方法冲突,把内置方法替换掉了

2、在扩展的方法中this就是当前要处理的数据 所以不能传参(arr.myDistinct())

arr.push()执行完以后返回的是push完以后数组的长度,是个数字,不能再使用数组的方法

扩展原型方法间的区别

1、Fn.prototype.xxx是向 现有公有方法中添加,实例.__proto__能找到Fn.prototype;实例.constructor就是Fn

2、Fn.prototype={}是 改变公有方法指向,如果不用constructor=Fn实例.__proto__不能找到Fn.prototype;实例.constructor就是Object

//1
function Fn(name,age){
    this.name=name;
    this.age=age;
}

Fn.prototype.say=function(){
    console.log(this)
    console.log('hello world')
}

var f1=new Fn('aaa',18);
console.log(f1.constructor)  //Fn

//2
function Fn(name,age){
    this.name=name;
    this.age=age;
}

Fn.prototype={
    say:function(){
        console.log(this)
        console.log('hello world')
    }
}

var f1=new Fn('aaa',18);
console.log(f1.constructor)		//Object内置类

//3
function Fn(name,age){
    this.name=name;
    this.age=age;
}

Fn.prototype={
    constructor:Fn,
    say:function(){
        console.log(this)
        console.log('hello world')
    }
}

var f1=new Fn('aaa',18);
console.log(f1.constructor)				//Fn

复制代码

链式写法原理:

执行完成一个方法后,返回的结果依然是当前类的一个实例,这样就可以继续调用当前类的其他方法操作(所以原型扩展方法要写上return this)

Number(undefined) = NaN 假

//数组去重基本写法
function distinct(arr){
    var obj={}
    for(var i=0;i<arr.length;i++){
        var item=arr[i];
        if(typeof obj[item] !=== 'undefined'){
            arr[i] == arr[arr.length-1];
            arr.length--;
            i--;
        }
        obj[item] = item;
    }
    obj = null;
    return arr;
}
var arr=[1,2,1,3,4,5,6,4,5,64,2,5,1,2,1]
console.log(distinct(arr))
复制代码
//在原型上扩展去重方法
Array.prototype.myDistinct=function myDistinct(){
    //this就是arr(当前要处理的那个数组) 所以不能传参
    var obj={}
    for(var i=0;i<this.length;i++){
        var item=this[i];
        if(typeof obj[item] !=== 'undefined'){
            this[i] == this[this.length-1];
            this.length--;
            i--;
        }
        obj[item] = item;
    }
    obj = null;
    return this;        //=>实现链式写法,返回去重后的数组,这样执行完成这个方法后,我们还可以继续调用数组中的其他方法(不考虑链式调用时可以不写)
}
var arr=[1,2,1,3,4,5,6,4,5,64,2,5,1,2,1]
arr.myDistinct()
复制代码
//链式写法:执行完成一个方法紧跟着就调取下一个方法
//链式写法原理:执行完成一个方法后,返回的结果依然是当前类的一个实例,这样就可以继续调用当前类的其他方法操作
arr.sort(function(a,b){
    retrun a -b;
})
arr.push(100)
        ||
        ||
        \/
arr.sort(function(a,b){
    retrun a -b;
}).push(100)

//arr.push()执行完以后返回的是push完以后数组的长度,是个数字,不能再使用数组的方法
复制代码

示例

//=> 实现(3).plus(2).minus(1) =4

//简单
Number.prototype.plus=function plus(){
    return this+arguments[0];
}
Number.prototype.minus=function minus(){
    return this-arguments[0];
}

//严谨  考虑到不传参的情况下
Number.prototype.plus=function plus(){
    var val=Number(arguments[0]) || 0;
    return this+val;
}
Number.prototype.minus=function minus(){
    var val=Number(arguments[0]) || 0;
    return this-val;
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享