0%

基本概念

  • 作用域
  • 词法作用域
  • 函数级作用域和块级作用域
  • 作用域提升
  • 作用域闭包

作用域简单地理解就是变量的可访问范围,词法作用域也称静态作用域,强调编码决定作用域范围而与运行时无关,js使用的是词法作用域

函数级作用域指函数内定义的变量在函数体范围内有效

let const具备块级作用域,通常是大括号的范围

var在运行时具有声明提前的作用,但不会突破函数级作用域或者块级作用域

题目1

1
2
3
4
5
6
7
function t1(){
console.log(str1);
console.log(str2);

var str1="xx1";
str2="xx2"
}

调用t1();输出

1
2
undefined
Uncaught ReferenceError: str2 is not defined

关键字var提示js编译器词法分析对变量进行声明,没有该关键字,分析认为是单纯赋值操作;执行时自上而下,输出str1时未赋值,其值为undefined,而str2为未声明。

题目2

1
2
3
4
5
6
7
var i=10;
function t2(){
i=20;
for(var i=0;i<6;i++){
}
console.log(this.i);
}

调用t2()输出10。
this指向window对象

题目3

关于闭包

闭包的作用和垃圾回收

创建对象

1 工厂方式

1
2
3
4
5
6
7
8
9
function createPerson(name, age, job){ 
var o = new Object(); o.name = name; o.age = age; o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29"Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

工厂模式虽然解决了创建 多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)

2构造方法

1
2
3
4
5
6
7
function Person(name, age, job){ 
this.name = name; this.age = age;
this.job = job;
this.sayName = function(){ alert(this.name);};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

构造的对象属于同一类型

1
2
3
4
5
6
7
alert(person1.constructor == Person); //true 
alert(person2.constructor == Person); //true

alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true

其实有这么一个问题:不同实例上的同名函数是不相等的
即person1.sayName == person2.sayName为false

3原型方式

1
2
3
4
5
6
7
8
9
function Person(){ }
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){ alert(this.name);};
var person1 = new Person();
person1.sayName();
var person2 = new Person();
person2.sayName();

所有对象可以通过引用Person.prototype属性,从而实现共享属性和方法

原型对象,对象属性和原型属性

无论什么时候,只要创建了一个新函数(如上文Person),就会根据一组特定的规则为该函数创建一个原型对象prototype,在默认情况下,所有原型对象都会自动获得一个 constructor,该构造方法指向 prototype 属性所在函数(即Person.prototype.constructor=Person)通过这个构造函数,我们还可继续为原型对象添加其他属性和方法。创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都是从 Object 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(__proto__,注,ECMA-262第5版中管这个指针叫[[Prototype]]。虽然在脚本中没有标准的方式访问[[Prototype]],但 Firefox、Safari 和 Chrome 在每个对象上都支持属性 proto 而在其他实现中,这个属性对脚本则是完全不可见的),指向构造函数的原型对象(即person1.__proto__=Person.prototype)。

![prototype](https://upload-images.jianshu.io/upload_images/3140250-a75fde4f80938b58.PNG "prototype")

在无法访问[[Prototype]]的情形下(QQs尚未接触到不支持__proto__的环境),可以通过 isPrototypeOf()方法来确定对象之间是否存在这种关系,有Person.prototype.isPrototypeOf(person1)返回true,另ES5提供Object.getPrototypeOf,有Object.getPrototypeOf(person1) == Person.prototype为true。

代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先 从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到, 则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这 个属性,则返回该属性的值。也就是说,在我们调用 person1.sayName()的时候,会先后执行两次搜 索。首先,解析器会问:“实例 person1 有 sayName 属性吗?”答:“没有。”然后,它继续搜索,再 问:“person1 的原型有 sayName 属性吗?”答:“有。”于是,它就读取那个保存在原型对象中的函数。

可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果我们 在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。而delete对象实例的这个属性,会重新暴露原型对象的属性

1
2
3
4
5
6
Person.prototype.name = "Nicholas";
var person1 = new Person();
person1.name = "Greg";
alert(person1.name); //"Greg"——来自实例
delete person1.name;
alert(person1.name);//"Nicholas"——来自原型

使用hasOwnProperty()方法可以检测一个属性是存在于实例中(true),还是存在于原型中(false)。
检测属性(无论实例属性或是原型属性)是否存在可以用in,即”name” in person1返回true。

从in说到for in,遍历所有能够通过对象访问的、可枚举的(enumerated)属性,其中 既包括存在于实例中的属性,也包括存在于原型中的属性。

“可枚举”相当于”可以出现在对象属性的遍历中”,在数组上使用for in遍历,不仅会包含所有数值索引,还会包含所有可枚举属性,。最好只在对象上应用 for in 循环,如果要遍历数组就使用传统的 for 循环来遍历数值索引

Object.propertyIsEnumerable()方法可以检查给定属性名是否直接存在于实例中且enumerable = true
不可枚举属性,即将 [[Enumerable]]标记为 false 的属性,而屏蔽不可枚举属性的实例属性也会遍历到。
Object.keys()返回所有可枚举属性的字符串数组。
Object.getOwnPropertyNames()返回所有属性的数组,无论是否可以枚举

更多创建对象姿势

动态原型模式

1
2
3
4
5
6
7
8
9
function Person(name, age, job){ //属性
this.name = name; this.age = age;
this.job = job;
if (typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
};
}
}

原型对象方法sayName在第一次调用构造方法Person时执行。

寄生构造方法模式

1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, job){ 
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");

注意这种方式不能依赖 instanceof 操作符来确定对象类型

稳妥构造函数模式

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age, job){ //创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function(){
alert(name);
};
//返回对象
return o;
}
var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"

对象实例只能调用闭包内函数以返回name,而无法直接访问任何属性

原型链和继承

原型链

假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。

1
2
3
4
5
6
7
8
9
10
11
12
13
function SuperType(){ 
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){ }
SubType.prototype = new SuperType();//继承了 SuperType
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());//true

SubType 继承了 SuperType。实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于基类型的实例中的所有属性和方法,现在也存在于衍生类原型对象中了。
注意,所有引用类型的对象都继承了Object,这个继承也是通过原型链实现的。

![inherit](https://upload-images.jianshu.io/upload_images/3140250-998e96f2cdff75a3.PNG "inherit")

两个问题缺点:父类包含的引用类型属性,随实例共享到子类的所有实例,这可能并不是我们期望的;子类原型引用的是父类实例,不能向超类型的构造函数中传递参数(在ES6中可以在子类constructor中通过超类型super调用父类方法)。
子类原型对象的constructor指向父类构造方法

借用构造方法constructor stealing

1
2
3
4
5
6
7
8
9
10
11
12
function SuperType(){ 
this.colors = ["red", "blue", "green"];
}
function SubType(){
//继承了 SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);//"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors);//"red,blue,green"

传参

1
2
3
4
5
6
7
8
9
10
function SuperType(name){ 
this.name = name;
}
function SubType(){
SuperType.call(this, "Nicholas");//调用SuperType构造方法
this.age = 29; //实例属性
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age);//29

无法避免构造函数模式存在的问题——方法都在构造函数中定 义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是很少单独使用的。

组合构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function SuperType(name){ 
this.colors = ["red", "blue", "green"];
this.name=name;
}
SuperType.prototype.sayName=function(){alert(this.name)};

function SubType(name,age){
//继承了 SuperType
SuperType.call(this,name);
this.age=age;
}
SubType.prototype=new SuperType();
SubType.prototype.constructor=SubType;// 注@
SubType.prototype.sayAge=function(){alert(this.age)};

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);//"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors);//"red,blue,green"

在注@这一行,将原型对象的构造方法指向到SubType,因为之前该对象根据继承关系图示,作为SuperType实例,其[[prototype]]指向SuperType.prototype,故而SubType.prototype.constructor原先为SuperType方法。

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。而且,instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象。

原型式继承

浅复制方法

1
2
3
4
5
function object(o){ 
function F(){}
F.prototype = o;
return new F();
}

关于ES5 Object.create();

操作符instanceOf

1
2
3
4
5
6
7
8
9
10
11
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;// 取 R 的显示原型
L = L.__proto__;// 取 L 的隐式原型
while (true) {
if (L === null)
return false;
if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}

可迭代 (Iterable) 可枚举 (Enumerable)

可迭代决定了能不能用 for…of 循环遍历每一项。

其本质是对象内部定义了iterator 遍历时调用iterator.next()

原生可迭代对象:数组 (Array)、字符串 (String)、Map、Set、TypedArray、Arguments 对象

可枚举决定了能不能用 for…in 循环看到该属性。

js对象其属性包含描述符enumerable 当enumerable为true的可枚举 即可以for in、Object.keys()、 Object.values()、Object.entries()、 JSON.stringify()

1
2
3
4
5
6
7
// 定义不可枚举属性
Object.defineProperty(obj, 'secret', {
value: '123456',
enumerable: false,
writable: true,
configurable: true
});

编写方法时,考虑到不同的业务需求(如首次加载配置项js片段,甚至是为了兼容不同内核浏览器)需要用多个if语句加以区分,这将使每次调用都回进行判断,然而往往在具体的情景下是不必要的,比如已经以chrome载入了该页面。
一种思路是覆盖方法定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function addEvent (type, element, fun) {
if (element.addEventListener) {
addEvent = function (type, element, fun) {
element.addEventListener(type, fun, false);
}
}
else if(element.attachEvent){
addEvent = function (type, element, fun) {
element.attachEvent('on' + type, fun);
}
}
else{
addEvent = function (type, element, fun) {
element['on' + type] = fun;
}
}
return addEvent(type, element, fun);
}


另一种思想是将函数定义引用一个自执行匿名函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var addEvent = (function () {
if (document.addEventListener) {
return function (type, element, fun) {
element.addEventListener(type, fun, false);
}
}
else if (document.attachEvent) {
return function (type, element, fun) {
element.attachEvent('on' + type, fun);
}
}
else {
return function (type, element, fun) {
element['on' + type] = fun;
}
}
})();

第二种方式不会给第一次调用增加额外开销,而是在定义时进行了判断

剔除符号

1
str.replace(/[^0-9a-zA-Z]/g,"");

1
2
3
4
5
function palindrome(str) {
str = str.replace(/[^0-9a-zA-Z]/g,"");
str = str.toLowerCase();
return str==str.split("").reverse().join("");
}

指定内容行前注#
1
2
^(Proxy.+)$
#$1

$1为()内的内容

常见用法

regExp.test(target)

判断target是否符合正则表达式规则

target.match(regExp)

返回target字符串中符合正则表达式规则的部分

匹配语法

flag: g, i

g: global

i: case insensitive

集合[]

非和开头^

^在集合中表示非

比如上面剔除符号的用法

在集合外表示以…开头

频率 +, *, ?

symbol description
+ 出现1次或者多次
* 出现任意次
? 出现一次或者不出现
{n} 重复n次
{m, n} 重复m~n次

greedy matching vs lazy matching

贪心匹配(?)匹配最长的串,惰性匹配(?)匹配最小的串

1
2
3
4
5
6
7
8
9
10
let text = "<h1>Winter is coming</h1>";
let myRegex = /<.*>/;
let result = text.match(myRegex); // matching <h1>Winter is coming</h1>

myRegex = /<.*?>/;
result = text.match(myRegex); // matching <h1>

let article = "<h1>Winter is coming</h1><div></div><h1>north remembers</h1><h1>dragon is coming</h1><h1>the Queen is coming</h1>"
let titles = article.match(/<h1>.*?<\/h1>/g)

  • 思考:折中的需求

匹配符合下列规律的字符串,规律是连续的两个parameter,即某规则重复2次

1
2
3
<parameter key="xxx" value="1" />
<any-other-marks>
<parameter key="yyy" value="2" type="0"/>

可以这样
1
xmlInfo.match(/<parameter.*?\/>.*?<parameter.*?\/>/)

里面有重复了两段规则,见下文()部分

代码占位符

symbol description
\w 字符,空格,标点,下划线等 类似但不等价于[A-Za-z0-9_]
\W 非字符
\d 任意数字 相当于[0-9]
\D 任意非数字
\s 匹配任意不可见字符,包括空格、制表符、换页符等 相当于[ \f\n\r\t\v]
\S 匹配任意可见字符

lookaheads

暂时不知道如何翻译

例 密码规则不少于6位,且至少包含连续2位数字

1
2
let pwRegex = /(?=\w{6,})(?=\D*\d{2})/;
let result = pwRegex.test(sampleWord);

(?=\D*\d{2})应理解为任意非数字内容(或者不存在这部分内容)加两个数字
(?=…)检测包含…的字符串,(?!…)检测不包含…的字符串,只检测是否符合条件,不返回匹配片段

()

括号的本义是封装模式,封装的模式可以用\1 \2的方式代替
上面有个遗留问题,重复部分可写作

1
xmlInfo.match(/(<parameter.*?\/>).*?\1/)

上述代码存在问题

正/反向断言

  • (?=pattern):正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。
    非获取匹配,即符合该模式的匹配结果不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。
    不消耗字符,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

  • (?!pattern):正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。

  • (?<=pattern): 反向肯定预查,与正向肯定预查类似,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。

  • (?<!pattern):反向否定预查,与正向否定预查类似,只是方向相反。例如“(?<!95|98|NT|2000)Windows”能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”。
    从结果理解正向反向:匹配得到的“Windows”,如果对其接下来的内容有要求,正则表达式/Windows/要追加一个断言模式,如果对其之前的内容有要求,除了在/Windows/之前加断言模式,还要用“?<”表明这是反向断言

    写过的例子

  • 常见邮箱格式
    1
    /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/
  • 以,分隔的邮箱地址
    1
    /^(([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4}),)*(([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4}))$/
  • 获取文件扩展名

    1
    filename.match(/\.(\w+)$/);

    返回match的Array,取第一项为扩展名

  • 查找代码中所有的描述字符串(目的是做多语言替换)

    1
    (?<!from |require\(|: |console\.log\()'.+'

    即非空字符串且排除import from,require,以及console.log等语法

  • 图床地址https://tvax+数字+.sinaimg.cn 模式的字符串替换为 https://i0.wp.com/tvax+相同数字+.sinaimg.cn 的字符串
    捕获组

    1
    (https:\/\/tvax)(\d)(\.sinaimg\.cn)

    替换

    1
    https://i0.wp.com/tvax$2.sinaimg.cn
  • 查找项目对某个库任意api的引用

    1
    import\s*\{([^}]*)\}\s*from\s+['"]three['"]$

ES6

ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

基本数据类型

基本类型:string, number, boolean, null, undefined, symbol, bigint。它们存储的是值本身。
引用类型:Object。所有非基本类型都属于 Object 类型,包括 Array, Function, Date, Map, Set 等。它们存储的是对内存中一个对象的引用(地址)。

Map vs Object:

  • Map可使用任意类型作为键 Object只能用字符串和symbol
  • 查找键的时间复杂度是O(1)与Object相同 但Object有涉及原型、访问器属性历史包袱 Map在大规模数据下有性能上的稳定性(Object存在隐性查找模式切换)

模块化

export require是CommonJS规范的方法,es6 引入了import,可以将模块中的对象部分引入,以减少开销,或者使用import as objName from 引入文件所有对象
CommonJS vs ES Modules:
*nodejs是默认使用CommonJS的
,其表现可见express的服务中,如”module.exports = AssetService;”以及”var AssetService = require(‘./AssetService’)”。而es6中用”import common from ‘@myRepo/core’”和”export ..”
更多了解NodeJS Docs

Caution: the semicolon at the end of the import is mandatory!

1
2
3
4
/*util1.js*/
export default{
a:100
}
1
2
3
/*index.js*/
import util1 from './util1.js'
console.log(util1)

动态引入

1
2
3
4
5
6
import('/modules/my-module.js')
.then((module) => {
// Do something with the module.
});
// OR
let module = await import('/modules/my-module.js');

MDN:import
据说动态import的模块在打包时会自动分割,见React Docs: 代码分割

关于声明

>

let 代替 var. 同一变量不能重复使用let声明, 否则报错

const是常量,区别于只读

1
2
const s = [1];
s[0]=0;

但是可以用Object.freeze()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function freezeObj() {
"use strict";
const MATH_CONSTANTS = {
PI: 3.14
};
Object.freeze(MATH_CONSTANTS)
try {
MATH_CONSTANTS.PI = 99;
} catch( ex ) {
console.log(ex);
}
return MATH_CONSTANTS.PI;
}
const PI = freezeObj();

Object.assign()

>

将所有可枚举属性的值从一个或多个源对象复制到目标对象

Object.assign(target, …sources)

1
2
3
4
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source); // expected output: Object { a: 1, b: 4, c: 5 }

用法用途

  • 浅拷贝 cloneObj = Object.assign({}, …sources)
  • 覆盖目标同名属性
  • 合并数组 实际上是覆盖序号属性值 即Object.assign([1,2,3], [4,5]) // [4,5,3]

箭头函数表达式

1
2
3
const squaredIntegers = arr.filter(item=>{
return Math.floor(item)===item&&item>0 ;
}).map(item=>Math.pow(item, 2))

箭头表达式牵扯到的一个知识点是“箭头表达式不会创建this”
需知javascript this是区分运行环境的标记,见 阮一峰 JavaScript 的 this 原理,箭头表达式继承外层函数运行环境。

QQs按:创建/生成this的说法只是方便记忆 事实上 this 是运行时调用的东西 箭头函数没有自己的this绑定 其运行时this永远指向到定义箭头函数的所在(即所谓词法作用域)的this

关于省略,函数体只有一个return语句时,可省略return关键字和花括号

1
2
func = x => { return x+1;}
func = x => (x+1)

剩余参数运算符(Rest Operator)

使用Rest Operator定义参数数量可变的函数,参数将存入Rest Operator指定的变量指代的数组中

1
2
3
4
function howMany(...args) {
return "You have passed " + args.length + " arguments.";
}
console.log(howMany(0, 1, 2)); // You have passed 3 arguments
1
2
3
4
5
6
7
8
const sum = (function() {
"use strict";
return function sum(...args) {
//const args = [ x, y, z ];
return args.reduce((a, b) => a + b, 0);
};
})();
console.log(sum(1, 2, 3)); // 6

回顾下归并方法reduce,array.reduce(callback,[, initialValue]),callback接受以下参数:previousValue,前一次调用回调函数的返回值;currentValue当前处理的数组元素,currentIndex可选,当前数组元素下标;array可选,调用reduce()方法的数组。initialValue可选,作为第一次调用callback方法的previousValue参数

1
2
3
4
5
6
var arr = [6, 89, 3, 45];
var maximus = Math.max.apply(null, arr); // returns 89
maximus = Math.max(...arr); // returns 89

arr0 = [...arr]
console.log(arr0==arr) // false

异步归并方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const groupedMeshes = await meshes.reduce(async (accP, mesh, index) => {
const acc = await accP;
const group = isUpper(mesh) ? acc[0]: acc[1];
if(group.length==0){
group.push(mesh)
}else{
const newMesh = compute(mesh, group[0])
group.push(newMesh)
}
return Promise.resolve(acc)
}, Promise.resolve([
new Array<IMesh>(),
new Array<IMesh>()
]))

上述代码的需求是,将一个集合中的Mesh进行分组运算,分组依据是Upper or Lower,组内运算时以第一个Mesh为基准,调整组内其他Mesh

解构赋值(Destructuring Assignment)

1
2
var voxel = {x: 3.6, y: 7.4, z: 6.54 };
const { x, y, z } = voxel; // x = 3.6, y = 7.4, z = 6.54

或者将部分属性 voxel.x, voxel.y 存入指定变量 a, b

1
const { x : a, y : b} = voxel // a = 3.6, b = 7.4

分离多余的变量

1
2
let postData = Object.assign({ extraAttr:'extra' }, dto);
let { extraAttr, ...entity } = postData;

嵌套对象(nested objects)

1
2
3
4
5
6
7
8
9
10
11
const tree = {
text:'A',
value:0,
parentNode:{
text:'Root',
value:-1
}
}

const {parentNode:{value:RootVal}} = tree;
console.log(RootVal)

数组

1
2
3
4
const [a, b,,, c] = [1, 2, 3, 4, 5, 6];
console.log(a, b, c); // 1, 2, 5
// use rest operator
const [begining,...rest] = [1, 2, 3, 4, 5, 6];

交换值

1
[a, b] = [b, a];

函数传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const stats = {
max: 56.78,
standard_deviation: 4.34,
median: 34.54,
mode: 23.87,
min: -0.75,
average: 35.85
};
const half = (function() {
"use strict";
return function half({max,min}) {
return (max + min) / 2.0;
};
})();
console.log(half(stats)); // 28.015

函数返回多个值

1
2
3
4
func = () => {
return [1,2]
}
const [x, y] = func()

类似python中常用的返回多个结果,需要在调用函数时清楚的知道其返回的结果结构 实际上可能并不利于维护

ES9 剩余运算符可应用于解构语法

幂运算符

1
2
3
4
5
6
2**3
Math.pow(2,3)
// 右结合优先级
2**3**2 // 不等于8的平方64 而是512
Math.pow(2, Math.pow(3,2))
-2**3 // Error 语法歧义

Math函数扩展

常用的Math.sigh返回正负符号(-1,0,1) Math.trunc直接移除小数部分

字符串模块(Template Literals)

String.prototype.format

1
2
3
4
5
// source code
ƒ (){
var e=arguments;
return!!this&&this.replace(/\{(\d+)\}/g,function(t,n){return e[n]?e[n]:t})
}

`xxx${str}xxx`模板

1
2
3
resultDisplayArray = arr.map(item=>{
return `<li class="text-warning">${item}</li>`
});

标签函数

1
2
function tag(strings, ...keys){}
tag`string1${key1}string24{key2}`

将模板中的字符串和变量分别再拎出来

简化声明

1
2
3
4
5
6
const person = {
name: "Taylor",
sayHello: function() {
return `Hello! My name is ${this.name}.`;
}
};

可写作

1
2
3
4
5
6
const person = {
name: "Taylor",
sayHello() {
return `Hello! My name is ${this.name}.`;
}
};

class 以及 get set 以及super

1
2
3
4
5
6
7
8
9
10
11
12
13
class Book {
constructor(author) {
this._author = author;
}
// getter
get writer(){
return this._author;
}
// setter
set writer(updatedAuthor){
this._author = updatedAuthor;
}
}

Promise

>

区别于回调函数方式的异步编程方案, ES6将其写入语言标准。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

Promise对象的状态

pending(进行中)、fulfilled(已成功)和rejected(已失败)只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image();

image.onload = function() {
resolve(image);
};

image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};

image.src = url;
});
}

加载图片的异步过程,成功或失败时触发相应的事件(load,error),resolve和reject刚好对应作为事件响应方法

Promise 链式调用
Promise可以链式调用是因为then()返回也是一个Promise,见手写Promise 注意是then(onFulfilled, onRejected)如果缺省onFulfilled,则第一个Promise中的结果不做任何保留,意义在于过程,如果onFulfilled=(result)=>{ return newPromise(…)} 即将第一个Promise过程得到的result传入newPromise newPromise作为onFulfilled,resolve(newPromise)就将数据传递给了下一个过程,依次类推可以依次执行不同的异步操作

Promise().then(…)的第二个参数可以捕捉异常,和链式调用最后的catch有和区别?

catch只是一个语法糖而己 还是通过then 来处理的,大概如下所示

1
2
3
Promise.prototype.catch = function(fn){
return this.then(null,fn);
}

generator

以function*定义的生成器,与function的区别是除了return,其执行期间可以yield‘返回’多次

1
2
3
4
5
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}

斐波那契生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}

fib(5)返回一个generator对象console输出形如fib {[[GeneratorStatus]]: “suspended”, [[GeneratorReceiver]]: Window}
调用generator对象的next方法迭代

1
2
3
4
5
6
7
var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}

也可以var x of fib(5)进行迭代,x即value
generator似乎是为了状态管理而生的工具,所谓状态管理,因为其表象是一步接一步(next)来的,后面步骤的结果有可能需要依赖前面一步的状态(比如成功或失败,抑或current result)
async await是generator的语法糖

关于迭代器和可迭代对象
迭代器是使用next()方法实现迭代器协议(lterator protocal)的任意对象,如Array迭代器
可以使用for of迭代的对象即可迭代对象

Proxy 和 Reflect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const user = {
name:'QQs'
age:18
}

const proxyUser = new Proxy(user, {
set(target, property, value, receiver){
Reflect.set(target, property, value, receiver)
}
get(target, property, receiver){
return Reflect.get(target, property, receiver)
}
})
//
// Reflect 的 receiver 指向子对象

在ES6之前 属性get set添加响应式逻辑需要遍历原对象 调用Object.defineProperty定义getter setter

ES7

Array.protorype.includes

array.includes(x) 相当于 array.indexOf(x)

ES6中已添加了String.prototype.includes

类似的还有startsWith endsWith 参数接受偏移量

1
2
3
4
5
const str = 'Hello World'
str.include('W') // true
str.indexOf('W')>=0 // true
str.starsWith('World', 6)// true
str.indexOf('World')===6 //true

String.prototype.repeat

1
'*'.repeat(6) // ******

指数运算符 **

1
2
2 ** 2 // 4
2 ** 3 // 8

js引擎(V8)对**的实现与Math.pow的运算结果是会有差别的

ES8

字符串填充

以指定字符/字符串/函数方式, 在首或尾填充字符串至特定长度

1
2
3
str.padStart(targetLength [, padString])

str.padEnd(targetLength [, padString])

Object.values, Object.entries

获取对象属性值的集合

1
2
const obj = { x: 'xxx', y: 1 };
Object.values(obj); // ['xxx', 1]

其实现是for in,因此obj也可以是Array或string,其输出结果也是遍历逐个下标组成的集合。

获取对象键值对的集合

1
2
const obj = { x: 'xxx', y: 1 };
Object.entries(obj); // [['x', 'xxx'], ['y', 1]]

async, await

async(asynchronous)异步

await(asynchronous wait)等待异步结果
下面的代码结构被称作callback hell
其需求是从input.xml文件中读取action,如果action=token,则读取注册表,验证token时效,其中读文件,xml转json,读注册表均为异步操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(function(){
fs.readfile('input.xml',(err,data)=>{
if(err){
console.error(err)
}else{
regedit.list('HKCU\Software\QQSTEST\',(err,data)=>{
if(err){
console.error(err)
}else{
xmlParser.parseString(data,(err,data)=>{
if(err){
console.error(err)
}else{
done(data)
}
})
}
})
}
})
})()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function readfileAsync(filepath){
return new Promise((resolve,reject)=>{
fs.readFile(filepath,(err,data)=>{
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
(async function(){
var fileData = '';
await readfileAsync('input.xml).then(data=>{
fileData=data;
},err=>{
console.log(err)
})
if(!fileData) return;
await readRegEditAsync('')
})()

在async声明的function内使用await, 执行时等待promise对象的结果,完成其resolve或reject操作后向下执行。

Caution! 在forEach中加async await不能阻塞循环,事实上forEach回调函数无法跳出循环,不要在forEach里面使用async-await、break、return,在有相关需求的场景下使用for循环语法
Q160解析

见下文 ES 9 异步迭代

ES9

异步迭代

1
2
3
4
5
async function process(array) {
for await (let i of array) {
doSomething(i);
}
}

Promise.finally

1
2
3
iPromise.then(do_sth1).then(do_sth2)
.catch(err_handle)
.finally(complete_handle)

在正则表达式中命名模式匹配结果

模式指使用()封装的规则,在ES9中可以用(?\)的方式封装,在之后的js逻辑中,可以使用命名变量

1
2
3
4
// 将"年-月-日"改为"月-日-年"
const reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/,
d = '2018-04-30',
usDate = d.replace(reDate, '$<month>-$<day>-$<year>');

正则表达式断言和反向断言

正则表达式

正则表达式dotAll

正则表达式中点.匹配除回车外的任何单字符,标记s改变这种行为,使之匹配包含终止符的所有字符,例

1
2
/hello.world/.test('hello\nworld');  // false
/hello.world/s.test('hello\nworld'); // true

正则表达式 Unicode 转义

转义语法\p{…}

1
2
const reGreekSymbol = /\p{Script=Greek}/u;
reGreekSymbol.test('π'); // true

ES10

Ecmascript2025(ES16)

Array.prototype.flat, flatMap

可指定层次数(允许Infinity)迭代遍历,输出元素的集合 ———— 数组降维

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2); // [1, 2, 3, 4, 5, 6]

```js

flat自动移除空项

### Array.prototype.flatMap

如array.map(callback)一样遍历每个元素,之后,把运算结果“压平”成一个数组,QQs:不如改叫mapFlat

```js
[1,2,3].flatMap(i=>{return new Array(i).fill(i)})
// output: [1, 2, 2, 3, 3, 3]

String.prototype.trimStart, trimEnd

去除首/尾的空白字符

Object.prototype.fromEntries

是ES8 Object.entries 的反向操作

String.prototype.matchAll

返回匹配正则表达式的所有结果的集合

新增基本数据类型 BigInt

js基本数据类型(值类型)已不止5种(ES6之后是六种)!ES10后一共有七种基本数据类型,分别是: String、Number、Boolean、Null、Undefined、Symbol、BigInt

关于Symbol,生成唯一的值,用于不关心具体值(只需要用变量名区分即可)的场景 见知乎:JS 中的 Symbol 是什么?

装饰器Decorator

Angular常见的@Component(), @Module()等,在Ts中已有规范
ES6 提案 尚未标准化,js decorator或无法被浏览器适用,需使用babel打包并规范化
其实质是将定义的方法(装饰器名即函数签名)添加到紧随其后的class, function的propotype上

1
2
3
4
5
6
7
8
9
10
11
// 例如 mobx 中 @observer 的用法
/**
* 包装 react 组件
* @param target
*/
function observer(target) {
target.prototype.componentWillMount = function() {
targetCWM && targetCWM.call(this);
ReactMixin.componentWillMount.call(this);
};
}

关注木易杨每日面试题

Daily Interview Question

知乎专栏:flex
display: flex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style>
#box-container {
height: 500px;
display: flex;
}

#box-1 {
background-color: dodgerblue;
width: 50%;
height: 50%;
}

#box-2 {
background-color: orangered;
width: 50%;
height: 50%;
}
</style>
<div id="box-container">
<div id="box-1"></div>
<div id="box-2"></div>
</div>

flex-direction

value: row | column | row-reverse | column-reverse

justify-content

value: flex-start | flex-end | space-between | space-around
子元素不占满父元素时,在水平方向设置排列和空余的选项

align-items

value: flex-start | flex-end | space-between | space-around
子元素不占满父元素时,在垂直方向设置排列和空余的选项

align-content
value: flex-start | flex-end | center | strech | space-between | space-around

flex-wrap

value: nowrap | wrap | wrap-reverse
当子元素宽度之和超过弹性布局容器时,设置换行

flex-shrink

value:(number)
设置子元素在弹性布局容器中的宽度压缩比例

flex-grow

value:(number)
设置子元素在弹性布局容器中的高度拉伸比例

flex-basis

value:(px, em, %)
在进行flex-shrink和flex-grow前初始化子元素尺寸

缩写

1
2
3
4
5
flex: 1 0 10px;
// same as
flex-grow: 1;
flex-shrink: 0;
flex-basis: 10px;

order
value:(number)
次序

align-self

value:auto | flex-start | flex-end | center | baseline | stretch | inherit
设置在各子元素与父元素的align-item作用对应并覆盖align-item效果

从原理出发

标记为 display:flex | inline-flex 的块状元素(即flex container)获得控制其子元素(flex item)宽高或顺序的能力

子元素沿main axis均匀排布(just-content)沿cross axis调整对齐(align-item)
main axis未必是水平的 由flex-direction控制

  • just-content控制沿主轴的排布


所有子元素都沿主轴排布 所以是调整‘content’

  • align-item控制cross axis方向上的对齐


每个子元素在其侧轴上 与其他子元素对其 所以是对其‘item’

  • align-content
    顾名思义 对其所有元素 应用于 超出一行 且未占满整个container的情形

Angular Doc: 路由
在AppComponent初始化登录服务,其中有request发送到后台,报401,有HttpInterceptor拦截401,且有RoutGuard校验登录成功后的数据,该场景下HttpInterceptor先生效,因token失效而做重定向,然后RoutGuard生效,二者依据有所区别,固行为有所冲突。现workaround是避免在初始化Angular App或初始化登录模块时调用有可能被HttpInterceptor拦截的接口

查询列表与详情页的路由跳转

从指定查询条件的列表,路由到详情页,以及从详情页返回原查询结果页
list component

1
this.router.navigate([`/asset/list/detail/${dataItem.asset_id}`], {queryParams: this.s})

detail component
1
2
3
4
5
6
7
8
9
10
queryParam:any
// init
this.route.paramMap.subscribe(params => {
const id = +params.get('id');
this.getAsssetById(id);
});
this.route.queryParamMap.subscribe(queryParam=>this.queryParams=queryParam)

// return
this.router.navigate(['../'], {queryParams: this.queryParams});

Hash:false

去掉url的#

useHash:false的情况下navigateByUrl完整的url出现404,相对路由路径正常

apache配置

1
a2enmod rewrite

出于复用需要的函数封装

典型的函数库Jquery

匿名函数

1
2
3
function(){

}

匿名函数使用

1
2
3
4
func1=function(){
console.log(1);
}
func1();

考点:下面的代码输出结果
1
2
3
var func=function a(){};
console.log(func);
console.log(a);

答案:function undefined
这个叫具名函数表达式
变量func引用函数体,虽然该函数具有函数名a 实际上 函数名a仅在其函数体内部有效

自执行函数
1
2
3
(function(){
console.log(1);
})()

function外面的括号()消除歧义1

使用自执行函数创建命名空间

1
2
3
4
5
6
7
var qqsNamespace = function(){
var func1 = function(parameter1,parameter2){
console.log('func1');
}
return {action:func1}
}();
qqsNamespace.action(1,2);

所谓创建命名空间,模仿了块级作用域,调用匿名函数生成一个对象Lib,对象Lib内部定义了自己的方法,使用时根据需要从生成的对象Lib中调用

JQuery