读书笔记-你不知道的js(上卷)

本书深入剖析JavaScript的核心概念,如作用域、闭包和原型,强调从语言原生角度理解而非营销角度。作者通过问题引导,揭示常见误解,例如对象与函数的区别、变量提升和this的绑定规则。书中讨论了词法作用域、块级作用域、函数作用域、动态作用域以及this的指向问题,还涵盖了对象、原型链、属性描述符、类和继承等内容,提供了深入理解JS的宝贵见解。

你不知道的js

该书不是全面的讲解,这本书可以作为扫过基础知识后的提升和补充.
作者站在js原生语言的角度(而不是站在js营销的角度,营销使得js扭曲本身的含义去迎合其他语言的理解和使用习惯)去从新定义概念和语义化js。
作者的思路是从问题出发,比如为什么存在这样的问题,又或者是为什么会这样的问题,
进而一步步引导读者去解开谜底,中间经过演变和推论,最后列出结论和推荐.
书本里面的例子代码很有趣.
有经验的读者会有所提升,纠正一些认知,比如对象是没有方法的,原来称呼的方法和函数是不同的概念.对象是有函数的,但没有一个函数是属于对象的,因为对象拥有的都是函数的引用等等这些概念的问题
没有经验的读者稍微会吃力一些,有些部分在讲解基础知识点的时候穿插了其他一些中高级知识点进入,比如设计模式,或者柯里化等
全面讲解意味着深长,而这本书里面的知识点比较犀利简短,有时候为了尽量简单的去讲,有意无意的带有知识点的跳跃性,比如函数运行时的运行时上下文,活动对象等.
所以应该可以这么说,作者是从全面知识体系上一步步纠正以往的代码误解,揭开常见的陷阱,解答模糊不清以及平时不解又不会深入去探讨的原理性问题,更正一些语言的营销概念等等.

作用域和闭包

作用域

编译原理
  1. 分词/词法分析
  2. 解析/语法分析
  3. 代码生成
  • 编译时
  • 运行时
什么是作用域
  1. 引擎
  2. 编译器
  3. 作用域
赋值和查找值

LHS: 赋值操作的目标是谁,赋值
RHS: 谁是赋值操作的源头,查找值

异常

严格模式:禁隐性或自动创建全局变量,顶级变量为undefined。
错误类型

  1. ReferenceError 作用域判别失败, RHS 未声明
  2. TypeError 操作非法或不合理 已声明

词法作用域

  1. 词法作用域
  • 作用域欺骗
  • eval 性能损失 严格模式-禁止使用
  • with 变量泄漏 词法静态分析,引擎优化。而动态代码得不到这些优化,会导致性能下降变慢。
  1. 函数作用域
  • 隐藏内部实现-最小授权原则
  • 规避冲突
    • 全家名称空间
    • 模块管理->闭包
  • 匿名函数
    • 无名称,可读性差。
    • debugger困难
    • 递归和销毁困难
  • 立即执行函数
    • 传参
    • 倒置代码顺序,将函数当作参数传入。
  1. 块作用域 with/try-catch/let/const
  • for循环变量i泄漏问题
  • 遮蔽效应
  • with
  • try-catch 速度慢
  • let 解决for循环i泄漏问题,垃圾收集
  • const
  • 编译优化
  1. 变量提升
  • let 无提升,劫持作用域
  • 变量提升,变量和函数同名,函数优先
  foo() // 1
  function foo() {
    console.log(1)
  }
  var foo = function(){
    console.log(2)
  }
  foo() // 报错,foo重复定义。
  function foo() {
    console.log(1)
  }
  let foo = function(){
    console.log(2)
  }

另一个问题,运行时定义函数。该函数不能提升。

foo() // foo is not a function
let a = true
if (a) {
  function foo() {
    console.log(1)
  }
} else {
  function foo() {  
    console.log(2)
  }
}

动态作用域:this 运行时调用

闭包

  • new Function()
  • 内存泄漏
    • jQ回调函数
    • Ajax
    • 定时器 for循环+定时器,闭包/let解决i泄漏问题
    • 事件监听器
    • 跨窗口通信
    • 异步任务
  • 模块
    • 外部封闭函数,并被调用一次
    • 封闭函数返回一个内部函数,在私有作用域中形成闭包,并提供访问内部变量或修改私有状态功能。
  • 依赖加速器
var moduleClass = (function Manager(){
  var modules = {}
  function define(name, deps, impl) {
    for (var i = 0; i < deps.length; i++) {
      deps[i] = modules[deps[i]] // 查找模块组中的模块装入依赖组中。如果不存在则表示无依赖,比如第一次定义时。
    }
    modules[name] = impl.apply(impl, deps) // 将依赖组当作参数传入回调函数,并执行impl后返回一个功能函数(即return部分)在模块组中和模块名绑定。
  }
  function get(name) {
    return modules[name]
  }
  return {
    define,
    get
  }
})()
moduleClass.define('bar', [], function(){
  function hello(w) {
    console.log(w)
  }
  return {
    hello
  }
})
moduleClass.define('foo', ['bar'], function(bar){
  let w = 'world'
  function say() {
    bar.hello(w)
  }
  return {
    say
  }
})
let b = moduleClass.get('bar')
let f = moduleClass.get('foo')
b.hello('hjj')
f.say()

this和对象原型

this指向

非自身
为什么使用this

function getName(name) {
  return this.name
}
function say(name) {
  console.log('hello ' + getName(name))
}
let hjj = {
  name: 'hjj'
}
let zcf = {
  name: 'tcl'
}
say(hjj)
say(tcl)

使用this

function getName(name) {
  return this.name
}
function say(name) {
  console.log('hello ' + getName.call(this))
}
let hjj = {
  name: 'hjj'
}
let tcl = {
  name: 'tcl'
}
say.call(hjj)
say.call(tcl)

eg. this丢失 for 下函数this指向

function foo(num) {
  console.log('num'+ num)
  this.count++
}
foo.count = 0
for(var i = 0; i < 5; i++) {
  foo(i) // this指向顶级变量
}
console.log(foo.count)
for(var i = 0; i < 5; i++) {
  foo.call(foo, i)
}
console.log(foo.count)

绑定规则

  • 默认绑定
    function foo(){
      console.log(this.a)
    }
    var a = 1
    foo() // 1
    

顶级变量、严格模式下绑定到undefined

  • 隐式绑定
function foo() {
  console.log(this.a)
}
var obj = {
  a: 1,
  foo
}
obj.foo() // this帮定到obj。//1
ps:链式调用指向最后一个函数

  • 隐式丢失
function foo() {
  console.log(this.a)
}
var obj = {
  a:1,
  foo
}
var bar = obj.foo
var a = 2
bar() // 2 this从obj改变成指向顶级变量

eg.2

  function foo() {
    console.log(this.a)
  }
  var obj = {
    a: 1,
    foo
  }
  var a = 2
  function bar(fn) {
    fn()
  }
  bar(obj.foo) // 2
  • 显式绑定 call,apply,硬绑定bind
    一般call
function foo() {
  console.log(this.a)
}
var obj = {
  a: 1
}
var a = 2
foo.call(obj)

call解决的this绑定问题

function foo() {
  console.log(this.a)
}
var obj = {
  a:1,
  foo
}
var bar = obj.foo
var a = 2
bar.call(obj) // 2

eg.2 -call无法解决的this绑定问题

function foo() {
  console.log(this.a)
}
var obj = {
  a: 1,
  foo
}
var a = 2
function bar(fn) {
  fn() // 因为这个位置this指向bar的调用者
}
bar.call(obj, obj.foo) // 2

引出硬绑定
eg.2 硬绑定

function foo() {
  console.log(this.a)
}
var obj = {
  a: 1,
  foo
}
var a = 2
function bar(fn) {
  fn.call(obj) // 内部指向obj
}
bar(foo) // 1
// 即便在外部尝试改变this,也不会有效果,因为this在内部硬绑定了
bar.call(window, foo) // 1

使用apply优化

function foo(b) {
  console.log(this.a) // 1
  return this.a + b
}
var obj = {
  a: 1,
  foo
}
var a = 2
function bar() {
  return foo.apply(obj, arguments)
}
var sum = bar(4)
console.log(sum) // 5

bind实现

function foo(b) {
  console.log(this.a) // 1
  return this.a + b
}
var obj = {
  a: 1,
  foo
}
function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments)
  }
}
var bar = bind(foo,obj) // 柯里化生成上面的bar这个具体函数,将obj解耦出来。
var sum = bar(4)
console.log(sum)

使用原生bind

function foo(b) {
  console.log(this.a) // 1
  return this.a + b
}
var obj = {
  a: 1,
  foo
}
// bind会绑定obj为上下文(即参数被设置为上下文),然后调用foo函数(即obj.foo)
var bar = foo.bind(obj) // 柯里化生成上面的bar这个具体函数。
var sum = bar(4)
console.log(sum)

原生api的this绑定

forEach(function(value,index,ary), thisValue)

function foo(id){
  console.log(this.name + id)
}
var obj = {
  name: 'hjj'
}
let ary = [1,2,3]
ary.forEach(foo, obj)
  • new绑定 新对象
function foo(a) {
  this.a = a
}
var bar = new foo(2)
console.log(bar.a)
  • 软绑定,自动判断来切换使用硬绑定或者隐式还是显式绑定修改this

优先级

new绑定>显式绑定>隐式绑定>默认绑定
证明1:显示绑定和隐式绑定

function foo(){
  consol.log(this.a)
}
var obj1 = {
  a:1,
  foo
}
var obj2 = {
  a:2,
  foo
}
obj1.foo() // 1
obj2.foo() // 2
obj1.foo.call(obj2) // 2
obj2.foo.call(obj1) // 1

证明2:new 绑定和隐式绑定

function foo(a) {
  this.a = a
}
var obj1 = {
  foo
}
obj1.foo(1) // 1
var bar = new obj1.foo(2)
bar.a // 2 
obj1.a // 1

证明3:new绑定和显式绑定

function foo(a) {
  this.a = a
}
var obj1 = {}
var bar = foo.bind(obj1) // bar的this指向obj1
bar(1)
console.log(obj1.a) // 1
var baz = new bar(3) // 生成新的对象,并传入参数,参数为3,绑定到this.a上。
console.log(obj1.a) // 1 
console.log(baz.a) // 3 

忽略this/间接引用都是默认绑定

绑定例外
call、apply、bind传入null或者undefined则会被忽略。转而成为默认绑定。

function foo() {
  console.log(this.a)
}
var a = 1
foo.call(null) // 1 

参数柯里化

function foo(a, b) {
  console.log(a + b)
}
foo.apply(null, [1,2]) // 3
var bar = foo.bind(null, 2)
bar(3) // 5

安全的this

function foo(a,b) {
  return a + b
}
// 更简洁的对象`Object.create(null)`,没有property的对象,
var $ = Object.create(null) // 可读性更强,不用担心null或者undefined被修改.
foo.apply($, [2, 3]) // 5
var bar = foo.bind($, 2)
bar(3) // 5

箭头函数

箭头函数中this会用当前的词法作用域覆盖this本来的值,即指向外层作用域,它放弃了this绑定规则。
不推荐使用let self = this的词法作用域+闭包解决问题的写法

对象

类型

  1. string
  2. number
  3. boolean
  4. null
  5. undefined
  6. object

比如我以前的认识是:

  1. string
  2. number
  3. boolean
  4. null
  5. undefined
  6. object
  7. Array

内置对象

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

content

obj[prot]
obj.prot

可计算属性

obj[a + ‘a’]

属性与方法

属性访问,没有方法,方法也是属于属性。因为函数永远不会属于一个对象。
他们只是对相同函数对象的多个引用

obj = {
  a:1,
  fn: function() {}
}

对象的复制

浅复制
深复制

死循环

JSON安全

let newObj = JSON.parse(JSON.stringify(obj))
//es6
let newObj = Object.assign(obj)

属性描述符

  1. 值 value
  2. 可写性 writeable
  3. 可配置性 configurable
  4. 可枚举性 enumberable
  • 配置性设置为false则后续再无法重新修改该属性,并且无法删除该属性
  • delete会删除属性并且释放该属性的引用,但不能当作a = null 来释放内存占用

不变性

  1. 浅不变性
    仅上层对象不变,其下层引用可变
    writeable: false && configurable: false
    对象常量
let obj = {}
Object.defineProperty(obj, 'constValue', {
  value: 1,
  writeable: false,
  configurable: false
})
  1. 禁止扩展,不可添加属性
let obj = {a: 1}
Object.preventExtensions(obj)
obj.b = 2
console.log(obj.b) // undefined
  1. 密封,不可添加属性&&不可配置
    仅能修改现有属性的值,无法新增属性,也无法修改属性的配置
let obj = {a: 1}
Object.seal(obj) // 等于 object.preventExtensions(obj) && configurable:false
obj.b = 2
console.log(obj.b) // undefined
Object.defineProperty(obj, 'a', { // 报错TypeError
  value: 2,
  writeable: true,
  configurable: true,
  enumberable: true
})
  1. 冻结,不可添加属性&&不可配置&&不可读
let obj = {a: 1}
Object.freeze(obj) // 等于 object.seal(obj) && writeable:false
  • 以上的不可变形都是当前直接属性,引用属性不受影响。如果需要引用属性不可变,需要遍历属性并添加约束,但可能会影响共享对象。

访问描述符

隐藏函数

[[Get]] -> getter

[[Put]] -> setter

vue -> computed

computed: { 
  a:{
    set(){}, 
    get(){}
  }
}
'a' in obj // 是否存在在obj的prototype中,不管属性是否可枚, for in则只会查找可枚举的属性
obj.hasOwnProperty('a') // 是否存在在obj中
Object.prototype.hasOwnProperty.call(obj, 'a') // 能判断通过Object.create(null)后添加的属性。

可枚举

let obj = {}
Object.defineProperty(obj, 'a', {
  enumberable: true,
  value:1
})
Object.defineProperty(obj, 'b', {
  enumberable: false,
  value:1
})
// 判断是否可枚举
Object.propertyIsEnumberable('a') // true 不是原型上的属性,只对象上查找
Object.propertyIsEnumberable('b') // false
Object.keys(obj) // ['a'] 只会出现可枚举属性
Object.getOwnPropertyNames(obj) // ['a', 'b'] 只对象上查找

遍历

for...of...

let a = [1,2,3]
let it = a[Symbol.iterator]() // @@iterator
it.next()
it.next()
it.next()
it.next()

自定义遍历器

let obj = {
  a: 1,
  b: '22'
}
Object.defineProperty(obj, Symbol.iterator, {
  enumberable: false,
  writeable: false,
  configurable: true,
  value: function() {
    var o = this
    var idx = 0
    var ks = Object.keys(o)
    return {
      next: function() {
        return {
          value: o[ks[idx++]],
          done: (idx > ks.length)
        }
      }
    }
  }
})
let it = obj[Symbol.iterator]()
it.next()
it.next()
it.next()
it.next()

自定义iterator需要解决两个问题,一个是value的值进行推进,一个是done什么时候为true,即终止条件是什么。

let z = 'out var'
function a() {
  console.log(z)
  let params = [...arguments]
  let a = 1
  let s = 'abc'
  function print () {
    console.log(params)
  }
  print()
  return a + 1
}
a(...[1,2,3])

类-设计模式

对象、继承、实例
多态:重写方法,可读性和健壮性低
建筑-蓝图
实例-类
构造函数
继承->复制,不是自动执行复制,而是被关联起来

多重继承(不推荐使用,存在以下问题)
js无多重继承,取而代之是mixin

  • 多个父类拥有同一个方法,继承哪个?
  • 砖石问题

几个概念:对象,构造函数,实例,继承,多重继承,多态,混入.

混入(其实就是多态)

  • 显式混入
  • 隐式混入

一般表示为:extend(),或者mixin
实际代码很简单:

function copy() {
  let newObj = {}
  for (let key in obj) {
    if (!(key in newObj)) {
      newObj = obj[key]
    }
    return newObj
  }
}

显示混入 伪多态

  • 函数对象引用问题
  • 寄生继承 (new + call)
function Vehicle() {
  this.engines = 1
}
Vehicle.prototype.ignition = function() {
  console.log("Turning on my engine.")
}
Vehicle.prototype.drive = function() {
  this.ignition()
  console.log('Steering and moving forward!')
}
function Car() {
  var car = new Vehicle()
  car.wheels = 4
  var vehDrive = car.drive
  car.drive = function(){
    vehDrive.call(this)
    console.log('Rolling on all' + this.wheels +' wheels!')
  }
  return car
}
var myCar = Car()
myCar.drive()

隐式混入(不建议使用

let a = {
  init: function(){
    this.msg = 'hello'
    this.count = this.count? this.count + 1: 1
  }
}
let b = {
  init: function() {
    a.dd.call(this)
  }
}
a.init()
a.msg // hello
a.count // 1

b.init()
b.msg // hello
b.count // 1

count数据不会共享,b引用了a的函数,并将属性绑定到自身,从而隐式的继承了属性。

原型

属性设置和屏蔽

  • [[prototype]].foo && writeable:true。obj.foo && 屏蔽属性
  • [[prototype]].foo && writeable:false。无操作无屏蔽,严格模式下报错。
    使用 defineProperty 不受影响
  • [[prototype]].foo 是setter 。调用setter。无屏蔽。

只有第一种情况会被屏蔽

let obj = {
  a: 1
}
let o = Object.create(obj)
o.a
obj.a
obj.hasOwnProperty('a')
o.hasOwnProperty('a')
o.a++
obj.a
o.a
obj.hasOwnProperty('a')
o.hasOwnProperty('a')

构造函数

把原型继承称为委托
作者的观点是:new操作时,函数调用会变成’构造函数的调用’

  • new操作应该被理解为调用函数的构造函数,该函数执行了创建一个新对象,将原函数复制到新对象,然后把新对象的原型链指向原函数的原型对象,最后返回新函数.
  • 构造函数是存在于原型对象上的.所有实例也有构造函数.
  • 构造函数指向原函数本身,所以看起来实例的构造函数是原函数.
  • 原型对象的破坏和修复,尝试直接赋值空对象以及重新定义构造函数并将值指向原函数.此时new Obj()字面上就不成立了,因为实例此时不是由Obj这个对象生成的,而是原型链上查找到的最近一个构造函数生成的.
  • 构造函数可以被随意修改,不可靠

原型继承

function Foo(name) {
  this.name = name
}
Foo.prototype.getName = function() {
  return this.name
}
function Bar(name, label) {
  Foo.call(this, name)
  this.label = label
}
// Bar.prototype = Foo.prototype // 这种写法会在修改Bar的同时同步到Foo,这不如直接修改foo.
// Bar.prototype = new Foo() // 可能会在实例化时生成其他属性或者方法,然后带到后代Bar身上.
// 以前的写法
// Bar.__proto__ = Foo.prototype // 由的浏览器不兼容
// es6前的写法
// Bar.prototype = Object.create(Foo.prototype) // 生成一个新的对象并抛弃掉原有的Foo
// es6后的写法
Object.setPrototypeOf(Bar.prototype, Foo.prototype) // 直接修改
Bar.prototype.getLabel = function() {
  return this.label
}
var a = new Bar('a', 'obj a')
a.getName()
a.getLabel()

检查类的关系

内省/反射:检查实例和原函数是否存在委托关联(继承关系)

类是否生成过该实例(站在类的角度去理解)

a instanceof Foo

在a的整条[[prototype]]中是否出现过Foo.prototype对象(站在实例的角度去理解.更加语义化和便于理解)

Foo.prototype.isPrototypeOf(a) 

两个对象是否通过[[prototype]]链关联

b.isPrototypeOf(a)
Object.getPrototypeOf(a) === Foo.prototype

对象关联, 原型链

function Foo() {
  this.a = 1
}
var a = Object.create(Foo)
// VS
var a = new Foo()

var a = Object.create(Foo)会创建一个新对象并把它关联到 Foo

  • 没有属性
  • prototype 指向 a.prototype === Foo.prototype
  • constructor 指向 Function a.constructor === Function
  • __proto__ 指向 Foo
  • 无法使用instanceof判断
  • 可以使用isPrototypeOf判断

var a = new Foo会创建一个新对象并把它关联到 Foo

  • 带Foo的属性
  • prototype 没有该值
  • constructor 指向 Foo a.constructor === Foo
  • __proto__ 指向 Foo.prototype
  • 可以使用instanceof判断
  • 无法使用isPrototypeOf判断
let a = Object.create(null) // 空[[prototype]]的对象
  • 没有new操作时生成的prototype和constructor引用.
  • 空的[[prototype]]被称为’字典’,用来存储数据,并不受原型链干扰.
  • 该对象没原型链
  • 无法使用instanceof判断

Object.create()的实现

if (!Object.create) {
  Object.create = function(o) {
    function F() {}
    F.prototype = o
    return new F()
  }
}
let Foo = {
  a:1,
  getA: function(){
    console.log(this.a)
  }
}
var a = Object.create(Foo)
a.getA()

var a = Object.create(Foo) // 原型链备用的设计模式,不推荐,推荐使用proxy代理来处理方法找不到时的行为

以上的写法会导致理解困难,作者的想法是,该对象中不存在的,不应该去原型链上查找。

推荐的写法的使用委托的设计模式,目的是为了让api更加清晰。

let Foo = {
  a:1,
  getA: function(){
    console.log(this.a)
  }
}
var a = Object.create(Foo)
a.getMyA = function(){
  this.getA() // 内部委托
}
a.getMyA()

委托理论

  1. 关联对象
  2. 不使用重写改用清晰描述性方法,数据存储在委托者而不是委托目标
  3. this隐式绑定,让父类嵌入子类
  4. 委托行为:某些对象找不到属性或者方法引用时会把这个请求委托给另一个对象
  5. 任意方向的委托关系并排组织
  6. 禁止互相委托,设置时检查比每次检查更高效
  7. 不需要追踪创造者
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值