块内声明函数

块内声明函数,是指

if(x) {
    function foo() {}
}

虽然很多 JS 引擎支持这种写法,但它并不属于 ECMAScript 规范,严格模式下也会报错

ECMAScript 只允许在全局作用域下或函数中声明函数

如果一定要在块内声明函数,可以

if(x) {
    var foo = function() {}
}

function *

funciton 跟一个 *, 这种语法会定义一个生成器函数

根据 MDN

  • 调用一个生成器函数会返回一个 迭代器对象
  • 迭代器对象每次调用 next() 方法,都会执行函数内到下一个 yield 位置为止的语句
  • next() 方法返回一个对象,包含两个属性 value 和 done,value 表示本次 yield 返回的值,done 为布尔类型,表示生成器后续是否还有 yield
function* idMaker(){
  var index = 0;
  while(index<3)
    yield index++;
}

var gen = idMaker();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // undefined
  • 调用 next() 方法时,如果传入了参数,那么此参数会传给上一条执行的 yield 语句左边的变量。比如
function *gen(){
    yield 10;
    x=yield 'foo';
    yield x;
}

var gen_obj=gen();
console.log(gen_obj.next());// 执行 yield 10,返回 10
console.log(gen_obj.next());// 执行 yield 'foo',返回 'foo'
console.log(gen_obj.next(100));// 将 100 赋给上一条 yield 'foo' 的左值,即执行 x=100,返回 100
console.log(gen_obj.next());// 执行完毕,value 为 undefined,done 为 true

根据知乎,yield 有让出,产出之意。在我看来,有 yield 语句的函数就像一个播放器。可以 pause,可以 play。

正则表达式的 \1

遇到一道题

var str = "Hellllo world";
str = str.replace(/(l)\1/g, '$1');

查了一下,\1 表示第 1 个括号匹配的内容,\2 表示第 2 个括号匹配的内容,依此类推

比如上面代码,相当于 replace(''/(l)l/g', '$1'),也即每匹配 ll,换成 l

所以结果是

"hello world"

undefined 与 Uncaught Reference Error

一直总错误预测 console.log(不存在的变量) 的执行结果

如下代码

(function() {
      var a = 5;
  })();   
console.log(a);

执行后不是 undefined, 而是 Uncaught Reference Error

一般而言:打印不存在的变量是报错,打印对象不存在的属性是 undefined

注意:打印非对象的属性也是报错,比如 var str = '1111', console.log(str.xx)

null, undefined, false 的 == 关系

null == undefined // true
null == false // false
undefined == false // false

注意到,有 false 的比较式结果为 false

此外,我试了一下五个 falsy 值与 false 的 == 比较运算。发现数字和字符串还是成立的

0 == false // true
'' == false // true

所以:false == falsy 只在 falsy 为数字或字符串类型时成立

不得不说这些题真是坑,虽然花时间多记总能弄懂。但是直接上 === 不好吗?总是考这些平时都不常用的

select 值的获取

如下

<form name="a">
    <select name="a" size="1" id="obj">
    <option value="a">1</option>
    <option value="b">2</option>
    <option value="c">3</option>
</select>

对 select 元素 obj,有以下 api 获取选中值信息

obj.value // a or b or c
obj.selectedIndex // 0 or 1 or 2
obj.options // [option元素, option元素, option元素]

// 获取选中元素的值
obj.value

// 获取选中元素的文本
obj.options[obj.selectedIndex].text

let 的变量提升

有一道题

let foo = () => {
    console.log(x);
    let x = 20;
}
foo();

按照通常的想法,执行顺序应该是

let x
console.log(x)
x = 20
// undefined

但实际是

VM1772:3 Uncaught ReferenceError: Cannot access 'x' before initialization

查了一下,在这里找到答案

所有变量都有创建,初始化和赋值过程

变量必须先创建再初始化,先初始化再赋值

var x 语句:创建变量 x 并初始化为 undefined

let x 语句:创建变量 x,未初始化

let x = 20 语句:创建变量 x 并初始化为 20

所谓变量提升,对 var 是提升创建和初始化对 let 是提升创建

总的来说,就是:

var x
console.log(x) // ok

let y
console.log(y) // 报错:因为未初始化

forEach 遍历机制2

看一段代码

let array = [,1,,2,,3];
array = array.map((i, j) => j) // 输出什么?

首先,根据之前的博客, forEach 遍历是按成员遍历不是按长度遍历。map 也一样。可以确定,遍历只有 3 次

其次,j 是下标。不确定性在于 1, 2, 3 的下标究竟是 0, 1, 2,还是 1, 3, 5 ?

试着在控制台打印一下

Object.keys(array)
// [1, 3, 5]

因此,结果是 1, 3, 5

这说明,含空的元素数组,成员下标严格按照出现位置

细节

  • 字符串也有 concat 方法表示连接,但是没有 append, appendTo, push, pop
  • 一个有效变量名可以以字母,下划线开头,不能以数字开头
  • 数组的 sort 方法会改变原数组,只要它执行

原地算法

刷力扣的时候,遇到一道题删除排序数组的重复项

里面提到原地算法的概念。所谓原地算法,就是不依赖额外空间或者依赖少数额外空间,仅依靠输出来覆盖输入的一种算法操作。

以前去除数组的重复项,我会用哈希算法。两次遍历,多占用大约 2 倍数组空间(1 倍建表,1 倍构造新数组)

现在学到了一招,就是在数组有序的情况下去重,可用原地算法实现

具体做法就是 双指针法。定义两个指针,一个指向当前元素,一个指向最新元素。在一次遍历覆盖更新当前元素