# 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
# 声明
- 使用关键字
var
: 函数作用域
- 使用关键字
- 使用关键字
let
: 块级作用域(block scope local variable)
- 使用关键字
- 直接使用: 全局作用域
const
关键字可以声明不可变变量,同样为块级作用域,对不可变的理解在对象上的理解需要注意【object 的值不可变是指地址不可变,地址指向可以变】
# 变量提升
JavaScript 中可以引用声明的变量,而不会引发异变,这一概念称为变量声明提升
console.log(a); // undefined
var a = 2; // 变量的声明会提前
// 等同于
var a;
console.log(a);
a = 2;
# 函数 - JavaScript 精髓,JavaScript 归结到底就是函数
一个函数就是一个可以被外部代码调用(或函数本身递归调用)的程序
# 声明函数
- 函数声明
- 函数表达式
- function 构造函数
- 箭头函数
// 声明
function fn() {}
// 表达式
var fn = function() {};
var fn = new Function(){}
var a = ()=>{}
# arguments
arguments
: 一个包含了传递给当前执行函数参数的类似于数组对象
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)
# 定义对象
- 字面量
- 构造函数
- ...
var obj = {
prop: "value",
};
var data = new Date();
# 构造函数
class 是一种规范,new 出来的才是实体
构造函数,使用 new 关键字调用就是构造函数,使用构造函数可以实例化一个对象。
函数的返回值可能有两种:
- 显式调用 return 返回 return 后表达式取值
- 没有调用 return 返回 undefined
# 构造函数返回值
- 没有返回值
- 简单数据类型
- 对象类型
前两种情况构造函数返回构造对象的实例,实例化对象正是利用这个特性。
第三种构造函数和普通函数表现一致,返回return
后表达式的结果
- 对象类型
前两种情况构造函数返回构造对象的实例,实例化对象正是利用这个特性。
function People(name, age) {
this.name = name;
this.age = age;
}
var people = new People("Hai", 26);
# prototype
- 每个函数都有一个
prototype
的对象属性,对象内有一个constructor
属性,默认指向函数本身
- 每个函数都有一个
- 每个对象都有一个
__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 和作用域
- 我是谁 this
- 我有哪些马仔 局部变量
# this 场景
# 普通函数:
- 严格模式:
undefined
- 严格模式:
- 非严格模式: 全局对象
- Node:
global
- 浏览器:
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);
});
# 闭包
闭包由两部分组成,函数的嵌套,并返回函数
- 函数
- 环境:函数创建时作用域内的局部变量
- 解决全局污染的问题
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));
# 尾递归
- 尾调用是指某个函数的最后一步是调用另一个函数
- 函数调用自身,称为递归
- 如果尾调用自身,就称为尾递归 斐波那契数列
递归很容易发生栈溢出
错误(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);