js小黄书笔记(二)this与对象原型

120 阅读6分钟

this

this 提供了一种优雅的方式来隐式的传递一个对象引用。可以解决当代码模式越来越复杂使用显示的对象传递会使得代码越来越混乱的问题。

常见错误

  • this 并不是指向自身
    • 在一个函数内部调用 this 并不是指向他自身,而是??
    • 你可以在函数内部调用本函数的函数名来取到函数本身|词法作用域,匿名函数做不到取到本身
    • 使用 call 来传入函数本身来让 this 指向函数本身而不是调用它的对象
  • this 并不是指向它的作用域
    • 它错误但不完全错误|作用域是引擎内部的定义,并非代码可以取到的一个对象
  • this 是运行时绑定的,并不是定义时绑定,它的上下文取决于函数调用时的各种条件。
    • 当一个函数被调用,会创建一个执行上下文,它包含了函数在哪里被调用(调用栈),函数的调用方法和传入的参数信息。this 就是上下文中的一个属性。

解析this

调用位置

  • 解析调用栈,调用时的所在位置即为调用位置(当前调用栈的第二个即为第一个函数的调用位置)

绑定规则

四种绑定方式/

严格模式不允许 this 默认绑定到全局对象。

  • 默认绑定,独立调用函数,默认绑定在全局对象
  • 隐式绑定:调用时被一个对象拥有或者包含,由这个对象在内部调用了函数,那么此时 this 绑定到这个上下文对象,且只有最近一层会影响到调用位置。
    • 大白话理解:把函数作为一个对象的属性,然后调用这个属性
    • 隐式丢失:let a = obj.fn; 此时 a 调用了 fn,且失去了 obj 的外部包裹,也就是此时的引用丢失了,同样在一个函数作为参数被传递,在调用参数时也会发生隐式丢失。
  • 显式绑定:call,applay,bind,详见这仨东西的手撕实现
  • new绑定 绑定详见也是上面那个手撕实现 new 的文章 Tips/
  • 关于 this 绑定的优先级
    • bind 从原理上看永远无法更改,闭包保存了 this 指向。
    • 其次是 new 或 call、apply,手动更改了 this 指向
    • 其次是上下文对象、默认绑定。
  • 例外:如果吧 null & undifned 传入call、apply 和 bind,这些值会被忽略,使用默认绑定规则
    • 这种情况一般用于扩展运算符出现之前的数组展开
  • 软绑定
    • 效果目标:在硬绑定的效果上还能做到二次更改this
    • 在实现 bind 时,先判断函数调用的当前 this 是否是 global 或者 window,如果是的话则正常执行硬绑定,如果 this 已经又了上下文绑定或其他绑定,那么不再更改 this 指向。
  • 箭头函数
    • 箭头函数不适用上述的四种绑定,它的 this 来自运行时的外层作用域中的 this 继承而来。
  • 在实际使用中,下面的风格二选其一并统一所有代码
    • 只使用词法作用域
    • 完全采用 this 风格,使用 bind 来避免出现箭头函数或 let self = this;

对象

  • 两种定义方式,没有本质区别。
    • let obj = {};
    • let obj = new Object();
  • 六种基本类型中,null 的 typeof 为 object 。
    • unll 所有位码为 0,其中前三位的二进制码,也就是类型标志码的对象标志码也是 0,属于语言bug。
  • 对象属性名可以使用点或[]
    • 如果出现不满足命名规范的属性名要调用,则必须使用可计算属性名['']的方式
    • 对语动态计算的属性名也是使用后者
  • 在js中,函数永远不是一个对象的属性,也因此不能成为方法,对象只是对函数的调用或者是在对象的语法中被定义而已。

属性描述符

  • 使用 defineProperty( obj,name )读取一个属性的属性描述符
  • 使用 Object.defineProperty( obj,name,{特征对象} )来设定 obj 对象的 name 属性的特征。
  • 特征对象包含你要设置的特征配置值
    • value:属性值
    • writable:是否可写,设置为 false 则这个值无法被二次更改(相当于定义了一个空的 setter 函数)
    • configurable:是否可以配置,如果把这个值设为 false,则这个属性的特征配置无法被二次更改并无法被删除。
      • 但是writable可以从true改为false,一个例外。
    • enumerable:是否可枚举,比如 for..in循环。
      • obj.propertyIsEnumerable( name )可以检查一个属性是否可枚举
      • forEach 循环每个值,every 直到返回 false,some 直到返回一个 true。
  • [[put]]:[[put]]操作找到了属性值,会做出一下动作
    • 属性是否是访问描述符get或set,如果是并且存在 setter 则调用 setter
    • 数据描述符中 writable 如果为 false 则静默失败,严格模式会抛出 typeError
    • 都不是则把这个值设为属性的值

访问描述符 getter & setter

  • 对象会有默认的访问描述符,put和get操作可以分别控制属性的取值和设置
  • 你可以自定义属性的访问描述符 put/get name(){}
  • 当你定义了 get 和 set 来把属性变成访问描述符后,js会忽略他们的 value 和 writable 而只关注 set 和 get。
  • get 定义或者 defineProperty 的显式定义,本质是在对象中创建一个不包含值的属性,对于这个属性的访问会被代理到一个隐藏的函数,函数的返回值当作属性的访问值。

如何区分 undefined 是属性的值还是没有此属性 "name" in obj 返回一个boolean,包括原型链的检查 obj.hasOwnProperty("name") 检查属性是否在对象中,不检查原型链 需要注意对于数组来说,in 操作只会检查下标是否存在而不是值,数组使用 item of arr,原理是请求一个迭代器对象然后调用 next() 遍历。

关于类、原型、与继承

  • js 并没有类的定义在语言中,只是一种设计模式。(这对于使用过面向对象编程语言如java的人来说很容易理解)
  • 原型
    • 对象上本身没有 .constructor 属性???
    • proto 本质更像一个不可枚举的 getter/setter ,通过 getPrototypeOf/setPrototypeOf 实现。「更改对象的proto是一根极其耗费性能的事情,会影响到所有访问proto的地方,尽量避免更改原型并使用 Object.create() 创建指定原型到新对象」
  • 行为委托
    • 通过原型把函数挂载在原型链上,而非在构造函数中使用 this。
    • 禁止环路原型
  • 更多关于原型链继承