你不知道的JavaScript上 -- 学习笔记

  • 你不知道的JavaScript上 – 学习笔记
    • 作用域和闭包
      • 编译
      • 作用域
        • 概念
        • 编译过程
        • LHS VS RHS
      • 词法作用域
        • 词法作用域
          • 改变词法作用域
      • 函数作用域
        • 块作用域
      • 提升
        • 变量提升 vs 函数提升
      • 闭包
        • 定义
      • 动态作用域
      • This词法
        • 问题
    • this和对象原型
      • 显示绑定 – call apply bind
        • 硬绑定
          • 直接硬绑定
          • 创建绑定函数
          • ES5中的内置方法 – Function.prototype.bind
      • new 绑定
      • 优先级
      • 绑定例外
      • 对象
        • 语言类型
        • 内置对象 – 内置函数
        • 属性
          • 可计算属性名 – ES6
          • 复制对象
          • 属性描述符 – ES5 – 数据描述符
            • 不变性
            • get 和 set
            • Getter 和 Setter
            • 属性是否存在
        • 原型
          • 原型继承
          • 检查对象之间的关系
        • 委托
          • 委托 VS 原型
      • ES6的部分优化
      • 遍历
        • 数组迭代器 – ES5
        • 对象迭代器 – ES6
      • class的陷阱

你不知道的JavaScript上 – 学习笔记

作用域和闭包

编译

  • 分词/词法分析

    • 将字符串分解成代码块(词法单元) – var a = 2

    • 分词 vs 词法分析

    有状态的解析规则 – 词法分析

    无状态的解析规则 – 分词

  • 解析/语法分析

    将词法单元数组变成抽象语法树

  • 代码生成

    AST 转换为可执行代码的过程称被称为代码生成

作用域

概念

  • 引擎

    编译和执行JavaScript

  • 编译器

    语法分析及代码生成

  • 作用域

编译过程

变量的赋值操作会执行两个动作

  • 编译器会在当前作用域中声明一个变量(如果之前没有声明过)
  • 运行时引擎会在作用域中查找该变量, 如果能够找到就会对它赋值.

LHS VS RHS

词法作用域

作用域共有两种主要的工作模型。

  • 第一种是最为普遍的, 被大多数编程语言所采用的词法作用域, 我们会对这种作用域进行深入讨论。
  • 另外一种叫作动态作用域

词法作用域

是由你在写代码时将变量和块作用域写在哪里来决定的, 因此当词法分析器处理代码时会保持作用域
不变.

改变词法作用域
  • eval – 严格模式下,eval有它自己的作用域,所以严格模式下行不通.

    setTimeout(..)/setInterval(..) 也有类似效果 – 所以不提倡使用第一个参数做为字符串.

  • with – 严格模式也被完全禁止了.

函数作用域

块作用域

let 可以将变量绑定在块作用域

const 常量声明 且绑定在块作用域

提升

变量提升 vs 函数提升

  • 函数提升优先于变量提升
  • 后面的函数声明会覆盖前面的

闭包

定义

内部函数(定义函数)和外部函数(定义函数所在的函数)之间的作用域的引用叫做闭包.

动态作用域

词法作用域是在写代码或者说定义时确定.

动态作用域是在运行时确定. – 从堆栈一层一层向上找.

This词法

问题

利用词法作用域和闭包.解决this指向问题.

var obj = {
    count: 0,
    cool: function coolFn() {
        var self = this;
        if (self.count < 1) {
            setTimeout( function timer(){
                self.count++;
                console.log( "awesome?" );
            }, 100 );
        }
    }
};
obj.cool(); // 酷吧? 

箭头函数放弃了普通this的绑定规则,将当前的词法作用域(调用SetTimeout的作用域,cool函数的作用域)覆盖了this本来的值.

var obj = {
    count: 0,
    cool: function coolFn() {
        if (this.count < 1) {
            setTimeout( () => { // 箭头函数是什么鬼东西?
                this.count++;
                console.log( "awesome?" );
            }, 100 );
        }
    }
};
obj.cool(); // 很酷吧 ?

另一种方式

var obj = {
    count: 0,
    cool: function coolFn() {
        if (this.count < 1) {
            setTimeout( function timer(){
                this.count++; // this 是安全的
                // 因为 bind(..)
                console.log( "more awesome" );
            }.bind( this ), 100 ); // look, bind()!
        }
    }
}

this和对象原型

  1. 默认绑定 – 全局 – 非严格模式下
  2. 隐式绑定 – 谁调用就指向谁
  3. 显示绑定 – bind之类…
  4. new绑定 – 指向返回值

显示绑定 – call apply bind

xw.say.call(xh,"实验小学","六年级");
xw.say.apply(xh,["实验小学","六年级"]);
xw.say.bind(xh,"实验小学","六年级")();
xw.say.bind(xh)("实验小学","六年级");//bind还有其他功能 -- 绑定部分参数

function foo(p1,p2) {
    this.val = p1 + p2;
} // 之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么
// 反正使用 new 时 this 会被修改
var bar = foo.bind( null, "p1" );
var baz = new bar( "p2" );
baz.val; // p1p2

硬绑定

直接硬绑定
function foo(something) {
    console.log( this.a, something );
    return this.a + something;
}
var obj = {
    a:2
};
var bar = function() {
    return foo.apply( obj, arguments ); // arguments对象是所有(非箭头)函数中都可用的局部变量
};
var b = bar( 3 ); // 2 3
console.log( b ); // 5
创建绑定函数
function foo(something) {
    console.log( this.a, something );
    return this.a + something;
} // 简单的辅助绑定函数
function bind(fn, obj) {
    return function() {
        return fn.apply( obj, arguments );
    };
}
var obj = {
    a:2
};
var bar = bind( foo, obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
ES5中的内置方法 – Function.prototype.bind
function foo(something) {
    console.log( this.a, something );
    return this.a + something;
}
var obj = {
    a:2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5

new 绑定

使用new 调用函数

  • 创建一个全新的对象
  • 执行原型链接
  • 新对象绑定到函数调用的this
  • 如果没有手动return,默认自动return新对象

优先级

new > 显示绑定(硬绑定) > 隐式绑定 > 默认绑定

绑定例外

  1. call apply bind使用null或者undefined会被忽略. – 使用默认绑定规则.

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

    柯里化会用到绑定null

    function foo(a,b) {
    console.log( "a:" + a + ", b:" + b );
    } // 把数组“展开” 成参数
    foo.apply( null, [2, 3] ); // a:2, b:3
    // 使用 bind(..) 进行柯里化
    var bar = foo.bind( null, 2 );
    bar( 3 ); // a:2, b:3

    解决办法

    function foo(a,b) {
    console.log( "a:" + a + ", b:" + b );
    } // 我们的 DMZ 空对象
    var ø = Object.create( null );
    // 把数组展开成参数
    foo.apply( ø, [2, 3] ); // a:2, b:3
    // 使用 bind(..) 进行柯里化
    var bar = foo.bind( ø, 2 );
    bar( 3 ); // a:2, b:3
  2. 间接引用

    function foo() {
    console.log( this.a );
    }
    var a = 2;
    var o = { a: 3, foo: foo };
    var p = { a: 4 };
    o.foo(); // 3
    (p.foo = o.foo)(); // 2 返回的是foo而不是p.foo
  3. 软绑定

    看不懂…

    if (!Function.prototype.softBind) {
       Function.prototype.softBind = function(obj) {
           var fn = this;
           // 捕获所有 curried 参数
           var curried = [].slice.call( arguments, 1 );
           var bound = function() {
               return fn.apply(
                   (!this || this === (window || global)) ?
                   obj : this
                   curried.concat.apply( curried, arguments )
               );
           };
           bound.prototype = Object.create( fn.prototype );
           return bound;
    };
    }

对象

语言类型

  • string
  • number
  • boolean
  • null
  • undesined
  • object

简单基本类型(string、 boolean、 number、 null 和 undefined) 本身并不是对象。null 有时会被当作一种对象类型, 但是这其实只是语言本身的一个 bug, 即对 null 执行typeof null 时会返回字符串 “object”。实际上, null 本身是基本类型。

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

内置对象 – 内置函数

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

内置函数并不是类或类型,但是JavaScript会自动装箱在必要的时候.

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

null 和 undefined 没有对应的构造形式, 它们只有文字形式。 相反, Date 只有构造, 没有文字形式

Object、 Array、 Function 和 RegExp(正则表达式) 来说, 无论使用文字形式还是构造形式, 它们都是对象, 不是字面量.

尽量使用字面量形式.

属性

属性并不一定存在于对象内部.对象内部只存储属性名称.

可计算属性名 – ES6
var prefix = "foo";
var myObject = {
    [prefix + "bar"]: "hello",
    [prefix + "baz"]: "world"
};
myObject["foobar"]; // hello
myObject["foobaz"]; // world
复制对象

浅复制 – var newObj = Object.assign( {}, myObject );

深复制 – 没有保准

属性描述符 – ES5 – 数据描述符
var myObject = {
    a:2
};
Object.getOwnPropertyDescriptor( myObject, "a" );
// {
// value: 2,
// writable: true,
// enumerable: true,
// confgurable: true
// }

可以配置

var myObject = {};
Object.defineProperty( myObject, "a", {
    value: 2,
    writable: true,
    configurable: true,
    enumerable: true
} );
myObject.a; // 2

configurable: false唯一可以修改的属性是将writable从true –> false

不变性
  1. 对象常量

    var myObject = {};
    Object.defineProperty( myObject, "FAVORITE_NUMBER", {
       value: 42,
       writable: false,
       configurable: false
    } );
  2. 禁止扩展 – 禁止添加属性

    var myObject = {
    a:2
    };
    Object.preventExtensions( myObject );
    myObject.b = 3;
    myObject.b; // undefined
  3. 密封 – 2+configurable:false – 禁止添加新属性禁止配置属性可以修改属性的值

    Object.seal(..)

  4. 冻结 – 3 + writable:false

    Object.freeze(..)

get 和 set

get

  1. 从当前获取,
  2. 走原型链
  3. 返回undefined

set

  1. 存在该属性
    1. 存在setter调用setter
    2. 描述符writable是否是false?
      1. 如果是 – 无法修改或创建属性值 – 如果是严格模式会抛异常,非严格模式忽略.
    3. 如果不是,设置属性值
  2. 不存在该属性

Object.defineProperty(..)不会触发set

Getter 和 Setter

当你给一个属性定义 getter、 setter 或者两者都有时, 这个属性会被定义为“访问描述符”(和“数据描述符” 相对).对于访问描述符来说, JavaScript 会忽略它们的 value 和 writable 特性, 取而代之的是关心 set 和 get(还有 configurable 和 enumerable) 特性。

var myObject = {
    // 给 a 定义一个 getter
    get a() {
        return 2;
    }
    // 给 a 定义一个 setter
    set a(val) {
        this._a_ = val * 2;
    }
};
Object.defineProperty(
    myObject, // 目标对象
    "b", // 属性名
    { // 描述符
        // 给 b 设置一个 getter
        get: function(){ return this.a * 2 },
        // 确保 b 会出现在对象的属性列表中
        enumerable: true
    }
);
myObject.a; // 2
myObject.b; // 4
属性是否存在
  • in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中

  • hasOwnProperty(..) 只会检查属性是否在 myObject 对象中 ,不会检查原型链

    Object.prototype.hasOwnProperty.call(myObject,"a")

  • Object.keys(..) 会返回一个数组, 包含所有可枚举属性

  • Object.getOwnPropertyNames(..) , 无论它们是否可枚举

原型

  • Foo.prototype – 公有并且不可枚举 - 在new F00()的时候关联而不是创建.

    function Foo() { /* .. */ }
    Foo.prototype = { /* .. */ }; // 创建一个新原型对象
    var a1 = new Foo();
    a1.constructor === Foo; // false!
    a1.constructor === Object; // true!

    手动添加constructor

    function Foo() { /* .. */ }
    Foo.prototype = { /* .. */ }; // 创建一个新原型对象
    // 需要在 Foo.prototype 上“修复” 丢失的 .constructor 属性
    // 新对象属性起到 Foo.prototype 的作用
    // 关于 defineProperty(..), 参见第 3 章
    Object.defineProperty( Foo.prototype, "constructor" , {
      enumerable: false,
      writable: true,
      configurable: true,
      value: Foo // 让 .constructor 指向 Foo
    } );
  • Foo.prototype.constructor === Foo; – 共有可写不可枚举

  • 对象(foo)也有一个constructor 指向Foo.prototype.constructor. – 实际上是委托

原型继承

Object.create(..) – 创建一个新对象并把新对象内部的prototype关联到你指定的对象

function Foo(name) {
    this.name = name;
} 
Foo.prototype.myName = function() {
    return this.name;
};
function Bar(name,label) {
    Foo.call( this, name );
    this.label = label;
} 
// 我们创建了一个新的 Bar.prototype 对象并关联到 Foo.prototype
// Bar.prototype = Foo.prototype不合理 -- 直接引用会影响到父类
// Bar.prototype = new Foo() -- 会直接调用父类构造方法,不科学
//ES5 如下 继承方法不继承属性
Bar.prototype = Object.create( Foo.prototype );
// 注意! 现在没有 Bar.prototype.constructor 了
// 如果你需要这个属性的话可能需要手动修复一下它
//ES6 和 ES5功能完全相同
// Object.setPrototypeOf( Bar.prototype, Foo.prototype );
Bar.prototype.myLabel = function() {
    return this.label;
};
var a = new Bar( "a", "obj a" );
a.myName(); // "a"
a.myLabel(); // "obj a
检查对象之间的关系
  • a instance of Foo – a的原型链中是否存在Foo.prototype对象
  • Foo.prototype.isPrototypeOf( a ); // true 和上面相同
  • Object.isPrototypeOf( a )
  • b.isPrototypeOf( c );
  • Object.getPrototypeOf( a ) === Foo.prototype; // true
  • a.__proto__ === Foo.prototype; // truea.__proto__ 存在于Object.prototype

委托

Task = {
  setID: function(ID) {
    this.id = ID;
  },
  outputID: function() {
    console.log(this.id);
  }
};
// 让 XYZ 委托 Task
XYZ = Object.create(Task);
XYZ.prepareTask = function(ID, Label) {
  this.setID(ID);
  this.label = Label;
};
XYZ.outputTaskDetails = function() {
  this.outputID();
  console.log(this.label);
};
// ABC = Object.create( Task );
// ABC ... = ...
委托 VS 原型
  • 原型继承基于类 – 显性操作prototype对象.

    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();
  • 委托基于对象 – 直接创建对象,并关联对象,方法都和对象绑定而不是prototype

    Foo = {
    init: function(who) {
      this.me = who;
    },
    identify: function() {
      return "I am " + this.me;
    }
    };
    Bar = Object.create(Foo);
    Bar.speak = function() {
    alert("Hello, " + this.identify() + ".");
    };
    var b1 = Object.create(Bar);
    b1.init("b1");
    var b2 = Object.create(Bar);
    b2.init("b2");
    b1.speak();
    b2.speak();
  • 实际例子

    • 原型继承
    // 父类
    function Widget(width, height) {
      this.width = width || 50;
      this.height = height || 50;
      this.$elem = null;
    }
    Widget.prototype.render = function($where) {
      if (this.$elem) {
        this.$elem
          .css({
            width: this.width + "px",
            height: this.height + "px"
          })
          .appendTo($where);
      }
    };
    // 子类
    function Button(width, height, label) {
      // 调用“super” 构造函数
      Widget.call(this, width, height);
      this.label = label || "Default";
      this.$elem = $("
    • class关键字 – ES6
    class Widget {
        constructor(width,height) {
            this.width = width || 50;
            this.height = height || 50;
            this.$elem = null;
        } 
        render($where){
            if (this.$elem) {
                this.$elem.css( {
                    width: this.width + "px",
                    height: this.height + "px"
                } ).appendTo( $where );
            }
        }
    }
    class Button extends Widget {
        constructor(width,height,label) {
            super( width, height );
            this.label = label || "Default";
            this.$elem = $( "
    • 基于委托 – 问题是如果方法名字相同怎么办?
    var Widget = {
      init: function(width, height) {
        this.width = width || 50;
        this.height = height || 50;
        this.$elem = null;
      },
      insert: function($where) {
        if (this.$elem) {
          this.$elem
            .css({
              width: this.width + "px",
              height: this.height + "px"
            })
            .appendTo($where);
        }
      }
    };
    var Button = Object.create(Widget);
    Button.setup = function(width, height, label) {
      // 委托调用
      this.init(width, height);
      this.label = label || "Default";
      this.$elem = $("

ES6的部分优化

没有key了.

var AuthController = {
    errors: [],
    checkAuth() {
        // ...
    },
    server(url,data) {
        // ...
    } 
    // ...
};

但是也有问题.

var Foo = {
    bar() { /*..*/ },
    baz: function baz() { /*..*/ }
};

去掉语法糖之后变成

var Foo = {
    bar: function() { /*..*/ }, // 匿名函数
    baz: function baz() { /*..*/ }
};

遍历

数组迭代器 – ES5

  • forEach(..) – 忽略回调的返回值
  • every(..) – 一直运行直到回调返回false
  • some(..) – 一直运行直到返回true或”真”

对象迭代器 – ES6

var myArray = [ 1, 2, 3 ];
for (var v of myArray) {
    console.log( v );
} 
// 1
// 2
// 3
var myArray = [ 1, 2, 3 ];
var it = myArray[Symbol.iterator]();
it.next(); // { value:1, done:false }
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { done:true }
var myObject = {
    a: 2,
    b: 3
};
Object.defineProperty( myObject, Symbol.iterator, {
    enumerable: false,
    writable: 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)
                };
            }
        };
    }
} );
// 手动遍历 myObject
var it = myObject[Symbol.iterator]();
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { value:undefned, done:true }
// 用 for..of 遍历 myObject
for (var v of myObject) {
    console.log(v);
} 
// 2
// 3

class的陷阱

陷阱太多,不一一列举了.

你可能感兴趣的:(学习笔记)