阴影中的曙光

再谈JavaScript与OOP

2018.08.23 / JavaScript / 点击 438 / 回复 0 / JavaScript, OOP

感觉自己始终还是个面向过程的程序员,写啥都有一种写C的感觉(´・_・`)

JS中的类型

做为一种弱类型语言,JavaScript某种意义上只有两种类型,原始类型和引用类型,原始类型保存简单的数据(类比C里的一个内存段)引用类型存储一个内存位置的引用(类比C里的指针)原始变量作为储存单元储存相应的字面量,复制时可理解为申请新的储存单元储存同样的字面量(二进制编码相同内存地址不同);引用类型作为内存地址的指针,负责索引一系列的属性以及方法,复制时其实为浅拷贝,本质上只是复制了一个内存地址的索引值,两个变量指向的还是同一块内存区域(JS的垃圾回收机制会复制对象的内存回收,当一个对象没有引用类型对其索引的时候就会被销毁,所以对于不在需要的对象及时为清理其索引(object = null)是很重要的。

原始封装类型

原始类型数据可以作为引用类型(对象)使用,背后的运作原理为临时创建一个相应类型的对象(原始类型对象本身作为参数传入)然后在进行完操作后马上销毁。

函数

  • 函数及对象,作为可执行对象,函数的最大特点是具有一个内部属性[[call]]
  • 函数的声明有两种方式,函数表达式以及函数声明,值得注意的是函数声明会提升至上下文,而函数表达式不会。
  • 函数可以接受任意个数的参数,并且都储存在arguments中,但是function.length返回出的是函数的期望参数,及函数定义时的形参个数。
  • JS中的函数重载实现,基本上靠传入的参数个数来进行区分执行不同的逻辑,switch(arguments.length) ,typeof (arguments[0])嗯,感觉这种花里胡哨的东西,没啥卵用(´・_・`)
  • JS内所有函数的作用域内都有一个this对象代表调用该函数的对象(经典面试题,箭头函数和匿名函数的区别,匿名函数this在当前上下文,箭头函数在上级上下文)。

改变this的三种方法:
call()函数的第一次参数表示函数执行时this的指向对象,之后的所有参数作为调用call()的函数的参数。
apply()函数跟call()函数很类似,只不过它只接受两个参数,第一个参数与call相同,第二个参数为数组,同样存贮调用apply()函数的参数。
bind()函数是es5加入的新特性,第一个参数同样为this的指向对象,但是之后的所有值代表要永久设置在新函数中的命名参数,简而言之就是把传入的参数,和this的指向全都永久绑定在调用bind的函数上了。

对象

  • 当一个属性第一次被添加给对象时,JavaScript会在对象上调用一个名为[[put]]的内部方法,其会在对象上创建一个新节点储存属性(这个属性为自有属性),而已有属性进行赋新值的时候会调用[[set]]方法。
  • 科学切优雅的属性检测手段应当是通过in操作符进行检测,如果你想检测的是自有属性则可用hasOwnProperty
  • 对象中可枚举的属性都含有内部特征[[Enumerable]].。Object.key()函数es5绝赞放送中ヾ(。╹ω╹。)ノ一键获得属性列表,整个对象看光光,当然只能看到自有属性,谁要看父级。。。
  • 属性有两种类型,常规的数据属性和访问器属性,访问器属性不包含值而是定义了当属性读取时调用的函数(getter)和属性被设置时的(setter)
  1. set 函数一般用于产生副作用操作时的属性更改使用,值得注意的是如果你只对一个属性设置get则其为只读,只为期设置set则其为只写,然而不设置任何一个则可读可写(゚A゚三゚A゚)
  • Object.definProperty()又一个没卵用的方法,3个参数,对象,要更改的属性名,要更改成的特征属性

[[Enumerable]]可枚举
[[Configurable]]可配置(就是可删除啦)
[[Value]]数据的值
[[Writable]]是否可写
以上为数据属性的特性值所有属性默认值都为false,所以设置了就都设置下,大概这也是为啥上边的get,set函数为啥是那种表现方式的原因吧
[[Get]]访问器属性的getter函数
[[Set]]访问器属性的setter函数
借助这两个特性值就可以在对象初始化后动态的再去设置某个属性的访问器函数

构造函数

  • 不成文的规范是首字母大写的函数都为构造函数,通过
    左值 = new 后遭函数() 的方式进行实例化对象
  • 构造函数在函数中以 this.变量名 和 this.变量名 = function () {} 的方式绑定(如果实例化了,所实例化出的对象的)自有属性和方法 ,构造函数本身不需要返回值,new 操作符会帮你返回,对象可以通过instanceof来检测其是否为 某一个构造函数实例化所实例化出来的对象。当然你也可以显式的使用return如果其返回值是一个对象则会代替new的默认返回而返回,如果是一个原始类型,则会被忽略掉。
  • 原型对象的共享机制使得它们成为一次性为所有对象定义方法的理想手段,一般多用对象字面量的形式去配置构造函数的公共属性或方法,但是要注意的是在使用对象字面量形式构建的时候要手动重置其constructor属性。
  • 构造函数, 对象实例(由构造函数new出来的对象)和原型对象(挂载在构造函数.prototype上的公有属性和方法)三者关系如下

对象实例 ,构造函数的原型链上都挂载着原型对象
原型对象的constructor属性为构造函数
构造函数和对象实例没啥关系

继承

每一个构造函数都有Prototype,而实例化出的对象没有

Object.prototype上的那些公有方法
hasOwnProperty() 检查是否存在一个给定名字的自有属性
isPropertypeOf() 检查一个对象是否是另一个对象的原型对象
propertypeIsEnumerable() 检测一个自有属性是否可枚举
valueOf() 返回一个对象的值表达
toString() 返回一个对象的字符串表达

  • 对象的继承可以通过显式的Object.create()方法调用,其接受两个参数,第一个参数为要设置在propertype上的对象。第二个参数为属性描述对象(此对象中的属性都为继承后对象自己的属性)

完全不负责而且可能全是错误且不知所云的个人总结

深入理解原型链的运作机制就必须搞清楚 构造函数 对象实例以及原型对象三这的关系

function Person(name){
    this.name = name;
    this.sayName = function(){ alert(this.name)}
}
var TOKdawn = new Person('TOKdawn');

以上为一个很经典的构造函数使用,但是它背后是如何运作的呢?
构造函数本身(Person)首先它是个函数,并且他是个函数对象,而在原型链中呢?我个人把他理解为一个带有自己内存的指针。其一构造函数本身内以var或let方式声明的变量与方法会存在在自己的内存中,其二构造函数在使用时默认带有一个取地址符,虽然在存储上Person与Person.prototype物流隔离(不在同一个内存块中)但是在使用上Person.prototype又包含于Person,JS的机制就是寻址的时候既访问当前对象又访问原型链上的对象,所以逻辑上看起来又是一体的。
原型对象(Person.prototype)首先他也是个对象,在底层他是一块储存这name变量以及sayName函数数据的内存块,他服务于构造函数,构造函数通过. prototype这个指针索引着这段内存,想要访问他也必须通过.portotype进行索引,同时这段内存也通过.constructor反向索引着其服务的构造函数。

对象实例(TOKdawn)每次它也是个对象,毕竟万物皆对象嘛,他也是一个有自己内存的指针,指针的指向在其被实例化的时候指向了其构造函数的.prototype。

其实在JS里万物皆对象,而对象也都是一个带存贮 的指针而已(指针即prototype)使用使依照自己的指针吧自己当前的内存和指针引的内存都拼接在一起成为自己的作用域,那么prototype寻址的尽头是什么呢?就是JS原始提供的各种数据类型的.prototype。

以上全为个人理解,意在传神,切莫纠结,上面的指针,内存等名词都只是取其概念帮助表达。。。

最后是一组题,都理解了的话,基本JS的原型链也就算毕业了。引用自

最详尽的 JS 原型与原型链终极详解,没有「可能是」。(二)

TOKdawn.__proto__ 是什么?
Person.__proto__ 是什么?
Person.prototype.__proto__ 是什么?
Object.__proto__ 是什么?
Object.prototype__proto__ 是什么?
答案:
第一题:
因为 TOKdawn.__proto__ === TOKdawn 的构造函数.prototype
因为 TOKdawn的构造函数 === Person
所以 TOKdawn.__proto__ === Person.prototype

第二题:
因为 Person.__proto__ === Person的构造函数.prototype
因为 Person的构造函数 === Function
所以 Person.__proto__ === Function.prototype

第三题:
Person.prototype 是一个普通对象,我们无需关注它有哪些属性,只要记住它是一个普通对象。
因为一个普通对象的构造函数 === Object
所以 Person.prototype.__proto__ === Object.prototype

第四题,参照第二题,因为 Person 和 Object 一样都是构造函数

第五题:
Object.prototype 对象也有proto属性,但它比较特殊,为 null 。因为 null 处于原型链的顶端,这个只能记住。
Object.prototype.__proto__ === null