《你不知道的JS上》笔记

168 阅读6分钟

JS是编译型语言

编译发生在代码执行前几微秒,简单来说就是js在执行前要进行编译,编译过程发生在代码执行前几微妙,甚至更短。

编译的步骤

  1. 词法分析 以var a = 2 为例,词法分析会将其分成三个有意义的代码块即词法单元。
  2. 语法分析 将词法单元组合生成代表了程序语法的结构的树,即抽象语法书(AST)。
  3. 代码生成 将AST生成可执行的代码。即将AST转化成一组机器指令。​​​

LHS \ RHS

如果查找的目的是对变量进行赋值,那么就会使用 LHS 查询;如果目的是获取变量的值,就会使用 RHS 查询。

词法作用域

决定于你在写代码时的块级作用域

优化

依赖于词法的静态分析

eval \ with 会创建新的作用域

在词法分析阶段,无法知道eval \ with会对作用域做怎样的修改,此时引擎不再对作用域进行任何优化 ##函数作用域

函数声明 \ 函数表达式

区分函数声明和表达式最简单的方法是看 function 关键字出现在声明中的位 置(不仅仅是一行代码,而是整个声明中的位置)。如果 function 是声明中 的第一个词,那么就是一个函数声明,否则就是一个函数表达式。

let

  1. 隐式的生成块级作用域
  2. 不存在变量提升

提升

原因

变量(包括函数在内)的所有声明都会优先执行,只有声明本身会提升,而赋值或其他运行逻辑会留在原位置

过程

这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。 可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的 最顶端,这个过程被称为提升。 声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。

闭包

定义

当函数能够记住或访问所在的词法作用域,及时是被作用域外调用,就产生了闭包

模块

  1. 现代模块机制
  2. 未来的模块机制

关于this

绑定时间点

是在函数运行时绑定的,而非定义时。它的上下文取决于函数调用时的各种条件,和在哪里定义的没有关系,只取决于函数的调用方式。

绑定过程

当函数被调用时,会创建一个执行上下文,在这个上下文里包含了函数在哪里没调用(调用栈),调用函数的方法,参数等。this作为执行上下文的一个属性,可以在函数执行的过程中用到。

绑定类型

  1. 默认绑定 即绑定到全局,严格模式下回绑定到undefined。
    function foo() {
      console.log( this.a );
    }
    var a = 2;
    (function(){
      "use strict";
       foo(); // 2
    })()
    
  2. 隐式绑定 即绑定到最顶层(或最近调用对象)上
    function fun() {
      console.log(this.a)
    }
    var obj2 = {
      a: 3,
     fun: fun,
    }
    var obj1 = {
      a: 2,
      obj2: obj2,
    }
    obj1.obj2.fun() // 3
    
  3. 显式绑定 即用call或apply手动进行绑定
  4. bind方法实现
  5. new绑定(构造函数)
    1. 不存在 其实在js中不存在构造函数,我们所说的构造函数其实就是普通的函数,它只是用new被“构造调用”而已。
    2. new发生了什么?
      1. 创建(或者说构造)一个全新的对象。
      2. 这个新对象会被执行[[原型]]连接。
      3. 这个新对象会绑定到函数调用的this。
      4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
  6. 箭头函数 =>

对象

内置对象

基本类型在需要的时候(比如说获取长度),会被引擎默认转成内置对象,从而进行方法的调用。 基础类型并不是继承自内置对象​

    var strPrimitive = "I am a string";
    typeof strPrimitive; // "string"
    strPrimitive instanceof String; // false
    var strObject = new String( "I am a string" );
    typeof strObject; // "object"
    strObject instanceof String; // true
    Object.prototype.toString.call( strObject ); // [object String] 

null

typeof null === Object; 

原理是这样的,不同的对象在底层都表示为二进制,在 JavaScript 中二进制前三位都为 0 的话会被判 断为 object 类型,null 的二进制表示是全 0,自然前三位也是 0,所以执行 typeof 时会返回“object”

拷贝

  1. 浅拷贝 Object.assign({}, obj)
  2. 深拷贝 JSON.stringify

属性描述符

getOwnPropertyDescriptor(myObj, 'a')
defineProperty
Object.defineProperty(myObj, 'a', {
  value: 2,			
  ​writable: true,
  configurable: true, 
  enumerable: true 
​})

Getter 、Setter

var obj = {
  get a() {
    return this._a_
  },
  set a(val) {
   this._a_ = val * 5
  }
}
obj.a = 10
console.log(obj.a) // 50
​
var obj2 = {}
Object.defineProperty(obj2, 'a', {
  get: function() {
    return this._a_
  },
  set: function(val) {
    this._a_ = val * 2
  }
})
obj2.a = 15
console.log(obj2.a) // 30

存在性

  1. in 'a' in obj1 会检查obj及其原型链上是否有'a'
  2. hasOwnProperty 不会检查原型链,如果需要可以Object.prototype.hasOwnProperty.call(myObj, 'a')

原型(prototype)

constructor

返回实例对象O的构造函数(的引用)。任何一个prototype对象都有一个constructor属性,指向它的构造函数,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性​ 例如

function Test() {
  this.name = 'test'
}
var test = new Test()
console.log(test.constructor === Test) // true

类constructor

构造函数 constructor 是用于创建和初始化类中创建的一个对象的一种特殊方法.

class Polygon {
    constructor() {
        this.name = "Polygon";
    }
}
class Square extends Polygon {
    constructor() {
        super();
    }
}
class Rectangle {}
Object.setPrototypeOf(Square.prototype, Rectangle.prototype);
console.log(Object.getPrototypeOf(Square.prototype) === Polygon.prototype); //false
console.log(Object.getPrototypeOf(Square.prototype) === Rectangle.prototype); //true
let newInstance = new Square();
console.log(newInstance.name); //Polygon​

proto

实例对象__proto__指向生成改对象的构造函数的原型 例如

|function Test() {
  this.name = 'test'
}
Test.prototype = {
  color: 'red'
}
var test = new Test()
console.log(test.__proto__ === Test.prototype) // true
console.log(test.__proto__)

Object.create

var foo = {
something: function() {
  console.log( "Tell me something good..." );
}
};
var bar = Object.create( foo ); 
bar.something(); // Tell me something good...
Object.create(..) 会创建一个新对象(bar)并把它关联到我们指定的对象(foo)

这样 我们就可以充分发挥 [[Prototype]] 机制的威力(委托)并且避免不必要的麻烦 (比如使 用 new 的构造函数调用会生成 .prototype 和 .constructor 引用)。 ​

继承

原型继承

缺点 实例的属性都会指向同一个引用 实现

function Parent() {
  this.names = [1, 2, 3]
}
function Child() {
  
}
Child.prototype = new Parent()
var child1 = new Child()
var child2 = new Child()
child1.names.push(4)
console.log(child1.names) // [1,2, 3,4]
console.log(child2.names) // [1,2, 3,4]

####借用构造函数 实现

function Parent() {
  this.names = [1, 2, 3]
  this.getName = function () {
    console.log(this.name)
  }
}
function Child() {
  Parent.call(this)
}
var child1 = new Child()
var child2 = new Child()
child1.names.push(4)
console.log(child1.names)
console.log(child2.names)

缺点 每个子实例都会实例化方法一次,内存爆炸

组合继承(最常用)

实现

function Parent() {
  this.names = [1, 2, 3]
}
Parent.prototype.getName = function () {
  console.log(this.names)
}
function Child() {
  Parent.call(this)
}
Child.prototype = new Parent()
var child1 = new Child()
var child2 = new Child()
child1.names.push(4)
child1.getName()
child2.getName()

缺点

  1. 子类实例上有一份父类的属性,二者重复造成内存浪费
  2. 父类的构造函数被调用了两次​

寄生式组合继承

实现

function Parent() {
  this.names = [1, 2, 3]
}
Parent.prototype.getName = function () {
  console.log(this.names)
}
function Child() {
  Parent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
var child1 = new Child()
var child2 = new Child()
child1.names.push(4)
child1.getName()
child2.getName()

优点 属性不会再原型链上重复

行为委托

js中的继承其实就是在对象间建立关联关系,而行为委托就是建立这种关联关系的纽带。

("原型")面向对象风格

function Foo(who) {
  this.me = who;
}
Foo.prototype.identify = function () {
  return "I am" + this.me;
};
function Bar(who) {
  Foo.call(this,who);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.speak = function () {
  alert("Hello," + this.identify() + '.');
};
var b1 = new Bar("b1");
var b2 = new Bar("b2");
b1.speak();
b2.speak();

对象关联风格

Foo = {
  init:function (who) {
    this.me = who;
  },
  identify:function () {
    return "I am" + this.name
  }
};
Bar = Object.create(Foo);
Bar.speak = function () {
  alert("hello," + this.identify() + '.');
};
var b3 = Object.create(Bar);
b3.init("b3");
var b4 = Object.create(Bar);
b4.init("b4");
b1.speak();
b2.speak();