# JavaScript 语言精粹 1

  • 数据驱动 UI,
    • 所有的变化都是是可控的,所有的变化都是确定的,程序是可描述的
    • 指令式编程是糟糕的,程序是需要设计的
  • JavaScript 本身就是一个弱类型语言,是一门面向原型的设计
  • JavaScript 本身的不严谨要求用 JavaScript 的人更严谨
  • JavaScript 很灵活
  • JavaScript 是弱类型,而不是没有类型,靠值来确定具体是那种类型
  • 基本类型:
    • 值类型:存储【直接存储,栈】 Boolean Number String null-根本不存在 undefined-只声明,未赋值 Symbol
    • 对象:【地址放在栈内存里,实际上内存在堆内存里】Array RegExp Date Math Function ---
    • 类型检测 操作符 typeof
typeof alert; // function
typeof null; // object
typeof varss; // undefined

# 变量

在应用程序中,使用变量来为值命名,变量的名称称为 identifiers

# 声明

    1. 使用关键字var: 函数作用域
    1. 使用关键字let: 块级作用域(block scope local variable)
    1. 直接使用: 全局作用域
    1. const关键字可以声明不可变变量,同样为块级作用域,对不可变的理解在对象上的理解需要注意【object 的值不可变是指地址不可变,地址指向可以变】

# 变量提升

JavaScript 中可以引用声明的变量,而不会引发异变,这一概念称为变量声明提升

console.log(a); // undefined
var a = 2; // 变量的声明会提前
// 等同于
var a;
console.log(a);
a = 2;

# 函数 - JavaScript 精髓,JavaScript 归结到底就是函数

一个函数就是一个可以被外部代码调用(或函数本身递归调用)的程序

# 声明函数

    1. 函数声明
    1. 函数表达式
    1. function 构造函数
    1. 箭头函数
// 声明
function fn() {}
// 表达式
var fn = function() {};
var fn = new Function(){}
var a = ()=>{}

# arguments

    1. arguments: 一个包含了传递给当前执行函数参数的类似于数组对象
    1. arguments.length: 传给函数的参数的长度,类数组
  • 3. arguments.caller: 调用当前执行函数的函数
  • 4. arguments.callee: 当前正在执行的函数
function foo() {
  return arguments;
}
foo(1, 2, 3); // evl
// {"0":1,"1":2,"2": 3}

# rest (算是 arguments 的一种优化)

function foo(...args) {
  return args;
}
foo(1, 2, 3); // [1,2,3] Array

# default

函数的参数设置默认值

function fn(a = 2, b = 3) {
  return a + b;
}
fn();

# 对象

JavaScript 中对象是可变的 键控集合(keyed collections)

# 定义对象

    1. 字面量
    1. 构造函数
    1. ...
var obj = {
  prop: "value",
};
var data = new Date();

# 构造函数

class 是一种规范,new 出来的才是实体
构造函数,使用 new 关键字调用就是构造函数,使用构造函数可以实例化一个对象。
函数的返回值可能有两种:

    1. 显式调用 return 返回 return 后表达式取值
    1. 没有调用 return 返回 undefined

# 构造函数返回值

    1. 没有返回值
    1. 简单数据类型
    1. 对象类型 前两种情况构造函数返回构造对象的实例,实例化对象正是利用这个特性。
      第三种构造函数和普通函数表现一致,返回return后表达式的结果
function People(name, age) {
  this.name = name;
  this.age = age;
}
var people = new People("Hai", 26);

# prototype

    1. 每个函数都有一个prototype的对象属性,对象内有一个constructor属性,默认指向函数本身
    1. 每个对象都有一个__proto__的属性,属性指向其父类型的prototype
function Person(name) {
  this.name = name;
}
// 当做对象属性的时候   -> 方法
// 当单独拿出来用的时候叫 function
Person.prototype.print = function() {
  console.log(this.name);
};
const a = new Person("Ssss");
const b = new Person("Kkkkkkkk");

///
Person: function Person(name)
  arguments: null
  caller: null
  length: 1
  name: "Person"
  prototype: Object
    constructor: function Person(name)
    print: function()
    __proto__: Object
  __proto__: function()
  <function scope>

///
a: Person
  name: "Ssss"
  __proto__: Object
    constructor: function Person(name)
    print: function()
    __proto__: Object

# this 和作用域

    1. 我是谁 this
    1. 我有哪些马仔 局部变量

# this 场景

# 普通函数:
    1. 严格模式: undefined
    1. 非严格模式: 全局对象
    2. Node: global
    3. 浏览器: window
# 构造函数:对象的实例
# 对象方法:对象本身

# call & apply

  • fn.call(context, arg1, arg2, ..., argn)
  • fn.apply(context, args)
function isNumber(obj) {
  return Object.prototype.toString.call(obj) === "[object Number]";
}

# Function.prototype.bind

bind返回一个新的函数,函数作用域为 bind 参数

function fn() {
  this.i = 0;
  setInterval(
    function() {
      console.log(this.i++);
    }.bind(this), // 闭包,保留this的环境
    500
  );
}
fn();

# 箭头函数

拥有词法作用域和 this 值

function fn() {
  this.i = 0;
  setInterval(
    () => {
      console.log(this.i++);
    }, // 闭包,保留this的环境
    500
  );
}
fn();

# 继承

子类需要父类的

  • 对象属性
  • 对象方法
// es5
function inherits(child, parent) {
  const _proto = Object.create(parent.prototype);
  _proto.constructor = child.prototype.constructor;
  child.prototype = _proto;
}
function People(name, age) {
  this.name = name;
  this.age = age;
}
People.prototype.getName = function() {
  return this.name;
};
function English(name, age, language) {
  // 借用构造函数
  People.call(this, name, age);
  this.language = language;
}
inherits(English, People);
English.prototype.introduce = function() {
  console.log(this.getName());
  console.log(this.language);
};
const a = new English("aaa", 23, "English");
console.log(a);
console.log(People.prototype);
const b = new People("ddd", 253);
a.introduce();
b.introduce(); // err

/// es6   语法糖
class People {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  getName() {
    return this.name;
  }
}
class English extends People {
  // 如果构建的和父类一样,可以不写constructor
  constructor(name, age, language) {
    super(name, age);
    this.language = language;
  }
  introduce() {
    console.log(this.getName());
    console.log(this.language);
  }
}

# 语法

# label statement

loop: for (var i = 0; i < 10; i++) {
  for (var j = 0; j < 5; j++) {
    console.log(j);
    if (j === 1) {
      break loop;
    }
  }
}
console.log(i);

# 语句与表达式

var x = { a: 1 };
// 语句优先
{a:2};
{a:2, b:2};

(),+, -,!``~里要求必须是一个表达式

/// function(){}()  方法的声明
(function() {})();
// 与
(function() {})(); // 原理不一样

# 高阶函数

高阶函数是把函数当做参数或返回值是函数的函数
函数式编程 vs 指令式编程
确定的输入 肯定会得到 确定的输出

# 回调函数

[1, 2, 3, 4].forEach(function(item) {
  console.log(item);
});

# 闭包

闭包由两部分组成,函数的嵌套,并返回函数

    1. 函数
    1. 环境:函数创建时作用域内的局部变量
    1. 解决全局污染的问题
function makeCounter(init) {
  var init = init || 0;
  return function() {
    return ++init;
  };
}
var counter = makeCounter(10);
console.log(counter());
console.log(counter());
console.log(counter());
console.log(counter());
counter = null;
# 滥用闭包
const doms = [0, 1, 2, 3, 4, 5];
for (var i = 0; i < doms.length; i++) {
  doms.eq(i).on("click", function(ev) {
    console.log(i); // 因为i没被释放掉,没有被销毁,并且保存下来了
  });
}

for (var i = 0; i < doms.length; i++) {
  // 利用函数的特点  实参  把每一次的i值都保留下来
  (function(i) {
    doms.eq(i).on("click", function(ev) {
      console.log(i);
    });
  })(i);
}

# 惰性函数

// 执行一次   之后就能确定用哪一个
function eventBinderGenerator() {
  if (window.addEventListener) {
    return function(element, type, handler) {
      element.addEventListener(type, hanlder, false);
    };
  } else {
    return function(element, type, handler) {
      element.attachEvent("on" + type, handler.bind(element, window.event));
    };
  }
}
const fun = eventBinderGenerator();
fun(element, type, handler);

# 柯里化

一种允许使用部分参数生成函数的方式

# 抽象变化

  • 每次都变
  • 确定一次改变后 就不变了 一次性变化 抽象到 type
function isType(type) {
  return function(obj) {
    return Object.prototype.toString.call(obj) === "[object " + type + "]";
  };
}
var isNumber = isType("Number");
console.log(isNumber(1));
console.log(isNumber("d"));
var isArray = isType("Array");

function pipe(f, g) {
  return function() {
    return f.call(null, g.apply(null, arguments));
  };
}
var fn = pipe(f, g);
console.log(fn(5));

# 尾递归

    1. 尾调用是指某个函数的最后一步是调用另一个函数
    1. 函数调用自身,称为递归
    1. 如果尾调用自身,就称为尾递归 斐波那契数列

递归很容易发生栈溢出错误(stack overflow) 但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生栈溢出

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

# 反柯里化

Function.prototype.uncurry = function() {
  return this.call.bind(this);
};
// push 通用化
var push = Array.prototype.push.uncurry();
var arr = [];
push(arr, 1);
push(arr, 3);
push(arr, 4);