JavaScript与TypeScript总结
一、ES6标准
1.let、var、const
①let声明的变量只在let
命令所在的代码块内有效。var声明的变量在全局都有效,代码块内会收到全局其他地方重复声明的影响。const将声明一个无法重复赋值的变量。
②var的变量提升:变量可以在声明之前使用,值为undefined
。let不允许变量提升。
③“暂时性死区”(temporal dead zone,简称 TDZ):如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
④let
不允许在相同作用域内,重复声明同一个变量。var可以。
2.变量的解构赋值
①基本用法:
let [a, b, c] = [1, 2, 3];
②如果解构不成功,变量的值就等于undefined
。
③解构赋值允许指定默认值。
let [foo = true] = []; foo // true let [x, y = 'b'] = ['a']; // x='a', y='b' let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
④字符串的解构赋值:
const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o"
类似数组的对象都有一个length
属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello'; len // 5
⑤使用解构赋值交换变量的值
let x = 1; let y = 2; [x, y] = [y, x];
⑥遍历遍历 Map 结构
const map = new Map(); map.set('first', 'hello'); map.set('second', 'world'); for (let [key, value] of map) { console.log(key + " is " + value); }
3.函数的扩展
①利用参数默认值,可以指定某一个参数不得省略。
function throwIfMissing() { throw new Error('Missing parameter'); } function foo(mustBeProvided = throwIfMissing()) { return mustBeProvided; } foo() // Error: Missing parameter
②ES6 引入 rest 参数(形式为...变量名
),用于获取函数的多余参数。
function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } add(2, 5, 3) // 10
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
③严格模式
'use strict'
④name属性:调用XXX.name将返回这个XXX函数的函数名。
⑤嵌套的箭头函数
function insert(value) { return {into: function (array) { return {after: function (afterValue) { array.splice(array.indexOf(afterValue) + 1, 0, value); return array; }}; }}; } insert(2).into([1, 3]).after(1); //[1, 2, 3]
let insert = (value) => ({into: (array) => ({after: (afterValue) => { array.splice(array.indexOf(afterValue) + 1, 0, value); return array; }})}); insert(2).into([1, 3]).after(1); //[1, 2, 3]
但是,一旦不注意,可读性就会呈几何级下降。
⑥尾函数优化
函数执行的时候有一个函数栈,在A函数中执行B函数,A函数的执行结果和调用点都会保存在函数栈中,因为B函数调用完会还得返回去执行A函数。当A函数的最后一步操作是执行B函数的时候,显而易见的,可以释放A函数在函数栈上的资源了。
典型应用就是,递归函数,只要递归函数的最后一步是执行其本身,那么就会使爆栈的几率大大降低。看一个Fibonacci 数列的例子:
function Fibonacci (n) { if ( n <= 1 ) {return 1}; return Fibonacci(n - 1) + Fibonacci(n - 2); } Fibonacci(10) // 89 Fibonacci(100) // 堆栈溢出 Fibonacci(500) // 堆栈溢出
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) { if( n <= 1 ) {return ac2}; return Fibonacci2 (n - 1, ac2, ac1 + ac2); } Fibonacci2(100) // 573147844013817200000 Fibonacci2(1000) // 7.0330367711422765e+208 Fibonacci2(10000) // Infinity
遗憾的是,是否进行尾函数优化,是由编程语言跟编译器决定的。ES6支持,但是比如JAVA就不支持这个东西(JAVA不建议使用递归)。
4.数组的扩展
①扩展运算符“...”
将一个数组转为用逗号分隔的参数序列。
console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5 // 可与其他参数一起使用 [...document.querySelectorAll('div')] // [<div>, <div>, <div>] function add(x, y) { return x + y; } const numbers = [4, 38]; add(...numbers) // 42
注意,扩展运算符如果放在括号中,JavaScript 引擎就会认为这是函数调用。如果这时不是函数调用,就会报错。
由于扩展运算符可以展开数组,所以不再需要apply
方法,将数组转为函数的参数了。
还可以赋值数组:const a2 = [...a1];
还可以合并数组:const a4 = [...a1, ...a2];
还可以将字符串转为真正的数组:const a5 = [...'hello'] (任何定义了遍历器Iterator接口的对象,都可以用扩展运算符转为真正的数组。)
Map 和 Set 结构,Generator 函数,都可以使用扩展运算符,比如 Map 结构:
let map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); let arr = [...map.keys()]; // [1, 2, 3]
5.对象的扩展
①ES6 允许直接写入变量和函数,作为对象的属性和方法(只要你有一个引用,你甚至可以包括整个宇宙)。
let birth = '2000/01/01'; const Person = { name: '张三', //等同于birth: birth birth, // 等同于hello: function ()... hello() { console.log('我的名字是', this.name); } };
function getPoint() { const x = 1; const y = 10; return {x, y}; } getPoint() // {x:1, y:10}
②属性遍历
ES6 一共有 5 种方法可以遍历对象的属性。官方推荐使用Object.keys
返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
遍历对象的键名,都遵守同样的属性遍历的次序规则。
- 首先遍历所有数值键,按照数值升序排列。
- 其次遍历所有字符串键,按照加入时间升序排列。
- 最后遍历所有 Symbol 键,按照加入时间升序排列。
③Object.assign
方法
Object.assign
方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。但是,Object.assign
方法实行的是浅拷贝,而不是深拷贝。
const target = { a: 1 }; const source1 = { b: 2 }; const source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}
同样的操作也可以进行对象克隆
function clone(origin) { let originProto = Object.getPrototypeOf(origin); return Object.assign(Object.create(originProto), origin); }
为对象添加属性
class Point { constructor(x, y) { Object.assign(this, {x, y}); } }
为对象添加方法
Object.assign(SomeClass.prototype, { someMethod(arg1, arg2) { ··· }, anotherMethod() { ··· } }); // 等同于下面的写法 SomeClass.prototype.someMethod = function (arg1, arg2) { ··· }; SomeClass.prototype.anotherMethod = function () { ··· };
6.Symbol变量
①Singleton 模式
// mod.js const FOO_KEY = Symbol.for('foo'); function A() { this.foo = 'hello'; } if (!global[FOO_KEY]) { global[FOO_KEY] = new A(); } module.exports = global[FOO_KEY];
const a = require('./mod.js');
console.log(a.foo);
这同时也是全局变量的设置方案,唔。
7.Set 变量
①成员的值都是唯一的,没有重复的值。四大方法:add、delete、has、clear。
②Set
函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
// 例一 const set = new Set([1, 2, 3, 4, 4]); [...set] // [1, 2, 3, 4]
③利用Set给数组去重
// 去除数组的重复成员 [...new Set(array)]
给字符串去重
[...new Set('ababbc')].join('') // "abc"
Array.from
方法可以将 Set 结构转为数组。这就提供了去除数组重复成员的另一种方法。
function dedupe(array) { return Array.from(new Set(array)); } dedupe([1, 1, 2, 3]) // [1, 2, 3]
8.Proxy
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
其实就是一个拦截器,按照官方的描述,更像是一个C++的运算符重载,跟C#的set构造器有异曲同工之妙。它几乎可以作用于所有对象的行为,不仅仅是传参。(或许可以拿来弄一个enum)
一个技巧是将 Proxy 对象,设置到object.proxy
属性,从而可以在object
对象上调用。 var object = { proxy: new Proxy(target, handler) };
实现数组读取负数的索引
function createArray(...elements) { let handler = { get(target, propKey, receiver) { let index = Number(propKey); if (index < 0) { propKey = String(target.length + index); } return Reflect.get(target, propKey, receiver); } }; let target = []; target.push(...elements); return new Proxy(target, handler); } let arr = createArray('a', 'b', 'c'); arr[-1] // c
9.JavaScript Array 对象的方法
常用方法:
push() 向数组的末尾添加一个或更多元素,并返回新的长度。
concat() 连接两个或更多的数组,并返回结果(a.concat(b))。
值得注意的是,若a是空的,就不能直接concat。
10.=== 和 ==的区别
===严格比较,类型不同就是不同;==会将两边转换成值再比较,比如string和int,1和‘1’是相等的。