JS原型的学法

2017-11-06  本文已影响0人  流着万条永远的河

从初学JS就有比较片面的学法,比较定死的一些概念啊,结论之类的,只是为了学习每个东东的各自的基本作用的,不需要纠结它是怎么来的,只需要知道它能干吗就可以的,小学里最小的数是0,对的。初中就是错的。为什么,不是因为绝对的错了,是因为应用环境错的,平台换了哦。说这些,意思就是看山不是山的时候到了。
JS的数据类型,数字,字符串,布尔,对象,null,undifined。然后,我们用typeof去检验,嗯有些历史遗留的问题,函数怎么是对象,为啥检测是function?null为啥是对象?需要跟它们深入地谈谈人生啦。

对象

什么?你个攻城狮,竟然还么有对象?丢人不,new一个出来,来来来,,,
对象是什么玩意儿?
我是非科班出身,连c都没学过,我只写自己的理解和被洗脑后的感悟——对象,就是我要针对的目标,这个目标在JS里啊,是数据和数据属性,数据方法这些东东臃肿地一个封装集合。别看我平时用的时候,一个标签可以表现它的存在感,它只是那个标签吗?为啥我可以设置它的属性,它的内容,它的事件,大小,它的一些操作方法function。包括数组,内置的方法,遍历等,说到函数,函数为什么声明前置,为什么会有作用域,这不是属性规则吗?包括连字符串,数字不是都有属性方法吗?
对,有句话说,JS里万物基于对象!因为我们把各个研究对象内置了属性和方法,便于操作,否则,单单只是变量,要操作的话,每次都要写个函数,声明个变量作为它的属性,再操作,,,烦不烦啊,郭德纲说过,你死不死啊,,,
对,我们学JS时,对象浅显地理解为key-value的值的合体。不需要研究太深,还是那句话,越实在暴露出来的,越是最基本的,这里的对象是面向对象,就是目标数据和这目标的规定的变量包括内置的参数我们叫属性,长宽高等等吧,以及对这数据的操作方法了,这是改变你世界观的一个意思,不要太肤浅,要知道一件事的源头,这样更有利于干什么?有利于我们学习JS的设计思想和攻城狮的本身品质。虽然我没学到前端工程化,但是我就觉得完成对对象的封装就有工程化的感觉了,而且确实会有很多福利,比如有些内置的方法只在某些对象身上,我想调用这个方法,否则我还要自己写,但是数据不是符合的,怎么办?那必须要可以啊,否则这语言也太low了,效率太低了。下面会讲到的。

思路就是,把功能集成一个整体,不去考虑内部功能怎么实现的,会调用就行了,更简洁,可控。

构造对象

字面量的模式就是不能复用代码。复用代码怎么办?

function createobj(ni,age){
  var obj = {
    ni:ni,
    age:age,
    printName:function(){
      console.log(this.ni)
    }
  }
  return obj
}
var obj1 = createobj('haha',26)
obj1.printName()   //'haha'

我的理想很丰满,如果给我一个房子的户型图,我能做出一百间这样类的房子,现实,嗯也是可以的。
构造函数:

function people(name,age){
  this.name = name
  this.age = age
  console.log(this)
}
people('haha',26)   //haha
全局变量相当于window的属性,这个函数声明是全局变量,
this就是指window。不是我想要的结果啊。
如何调用?
var obj1 = new people('li',20)
var obj2 = new people('wu',20)

都是people类的对象了。

new 的过程

new运算符接受一个函数F和其参数:new F(arguments...)。三步:
1、创建类的实例,把一个空的对象的proto属性设为F.prototype。
2、 初始化实例,F被传入参数并调用,关键字this被设为该实例。
3、返回实例。
obj1是空对象,啥都没有,它的proto指向这个函数声明时的prototype属性指向的那个对象。它再执行这个people函数,执行时,this指向obj1这个空对象。而函数执行时,给this赋值,就是给obj1增加属性,增加好了,就返回这个已经不是空的对象给obj1,所以obj1就是那个对象有三个属性。

function people(name,age){
 //var  this ={}
  this.name = name
  this.age = age
 //return this
}
如果是要阻碍一下流程:
function people(name,age){
 //var  this ={}
  this.name = name
  this.age = age
 //return 2  //改了后,没有影响,没阻碍,基本类型,浏览器默认无效
}
如果是,,,
function people(name,age){
 //var  this ={}
  this.name = name
  this.age = age
 //return {a:1}     //这就有问题了。因为都是对象,就覆盖了
}
不需要return的

instanceof

操作符,判断对象是否为某个类型的实例。

obj1 instanceof people
//true
obj2 instanceof people
//true

现在先说一句,任何的一个对象怎么来的,它的属性方法怎么来的,都是由一个函数创造出来的,对象有个属性叫constructor。
现在构造函数,可以解决我们创建复用的难题了,但是里面的那个prototype是什么鬼??


任何函数只要声明了,它就有一个prototype属性,这个属性的值对应到了一个对象,这个对象里有一个constructor,它指向这个声明的函数,还有一个proto
有什么用呢?既然是对象,我们就可以写入方法属性,改成自己需要的图纸,再new出来一堆房子。这些房子都有一个属性proto,它也指向了图纸的prototype指向的那个对象,公用的,一般函数公用最好了。
例子:
function people(name,age){
  this.name = name
  this.age = age
  console.log(this.name)
}
people('haha',26)

people.prototype.sayage = function(){
  console.log('hahaha')
  
}
people.prototype.sayage()
var obj1 = new people('li',20)
var obj2 = new people('wu',20)
obj1.__proto__.sayage()

如图:



一般,不会这样写上proto的,因为直接写成obj1.sayage就可以的,因为它会先从自身属性找,找不到就从proto找。
经验之谈:
所有的实例都可以访问那个prototype,prototype是公用的,可以把公用的方法属性放到它里面。
prototype是函数的属性,只要声明就有。
__proto__是函数创建对象的时候,对象上有它。

对于基本类型是对象的说法

基本类型也是对象,因为有自己的属性方法的,但是我们作为应用层次,要严谨,要认为不是对象才去应用,因为不能随便给它增加删除属性方法,加了有时候也起不了作用的,起不了作用,也就不是对象了,看问题是相对的。
下面原型就来了,,,

对象与原型

刷完对象的世界观,我们来想想那些开发JS的攻城狮们的套路啊,他们是设计了一个个的对象封装了,比如数组有个数组的封装组件,集成了所有数组的规则和应用基本方法,他们是开发人员,我们是应用层次啊,他们想让我们效率更高更好,比如,我们var了一个[],就能操作它,用push,shift等等方法,还可以查它的length,用下标找到对应的值,,,多方便!我没有自己去写push的函数吧,我不用关心怎么输入一个[].length就可以知道它的长度了。我会用就行了。现在我们要做什么?
为啥嘞?这里的逻辑是什么?
我们只是声明了某个类型的变量,然后这个变量就有这个类型的属性和方法了,并且根据JS的设计尿性,属性可以设置,那就可以创造,包括方法。
在操作DOM时用的太多了,事实是必须的,之前写项目时,用封装,写成对象那样,里面的创造的很多变量就是对象的属性嘛。那我们可以创造对象的方法么?告诉你没毛病!
这个中间肯定是有些规则,有些设定好的套路的,好比,规定[]就是数组。
我们先验证一下,


找到arr的constructor是f Array(),然后打开这个函数的prototype跟数组的proto比较一样吗。
所以这个arr的方法源于它祖宗的prototype指向的对象。

首先,开发人员们肯定根据数据类型集成对象,不会为了一个数组开发成两个三个多个面向数组的对象的,到时候用哪个,并且集成了,所有声明的数组都可以用,为什么非要分情况?而且都是只要是数组,你就可以拥有一堆同样的方法属性。这里我把这个源头的封装的对象叫它们祖宗。那这祖宗跟它后代如何查到互相关联?这里就要引入原型了!

原型

原型的思想就好比是模具,后面生产的产品跟它差不多,至少模具有的,产品都有,至于产品要不要深加工再出现模具没有的或者改变些神马,跟模具没关系了,关键是现在产品已经有了模具的特性了,不用我再手把手从头做起了。
有大神,在这里引入了class的概念,类概念!类就是对象的抽象描述,对象是类的实例,反正那个祖宗,我们是看不到的,只能看到同类的后代怎样怎样。正好可以引出继承这一概念了,其实一说继承,就有父子关系了,可以说是子类继承父类的。在代码层次,就是新生产出的变量的方法和属性的集合的那个栈的指针指向哪里,指针从哪里赋值得到的。


arr.valueOf()在Array.prototype里没有啊,可是没报错啊,,,
再看看Array函数的proto里看看 ,有了,,,
规定父对象也可以成为子对象的原型。每个对象都有一个原型,每个原型也是对象,也有自己的原型,从而形成原型链。

因为访问对象的属性或者方法时,在自己身上没找到,就去自己的(父亲)原型prototype上找,再找不到,再去(父亲)原型的proto上找,再找不到,再去父亲的原型(爷爷)的prototype找,再找不到就到爷爷的proto,,,顺着原型链攀爬,直到找到或者到它们祖宗也没有。
规定:

1 js是基于原型的语言中的prototype
2 每个对象都有自己的原型中的prototype
3 每个函数都有一个原型属性中的prototype

prototype是什么?上面说过了,现在再直白点,创造者有prototype,指向一个对象。
生成者的proto的指针复制了创造者的prototype的内容。
因为身份不一样,就算是同样的内容叫法也不一样,为了一个逻辑性。

第一句不管,太底层了,没兴趣。回想当初学的时候,自己的追本溯源,有种成就感,我当时问老师,所有的源头,包括对象的设定,都是因为语言环境提供运行支撑的吧??



第三句,看图,函数也有一堆内置属性的。函数名字叫什么?对吧!把函数归为面向对象研究嘛,就当成对象得了。因为函数声明你也知道,那函数是怎么造出来的?直说结果,我也不知道过程,就是对象造出来的。

这里就要有个方法了,如何找到自己的原型?
首先,我们根据逻辑其实知道了,arr是Array创造的,所以,规定了
arr.proto === Array.prototype。
所以这是判断原型的一个依据的。再说一个事实,是谁创造了Array函数呢?是Object这个函数。

那如何判断Object是不是arr的原型?思路是先看arr的proto是不是等于Object的prototype,不等于就看父辈的proto,还不等,就继续看爷爷辈的proto,,,只要有一级相等,那就是,一般找到Object.prototype.proto ===null,这是终点了,Object.prototype指向的对象是个特殊对象,里面没有proto。这就是instanceof的判断逻辑的。
神附加题:

function people(){}
var p = new people()
p.__proto__  === people.prototype     //true,不解释
people.__proto__ === Function.prototype    // true ,因为函数的父亲是Function
(people.prototype).__proto__ === Object.prototype   //true ,因为Object创造了所有基本对象,
//左边的意思就是people函数的prototype指向的那个对象的父亲的prototype。

Function.prototype === Function._proto_     //true  ,Function的prototype指向了它生成的对象的 _proto_ ,
//那这里,意思是Function是不是自己创建的,是的,如下图,,,
Function.prototype === Object._proto_  //ture  ,Object的父亲是不是Function?Object也是个函数,,,
Function.prototype._proto_ === Object.prototype  //true ,Function.prototype指向的对象的父亲的prototype。
Function instanceof Object   //true  ,
就是判断Function.__proto__ === Object.prototype,因为Function._proto_ ===Function.prototype,

而Function.prototype._proto_ === Object._proto_ 

Function._proto_ ._proto_=== Object._proto_ ,Function._proto_ 指向了对象。

经验之谈:
当new一个函数时,会创建一个对象,函数的prototype === 这个对象的proto
一切函数由Function创建,包括它自己。
一切函数的原型对象都是由Object这个函数创建的。fn.prototype.proto ===Object.prototype
创造者是作为函数看待,有prototype,生成者被当成对象看待,有proto
其实这里凭感觉都可以知道的,只不过有些别扭的是自己生产自己的,这个其实很容易理解,一个变量自己还能自增,自增了自己还是自己。

对原型对象的操作,人为指定

小练习:
上一篇下一篇

猜你喜欢

热点阅读