JS p38~p94

String

js 中的字符串和 java 字符串都是不可变的,不同点是: java 里的字符串,如果它不是 new 出来的,会被放在常量池里,而 js 里的字符串都是放在堆中。

var str = 'abc';
str[0] = 'x';
str.length = 0;
console.log(str.length); // 3
console.log(str[0]); // a

模板字面量标签函数(ES6)

什么是“模板字面量” ?(用过但不知道叫啥…)

var str = `abc${'d'}`;
console.log(str); // abcd
var a = { 
    toString() { return '哇哈哈'; }
}
console.log(`我爱喝${a}`); // 我爱喝哇哈哈

模板字面量标签函数

function simpleTag(strings, aValExpression, bValExpression, sumExpression) { 
    console.log(typeof strings) // object
    console.log(...arguments); // [ 'gap1', 'gap2', 'gap3', 'gap4' ] 1 2 3
    console.log(strings.raw) // [ 'gap1', 'gap2', 'gap3', 'gap4' ]
    return sumExpression;
}
var a = 1, b = 2;
var result = simpleTag`gap1${a}gap2${b}gap3${a + b}gap4`;
console.log(result); // 3

String.raw(ES6 原始字符串)

String.raw 是模板字面量标签函数的应用,它不会对要转义的字符进行转义,而是保留原始值。

console.log(`\u00A9`); // ©
console.log(String.raw`\u00A9`); // \u00A9

Symbol.for (全局注册表)

Symbol.for 函数使用一个字符串作为建生成一个符号并注册在全局中,如果再次使用相同的建生成符号,会在全局中找到之前使用这个建生成的符号并返回。

let symbol1 = Symbol('a');
let symbol2 = Symbol('a');
console.log(symbol1 === symbol2); // false

// 全局符号注册表 Symbol.for
let symbol3 = Symbol.for('a');
let symbol4 = Symbol.for('a');
console.log(symbol3 === symbol4); // true

// 获取符号的“键”
let s3key = Symbol.keyFor(symbol3);
console.log(typeof s3key); // string
console.log(s3key); // a

使用符号作为属性

可以使用 Symbol 函数生成的字符串作为对象的键,如 Symbol(‘a’),这时键为 “Symbol(a)”

const a = { 
    'a': 1, 
    'b': 2,
    [Symbol('a')]: 3, // 因这里的 Symbol(x) 生成的建都是字符串!
    [Symbol('b')]: 4
};
console.log(Object.getOwnPropertyNames(a)); // [ 'a', 'b' ]
console.log(Object.getOwnPropertySymbols(a)); // [ Symbol(a), Symbol(b) ]
/**
 * {
  a: { value: 1, writable: true, enumerable: true, configurable: true },
  b: { value: 2, writable: true, enumerable: true, configurable: true },
  [Symbol(a)]: { value: 3, writable: true, enumerable: true, configurable: true },
  [Symbol(b)]: { value: 4, writable: true, enumerable: true, configurable: true }
}
 */
console.log(Object.getOwnPropertyDescriptors(a));
console.log(Reflect.ownKeys(a)); // [ 'a', 'b', Symbol(a), Symbol(b) ]

生成器函数

生成器函数并不会阻塞主线程的运行,只有当 yield 有了结果(即执行一次 next()后,yield就会有结果),才会执行下一步代码。这里其实是一个语法糖,其实还是将下一步代码放到回调里去执行。

require('colors');
/**
 * 生成器函数
 */
function* generator() {
    console.log('开始执行 *generator 的代码'.yellow)
    yield(console.log('到达第 1 个 yield'), 1);
    yield(console.log('到达第 2 个 yield'), 2);
    yield(console.log('到达第 3 个 yield'), 3);
    console.log('*generator 的代码执行结束'.yellow)
}
const gen = generator();
console.log('得到的生成器对象:', gen);
console.log('\r\n执行第 1 个 next 方法');
console.log('第 1 个 next 方法的返回结果:', gen.next());
console.log('\r\n执行第 2 个 next 方法');
console.log('第 2 个 next 方法的返回结果:', gen.next());
console.log('\r\n执行第 3 个 next 方法');
console.log('第 3 个 next 方法的返回结果:', gen.next());
console.log('\r\n执行第 4 个 next 方法');
console.log('第 4 个 next 方法的返回结果:', gen.next());

在这里插入图片描述
其中, next 方法可以传参,这个参数会替换为上一个 yield 的结果值!改一下上面的代码,实践一下。

require('colors');
/**
 * 生成器函数
 */
function* generator(x) {
    console.log('开始执行 *generator 的代码'.yellow)
    const v1 = yield(console.log('到达第 1 个 yield'), 1 + x);
    const v2 = yield(console.log('到达第 2 个 yield'), 2 + v1);
    const v3 = yield(console.log('到达第 3 个 yield'), 3 + v2);
    console.log(v3);
    console.log('*generator 的代码执行结束'.yellow)
}
const gen = generator();
console.log('得到的生成器对象:', gen);
console.log('\r\n执行第 1 个 next 方法');
console.log('第 1 个 next 方法的返回结果:', gen.next(4));
console.log('\r\n执行第 2 个 next 方法');
console.log('第 2 个 next 方法的返回结果:', gen.next(3));
console.log('\r\n执行第 3 个 next 方法');
console.log('第 3 个 next 方法的返回结果:', gen.next(2));
console.log('\r\n执行第 4 个 next 方法');
console.log('第 4 个 next 方法的返回结果:', gen.next(1));

在这里插入图片描述
第 1 个 next 方法传参无效,在上面这个例子中,应该要向生成器函数传参,来实现修改第 1 个 yield 的结果值!

异步迭代器(Symbol.asyncIterator)(ES9/ES2018)

异步迭代器为 for-await-of 服务,我们迭代某个可迭代对象的时候,其实就是在调用 next() 方法,它可以显示地调用,也可以隐式的调用,下面是一个隐式调用。

class Iterator {
    constructor(len = 5) {
        this.length = len;
    }
    // 异步迭代生成器函数
    async *[Symbol.asyncIterator]() {
        while (this.length) {
            yield new Promise(resolve => {
                setTimeout(() => {
                    resolve(--this.length)
                }, 1000);
            })
        }
    }
}
async function run() {
    const iterator = new Iterator();
    for await (const item of iterator) {
        console.log(item);
    }
}
run(); // 4 3 2 1 0

Symbol.hasInstance

在 ES6 中,instanceof 操作符会使用 Symbol.hasInanceof 来判读实例所属关系。但是它的操作数位置和 instanceof 的操作数位置相反。

class Parent {}
class Child extends Parent {
    static[Symbol.hasInstance]() {
        return false;
    }
}
const p = new Parent();
const c = new Child();
console.log(p instanceof Parent); // true
console.log(c instanceof Parent); // true
console.log(c instanceof Child); // falses
console.log(Parent[Symbol.hasInstance](p)); // true
console.log(Parent[Symbol.hasInstance](c)); // true
console.log(Child[Symbol.hasInstance](c)); // false

Symbol.iterator

使用 for-of 的时候,如果定义了同步迭代器 Symbol.iterator,那么就会使用它。

class Emitter {
    constructor(max = 5) {
        this.max = max;
        this.index = 0;
    };
    * [Symbol.iterator]() { 
        while (this.index < this.max) {
            yield this.index++;
        }
    }
}
const emitter = new Emitter();
for (const iterator of emitter) {
    console.log(iterator); // 0 1 2 3 4
}

Symbol.toStringTag

除了直接修改对象实例的 toString方法外,这个方法也可以重写实例对象的 toString 方法,但是它的优先级没有直接修改 toString 方法高。注意:这个方法只能在对象实例上添加,添加到它的原型对象上或构造函数上是不生效的!

class Test {
    constructor() { 
        this[Symbol.toStringTag] = 'Module';
    }
}

const test = new Test();
console.log(test); // Test { [Symbol(Symbol.toStringTag)]: 'Module' }
console.log(test.toString()); // [object Module]

测试优先级。

class Test {
    constructor() { 
        this.toString = () => 'MyModule';
        this[Symbol.toStringTag] = 'Module';
    }
}

const test = new Test();
console.log(test); // Test { toString: [Function (anonymous)], [Symbol(Symbol.toStringTag)]: 'Module' }
console.log(test.toString()); // MyModule

关于 Symbol 上的函数还有很多,上面是常见的几个。

后缀递增递减

let a = 1;
const b = a++ + 0;
console.log(b); // 1
console.log(a); // 2

位操作之位移

MCMAScript 中的所有数值都以 IEEE 754 64 位格式存储(另外它还有 32 位的格式),但是前边儿又说浮点数占用的存储空间是整数的两倍(暂时标记一下,后面再找答案…)

位操作并不直接应用到 64 位,而是先把值转为 32 位整数,然后再进行位操作!

算术位移

算术位移指的是保留符号位进行左移或右移,即符号位固定不移动,所以位移后数值的正负是不变的。

-2 >> 1 = -1
在这里插入图片描述
得到 -1 后,再右移会发现得到的还是 -1 。

逻辑位移

逻辑位移跟算术位移的区别就是逻辑位移会移动符号位,可能导致符号位会丢失。

  • 正数无符号右移,结果对符号位无影响;
  • 负数无符号右移,逻辑右移高位补 0 ,结果值会变成正数,对结果有影响;
  • 假如有正负数无符号左移,则符号位会变成紧靠它右边的那一位,对结果可能都会有影响。

为什么没有 “无符号左移” 而有 “无符号右移” ?

对正数和负数进行无符号左移,可能都会改变其符号,从而得到不正确的数值;
负数无符号右移虽然得不到正确的数值,但它可以用来做哈希运算和加密等等。

布尔操作符

console.log(!NaN); // true
console.log(!!NaN); // false

关系操作符

// 字符串与字符串比较 "abc" >= "ABC" true
console.log('字符串与字符串比较 "abc" >= "ABC"', 'abc'>='ABC');

// 数值与数值比较 1 >= 1  true
console.log('数值与数值比较 1 >= 1 ', 1>=1);
// 数值与布尔比较 1 > false true 
console.log('数值与布尔比较 1 > false', 1>false);
// 数值与对象比较 1 >= {} false
console.log('数值与对象比较 1 >= {}', 1>={});
// 数值与数组比较 1 >= [] true
console.log('数值与数组比较 1 >= []', 1>=[]);
// 数值与日期比较 1 >= new Date() false
console.log('数值与日期比较 1 >= new Date()', 1>=new Date());
// 数值与数值字符串比较 1 >= "1" true
console.log('数值与数值字符串比较 1 >= "1"', 1>='1');
// 数值与非数字字符串比较 1 >= "a" false
console.log('数值与非数字字符串比较 1 >= "a"', 1>='a');
// 数值与 undefined 比较 1 >= undefined false
console.log('数值与 undefined 比较 1 >= undefined', 1 >= undefined);
// 数值与 null 比较 1 >= null true
console.log('数值与 null 比较 1 >= null', 1 >= null);

// 对象与布尔比较 {valueOf() {return 0}} <= true true
console.log('对象与布尔比较 {valueOf() {return 0}} <= true', {valueOf() {return 0}} <= true);
// 对象与布尔比较 {toString() {return "0"}} <= true true
console.log('对象与布尔比较 {toString() {return "0"}} <= true', {toString() {return '0'}} <= true);
// 对象与数值比较 1 >= {valueOf() {return 0}} true
console.log('对象与数值比较 1 >= {valueOf() {return 0}}', 1 >= {valueOf() {return 0}});
// 对象与数值比较
console.log('对象与数值比较 1 >= {toString() {return "0"}}', 1 >= {toString() {return '0'}});
// 对象与对象比较 {valueOf() {return 0}} < {toString() {return "1"}} true
console.log('对象与对象比较 {valueOf() {return 0}} < {toString() {return "1"}}', {valueOf() {return 0}} < {toString() {return "1"}})

总结:

  • 如果两个操作数都是字符串,则逐个比较字符序列对应的编码,如果相等则继续比较下一个;
  • 如果两个操作数都是数值,则直接进行比较;
  • 如果操作数有一个是数值,则将另一个操作数也转为数值;
  • 如果操作数有一个是布尔值,则将布尔值转为数值后按前面的规则比较,即都变为数值后比较;
  • 如果操作数有一个是对象,则先调用 valueOf 方法,取结果后根据以上规则比较,如果没有 valueOf 方法,则调用 toString 方法,取结果后根据以上规则比较。

备注:这里转为数字的操作其实是调用了 Number 方法。

标签语句

goto 语句有些类型,不过这里是用来跳出或跳过循环的,配合 continuebreak 使用。

let num = 0;
xxxx: for (var i = 0; i < 10; i++) {
    if (i === 5) {
        break xxxx;
    }
    num++;
}
console.log(num); // 5
let sum = 0;
yyy: for (var i = 0; i < 11; i++) {
    for (let j = 0; j < 10; j++) {
        sum++;
        if (j % 5 === 0) {
            continue yyy;
        }
    }
}
console.log(sum); // 11

switch case

switch(true) {
    case 1<2: console.log('1<2'); // 1<2
    case false: console.log('false'); break; // false
    case true: console.log('true');
}

let i = 0;
switch(i) {
    case 0+0: console.log('0+0'); // 0+0
    case 2: console.log('2'); // 2
}

case 后面可以跟语句,当某个条件匹配后需要使用 break ,否则会得到不正确的结果!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值