ECMA Script 6

文章目录


JavaScript 包含了 ECMAScript、DOM、BOM。

DOM (Document Object Model)

DOM 浏览器特有。

  • 定义: DOM 是一种编程接口,用于访问和操作网页的内容和结构。它将网页的内容表示为一个树状结构,使得 JavaScript 可以动态地访问和更新网页内容、结构和样式。
  • 主要功能:
    • 节点: 通过 DOM,网页的每个部分(如元素、属性和文本)都可以视为一个节点。
    • 节点操作: JavaScript 可以创建、修改、删除和替换这些节点。
    • 事件处理: 通过 DOM,可以为网页中的事件(如点击、输入、加载等)添加事件监听器。

BOM (Browser Object Model)

  • 定义: BOM 是用于与浏览器窗口及其功能进行交互的一组对象和方法。它允许 JavaScript 访问和操作浏览器窗口、文档、历史记录、导航、位置等。
  • 主要功能:
    • window 对象: 代表浏览器窗口,提供了访问浏览器属性和方法的能力,例如 alert(), confirm(), prompt(), setTimeout(), clearTimeout() 等。
    • navigator 对象: 提供关于浏览器的信息,如浏览器版本和用户代理。
    • screen 对象: 提供关于显示屏的分辨率和尺寸的信息。
    • history 对象: 允许你访问和操作浏览器的历史记录。
    • location 对象: 允许你访问和修改浏览器的 URL。

ECMA Script 6


let 和 const 命令

{
    let a = 10;   //let声明只在其所在的代码块有效
    var b = 1;
}
a
b

//run:
a
^

ReferenceError: a is not defined
for(let i = 0; i < 10; i++){}   //for循环中的局部变量 let i

console.log(i)

//run:
console.log(i)
            ^

ReferenceError: i is not defined
var a = []
for (var i = 0; i < 10; i++) {    //var i 全局变量
	a[i] = function() {
		console.log(i);
	}
}
a[6]();   //10
var a = []
for (let i = 0; i < 10; i++) {    //let i 全局变量
	a[i] = function() {
		console.log(i);
	}
}
a[6]();  //6

变量提升

  • var 存在变量提升。
  • let 不存在。
console.log(a);    //变量一定要在声明后使用
let a = 2;

//run:
console.log(a);
            ^

ReferenceError: Cannot access 'a' before initialization
console.log(a);
var a = 2;

//run:
undefined

暂时性死区

var tmp = 123;

if (true) {
	tmp = "abc";
	let tmp;    //let const 封闭作用域
}

//run:
        tmp = "abc";
            ^

ReferenceError: Cannot access 'tmp' before initialization
if (true){
	//暂时性死区 TDZ 开始
	tmp = 'abc';
	console.log(tmp);
	
	let tmp; //TDZ temporal dead zone 结束
	console.log(tmp);  //undefined
	tmp = 123;
	console.log(tmp); //123
}

//run:
D:\jsPro\work\test.js:3
        tmp = 'abc';
            ^

ReferenceError: Cannot access 'tmp' before initialization
function bar(x = y, y = 2) {    //(x = 2, y = x)
    return [x,y];
}
bar();

//run:
function bar(x = y, y = 2) {
                 ^

ReferenceError: Cannot access 'y' before initialization

变量不能重复声明

function() {
    let a = 10;
    var a = ;  //error
}

function() {
    let a = 10;
    let a = 1;  //error
}

function func(arg) {
    let arg; //error
}

function func(arg) {
    {    //局部作用域
        let arg;   //correct
    }
}

块级作用域

function f1() {
    let n = 5;
    if (true) {
        let n = 10;
    }
    console.log(n)   //5
}
{
    let a = 'secret';
    function f() {
		console.log("inner")
        return a;
    }
}
f()

//run:
PS D:\jsPro\work> node test.js
inner
//块级作用域外可以调用块级作用域内的函数,ES6 书里面写的这里会报错
let f;
{
    let a = 'secret';
    f = function () {
        return a;
    }
}
console.log(f());

//run:
PS D:\jsPro\work> node test.js
secret

const

const PI = 3.14;  //常量值不可改变,声明时必须初始化,只在声明的块级作用域以内有效,不存在变量提升,存在暂时性死区,只能在声明后使用。
const foo = {};    //foo是常量,指向的地址不变,但里面的数据可变,对象声明为常量需要小心。

foo.prop = 123;
const a = [];
a.push("Hello");
const a = [];
a.push("Hello");
a.length = 0;
a = ["Dave"];

//run:
a = ["Dave"];
  ^

TypeError: Assignment to constant variable.
var a = [];
var b = [];

b.push("hello");
b.push("world")

console.log(b)

a = b

console.log(a)

//run:
PS D:\jsPro\work> node test.js
[ 'hello', 'world' ]
[ 'hello', 'world' ]

Object.freeze方法

const foo = Object.freeze({});

foo.prop = 123;   //不会添加新属性

console.log(foo)

//run:
PS D:\jsPro\work> node test.js
{}
//将一个对象彻底冻结
var constantize = (obj) => {
	Object.freeze(obj);
	Object.keys(obj).forEach( (key, value) => {
		if (typeof obj[key] === 'object') {
			constantize(obj[key]);
		}
	})
}

Object.freeze() 只会冻结对象的第一层。如果对象的属性值也是对象(嵌套对象),这些嵌套对象不会被自动冻结。

var constantize = (obj) => {
    Object.freeze(obj); // 冻结对象,防止对其进行任何修改
    Object.keys(obj).forEach((key) => { // 遍历对象的所有键
        if (typeof obj[key] === 'object') { // 如果值是对象
            constantize(obj[key]); // 递归调用 constantize 函数
        }
    });
}

// 创建一个包含嵌套对象的示例对象
const myObject = {
    name: 'John',
    age: 30,
    address: {
        city: 'New York',
        zip: '10001'
    },
    hobbies: ['reading', 'gaming']
};

// 调用 constantize 函数
constantize(myObject);

// 尝试修改对象(将会失败,因为对象已经被冻结)
myObject.name = 'Jane'; // 无效
myObject.address.city = 'Los Angeles'; // 无效
myObject.hobbies.push('swimming'); // 无效

console.log(myObject);

run:

myObject.hobbies.push('swimming'); // 无效
                 ^

TypeError: Cannot add property 2, object is not extensible

这个错误说明 myObject.hobbies 数组已经被冻结,无法对其进行修改(如添加新元素)。

跨模块常量

export const A = 1;
export const B = 2;
export const C = 3;

全局对象的属性

全局对象是最顶层的对象。浏览器环境中指的是 window对象。node.js 中指 global 对象。

var 命令和 function 命令声明的全局变量依旧是全局对象的属性;let 命令、const 命令 和 class 命令声明的全局变量不属于全局对象的属性。

var a = 1;
//node.js 中可以写成 global.a 或者写成 this.a
window.a
var a = 1;
console.log(a)   //1

this.a = 2
console.log(this.a, a)   //2 1

window.a = 3
console.log(window.a, this.a, a)    //ReferenceError: window is not defined
//nodejs 环境中没有 window 对象

run:

PS D:\jsPro\work> node test.js
1
2 1
D:\jsPro\work\test.js:7
window.a = 3
^

ReferenceError: window is not defined

变量的结构赋值

数组结构赋值

var [a, b, c] = [1, 2, 3];

console.log(a,b,c) //1, 2, 3

嵌套解构赋值

let [foo, [[bar], baz]] = [1,[[2], 3]];

console.log(a,b,c) //1, 2, 3
let [ , , third] = [1,2,3]
console.log(third)  //3
let [x, ,y] = [1,2,3];
console.log(x, y);   //1 3
let [head, ...tail] = [1,2,3,4,5];
console.log(head, tail)

//run:
1 [ 2, 3, 4, 5 ]
let [x, y, ...z] = ['a'];
console.log(x, y, z)

//run:
a undefined []

不完全解构

let [x, y] = [1,2,3];
console.log(x, y) //1 2
let [a, [b], d] = [1, [2,3],4];
console.log(a,b,d) //1 2 4
var [x, y, z] = new Set(['a', 'b', 'c']);
console.log(x, y, z);   //a b c
function* fibs() {
    var a = 0;
    var b = 1;
    while (true) {
        yield a;
        [a,b] = [b, a + b];
    }
}

var [a, b, c, d, e, f, g] = fibs();

console.log(a, b, c, d, e, f, g)  //斐波那契数列

function\* fibs():

  • 定义了一个生成器函数 fibs,它会生成斐波那契数列的数字。

yield a;:

  • yield 关键字会暂停函数执行,并返回当前的 a 值。每次调用 next() 时,函数会从这里继续执行。

[a, b] = [b, a + b];:

  • 使用解构赋值更新 ab 的值。a 被更新为 bb 被更新为 a + b,这是生成下一个斐波那契数所需的操作。

解构赋值默认值

var [foo = true] = [];
console.log(foo)
"use strict";
var [x, y = 'b'] = ['a'];
console.log(x, y) //a b
var [m, n = 'b'] = ['a', undefined];
console.log(m, n) //a b
var [x = 1] = [undefined];
console.log(x)  //x = 1
var [x = 1] = [null];
console.log(x)  // x = null   , null不严格等于undefined,默认值不会生效
function f() {
    console.log('aaa');
}

let [x = f()] = [1];    //默认值是表达式,惰性求值,只有在用到的时候求值

相当于:

let x;
if ([1][0] === undefined) {
    x = f();
} else {
    x = [1][0]
}

console.log(x)
function f() {
    console.log("aaa");
}

var [x = f()] = [undefined];    //aaa
function f() {
    return "aaa";
}

let arr = [1];
let x = arr[0] === undefined ? f() : arr[0];
console.log(x)   //1
function f() {
    return "aaa";
}

let arr = [undefined];
let x = arr[0] === undefined ? f() : arr[0];
console.log(x)   //aaa

对象解构赋值

var {foo, bar} = {foo: "aaa", bar: "bbb"};
console.log(foo, bar);  //aaa bbb
var {bar, foo} = {foo: "aaa", bar: "bbb"};
console.log(foo, bar);  //aaa bbb         和次序无关,按属性名赋值
var {baz} = {foo: "aaa", bar: "bbb"};
baz //undefined
var {foo: baz} = {foo: "aaa", bar: "bbb"};
console.log(baz)   // foo的属性值赋值给新变量 baz
let obj = { first: "hello", last: "world"};
let {first: f, last: l} = obj;
console.log(f, l)  //hello world
var {foo: foo, bar: bar} = {foo: "aaa", bar: "bbb"};   //完整写法
var {foo, bar} = {foo: "aaa", bar: "bbb"};   //省略属性名写法
let foo;
({foo} = {foo: 1});
console.log(foo)   //1
let baz;
({bar:baz} = {bar: 1})
console.log(baz)   //1

嵌套结构的对象

var obj = {
    p: [
        "hello",
        {
            y: "World"
        }
    ]
};
var {p: [x, {y}]} = obj;
console.log(x, y)

{ } 和 [ ]

对象使用 {} 来定义,表示键值对的集合。用于存储和组织数据,例如属性和方法。

const person = {
    name: 'Alice',
    age: 30,
    address: {
        city: 'Wonderland',
        postalCode: '12345'
    }
};

console.log(person.address.city); // 输出 'Wonderland'

数组使用 [] 来定义,表示有序的元素集合。用于存储列表或序列的数据,例如索引元素的集合。

const numbers = [1, 2, 3, [4, 5, [6, 7]]];

console.log(numbers[3][2][1]); // 输出 7
var node = {
    loc: {
        start: {
            line: 1,
            column: 5
        }
    }
};

var {loc: {start: {line}}} = node;
console.log(line)  //1
console.log(loc)   //loc是模式
console.log(start) //start是模式
let obj = {};
let arr = [];
({foo: obj.prop, bar: arr[0]} = {foo: 123, bar: true});

console.log(obj, arr);  //{ prop: 123 } [ true ]

对象解构指定默认值

var {x = 3} = {};
console.log(x)   //3
var {x, y = 5} = {x: 1};
console.log(x, y);  //1 5
var {message: msg = "Something went wrong"} = {};
console.log(msg)    //默认值生效的条件是,对象的属性严格等于undefined
var {x = 3} = {x: undefined};
console.log(x)  //3
var {x = 3} = {x: null};
console.log(x)  //null
var {foo} = {bar: 'baz'};
console.log(foo)  //undefined    解构是被就是undefined
var x;
{x} = {x: 1};    //报错,{}会解析成一个代码块
console.log(x)
var x;
({x} = {x:1});    //正确
console.log(x)
let {log, sin, cos} = Math;   //对象的解构赋值可以方便地将现有的对象的方法赋值到多个变量。

字符串解构赋值

const [a, b, c, d, e] = "hello";
console.log(a,b,c,d,e)   //h e l l o
let {length: len} = "hello";
console.log(len)  //5

数值和布尔值的解构赋值

let {toString: s} = 123;
console.log(s === Number.prototype.toString)   //true
let {toString: s} = true;
console.log(s === Boolean.prototype.toString)    //true

数值和布尔值都有 toString 属性。

解构赋值时,等号右边的不是对象就会先转换成对象,在进行解构赋值。undefined 和 null 无法转换成对象,解构赋值会报错。

let {prop: x} = undefined;  //TypeError
let {prop: y} = null;   //TypeError

函数参数解构赋值

function add([x, y]) {   //参数不是数组,而是数组经过解构的结果x和y
    return x + y;
}
console.log(add([1,2]));

function ad(x, y) {
    return x + y;
}
console.log(ad(1,2));
console.log([[1,2],[3,4]].map(([a,b]) => a + b));   //[ 3, 7 ]

函数参数解构赋值默认值

function move({x = 0, y = 0} = {}) {
    return [x, y];
}
console.log(move({x:3, y:8}));
console.log(move({x: 3}));
console.log(move({}))
console.log(move())

//run:
[ 3, 8 ]
[ 3, 0 ]
[ 0, 0 ]
[ 0, 0 ]
function move({x, y} = {x: 0, y: 0}) {     //函数的默认值,区别于函数参数解构赋值的默认值
    return [x, y];
}
console.log(move({x:3, y:8}));
console.log(move({x:3}));
console.log(move({}))
console.log(move())

//run:
[ 3, 8 ]
[ 3, undefined ]
[ undefined, undefined ]
[ 0, 0 ]

undefined 会触发函数参数的默认值。

console.log([1, undefined, 3].map((x = 'yes') => x));
//run:
[ 1, 'yes', 3 ]

圆括号的问题

//全部报错,变量声明语句,模式不能使用圆括号
var [(a)] = [1];
var {x: (c)} = {};
var {o: ({p: p})} = {o: {p: 2}};   

函数参数中,模式不能带有圆括号

//函数参数也属于变量声明,不能使用圆括号
function f([(z)]) { return z;}
//全部报错
({p: a}) = {p: 42};
([a]) = [5];
[({p: a}), {x:c}] = [{},{}];

可以使用圆括号的情况

//赋值语句的非模式部分可以使用圆括号,以下都是赋值语句而不是声明语句
[(b)] = [3];
({p: (d)} = {});
[(parseInt.prop)] = [3];

解构赋值的用途

交换变量值

[x, y] = [y, x];

从函数返回多个值

function example() {
    return [1,2,3];
}
var [a, b, c] = example();
console.log(a,b,c);
//返回一个对象
function example() {
    return {
        foo: 1,
        bar: 2
    };
}
var {foo, bar} = example();
console.log(foo,bar);

函数参数的定义:
函数参数有序无序的情况下参数都能准确地一一对应:

function f([x, y, z]) {
    console.log(`x:${x},y:${y},z:${z}.`)
}
f([1,2,3])    //x:1,y:2,z:3.
function f({x, y, z}) {
    console.log(`x:${x},y:${y},z:${z}.`)
}
f({z:3, x:1, y:2});  //x:1,y:2,z:3.

提取JSON数据

var jsonData = {
    id: 42,
    status: "ok",
    data: [867, 5309]
}
let {id, status, data: number} = jsonData;
console.log(id, status, number);   //42 ok [ 867, 5309 ]

函数参数的默认值

jQuery.ajax = function (url, {
    async = true,
    beforeSend = function() {},
    cache = true,
    complete = function() {},
    crossDomain = false,
    global = true,
    //... more config
}) {
    // ... do stuff
};

函数参数的默认值避免了在函数体内部写 var foo = config.foo || 'default foo';

config.foo: 这是一个可能存在的变量或对象属性。如果 config 对象中有一个 foo 属性,它的值会被使用。

|| (逻辑或运算符): 逻辑或运算符会检查 config.foo 的值。如果 config.foo 是假值(undefinednullfalse0NaN 或空字符串 ""),则 || 运算符会返回其右侧的值。

var config = {
    foo: undefined
}
var foo = config.foo || 'default foo';
console.log(foo)     //default foo
var config = {
    foo: null
}
var foo = config.foo || 'default foo';
console.log(foo)     //default foo
var config = {
    foo: 0
}
var foo = config.foo || 'default foo';
console.log(foo)     //default foo
var config = {
    foo: false
}
var foo = config.foo || 'default foo';
console.log(foo)     //default foo
var config = {
    foo: ""
}
var foo = config.foo || 'default foo';
console.log(foo)     //default foo
var config = {
    foo: NaN
}
var foo = config.foo || 'default foo';
console.log(foo)     //default foo
//提供默认值: 当你希望确保一个变量总是有一个有效的值,即使原始值不存在或为假值时。
function greet(name) {
    var displayName = name || 'Guest';
    console.log('Hello, ' + displayName);
}

greet(); // 输出 'Hello, Guest'
greet('Alice'); // 输出 'Hello, Alice'
  • 假值: 逻辑或运算符 || 只会在左侧值是“假值”时使用右侧的值。假值包括 undefinednullfalse0NaN 和空字符串 ""。这意味着,如果 config.foo 的值是这些假值中的一个,默认值将会被使用。

  • 更严格的默认值: 如果想在 config.fooundefinednull 时使用默认值,可以使用空值合并运算符 ??(在 ES2020 中引入):?? 运算符仅在左侧的值为 undefinednull 时使用右侧的值。

    var config = {
        foo: NaN
    }
    var foo = config.foo ?? 'default foo';
    console.log(foo)     //NaN
    
    var config = {
        foo: 0
    };
    
    var foo = config.foo ?? 'default foo';
    console.log(foo); // 输出 0
    

遍历 Map 解构:

任何部署了 Iterator 接口的对象可以使用 for … of 循环遍历。Map 原生支持 Iterator 接口。

var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
    console.log(`key ${key} is ${value}`);
}

只获取键:

for (let [key] of map) { ... ...}

只获取值:

for (let [value] of map) {... ...}

输入模块的指定的方法

const { SourceMapConsumer, SourceNode } = require("source-map");

字符串的扩展

Unicode 表示方法

console.log("\u0061");   //a
console.log("\uD842\uDFB7");   //𠮷
console.log("\u20BB7");   //₻7
console.log("\u{20BB7}");    //𠮷
console.log("\u{41}\u{42}\u{43}");   //ABC
console.log("hell\u{6F}");   //hello
console.log("\u{1F680}");    //🚀
console.log("\uD83D\uDE80");    //🚀
console.log("\u{1F680}" === "\uD83D\uDE80")    //true

javascript 6 中方法表示字符

'z'
console.log('\z' === 'z');    //true
console.log('\172' === 'z');  //true
console.log('\x7A' === 'z');  //true
console.log('\u007A' === 'z');  //true
console.log('\u{7A}' === 'z')   //true

codePointAt()

JavaScript 内部 字符格式 UTF-16(两个字节)。需要4个字节存储的字符(码点大于 0xFFFF 的字符),JavaScript 认为是两个字符。

var s = "𠮷";
let {length: len} = s;
console.log(len)
console.log(s.charAt(0));
console.log(s.charAt(1));
console.log(s.charCodeAt(0));
console.log(s.charCodeAt(1));

//run:
2
�
�
55362
57271
var s = "𠮷a";
console.log(s.codePointAt(0));
console.log(s.codePointAt(1));
console.log(s.codePointAt(2));
console.log(s.codePointAt(0).toString(16));
console.log(s.codePointAt(2).toString(16))

//run:
134071
57271
97
20bb7
61
var s = "𠮷a";
for (let ch of s) {
    console.log(ch.codePointAt(0).toString(16));
}
//run:
20bb7
61
//判断一个字符是否占4个字节
function is32Bit(c) {
    return c.codePointAt(0) > 0xFFFF;
}

console.log(is32Bit("𠮷"));    //true
console.log(is32Bit("a"));     //false

String.fromCodePoint()

//码点值到字符转换
console.log(String.fromCodePoint(0x20BB7))   //𠮷
console.log(String.fromCodePoint(0x78, 0x1f680, 0x79))
console.log('x\uD830\uDE80y')
console.log(String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y')

//run:
x🚀y
x𜊀y
true

字符串遍历接口

for (let ch of 'foo') {
    console.log(ch)
}
var text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {   //普通遍历无法遍历码点大于 0xFFFF的字符
    console.log(text[i])
}

//run:
�
�

at()

console.log('abc'.charAt(0)); //a
console.log('𠮷'.charAt(0));  //�     ECMAScript 7 将支持码点大于 0xFFFF 的字符,chrome已经支持

normalize()

console.log("\u01D1");
console.log("\u004F\u030c");
console.log("\u01D1" === "\u004F\u030c")
console.log("\u01D1".length)
console.log("\u004F\u030c".length)

//run:
Ǒ      //单独字符
Ǒ      //合成字符
false
1
2
console.log("\u01D1".normalize() === "\u004F\u030c".normalize())   //true
//normalize() Unicode 正规化,将字符的不同表示方法统一为同样的形式。

normalize() 参数:

  • NFC,默认参数,标准等价合成,(Normalization Form Canonical Composition),返回多个简单字符的合成字符。标准等价 指 视觉和语义上的等价。
  • NFD,Decomposition,标准等价分解,将 NFC 的合成分解成多个简单字符。
  • NFKC,兼容等价合成,Normalization Form Compatibility Composition,返回合成字符,语义等价,视觉不等价。
  • NFKD,兼容等价分解,NFKC 逆过来。
console.log('\u004f\u030c'.normalize('NFC').length)
console.log('\u004f\u030c'.normalize('NFD').length)
//run:
1
2

includes(), startsWith(), endsWith()

var s = "helloWorld.js";

s.startsWith("hello"); //true
s.endsWith(".js");  //true
s.includes("oWor");  //true

repeat()

'x'.repeat(5);
//xxxxx
'hello'.repeat(2);
//hellohello
let a = 'zero';
console.log(`start${a.repeat(0)}end`);    //startend
let a = 'zero';
console.log(a.repeat(2.9));    //zerozero    小数向下取整
let a = 'Zero';
console.log(a.repeat('2'));    //ZeroZero

padStart(), padEnd()

for (let i = 1; i < 14; i++) {
    console.log(String(i).padStart(3, '0'));
}
//run:
001
002
003
004
005
006
007
008
009
010
011
012
013
for (let i = 1; i < 14; i++) {
    console.log(String(i).padStart(5, 'ab'));
}
//run:
abab1
abab2
abab3
abab4
abab5
abab6
abab7
abab8
abab9
aba10
aba11
aba12
aba13
for (let i = 1; i < 14; i++) {
    console.log(String(i).padEnd(5, 'ab'));
}
//run:
1abab
2abab
3abab
4abab
5abab
6abab
7abab
8abab
9abab
10aba
11aba
12aba
13aba

模板字符串

//使用 jQuery 进行 DOM 操作,将字符串内容追加到一个 HTML 元素中。
//传统写法
$("#result").append(
    "There are <b>" + basket.count + "</b>" +
    "items in your basket, " + 
    "<em>" + backet.onSale + 
    "</em> are on sale!"
);
//ES6写法
$("#result").append(
    `There are <b>${basket.count}</b>
    items in your basket
    <em>${backet.onSale}</em> are on sale!`
);
// 反引号 `` 包裹的字符串镶嵌变量,镶嵌格式 ${变量名},取变量里面的值替换 ${变量名} 放在原字符串里面。
//反引号里的字符换可以换行,保留原有格式
console.log(`abcd
    1234
    yes
    no`)    //字符换原模原样输出,包括空格、换行等

//run:
abcd
    1234
    yes
    no
var x = 1;
var y = 2;
console.log(`x + y = ${x + y}`);

var obj = {
    x: 1,
    y:2
};

console.log(`the sum of x and y in obj is ${obj.x + obj.y}`);
function fn() {
    return "hello world";
}
console.log(`start ${fn()} end`);    //start hello world end

引用模板字符串本身:

写法一

let str = 'return ' + '`Hello ${name}!`';      // 用作函数体
let func = new Function('name', str);          //构造函数
console.log(func('Jack'))

new Function('name', str) 创建了一个新的函数。new Function() 是一个构造函数,用于动态创建 JavaScript 函数。这个构造函数允许代码在运行时生成新的函数,函数体的代码以字符串形式提供。

写法二

let str = '(name) => `Hello ${name}!`';  //字符串的内容是一个箭头函数
//箭头函数接收一个name参数,返回 `Hello ...`
let func = eval.call(null, str);
console.log(func('Jack'));

在 JavaScript 中,箭头函数是一种简洁的函数表示方式,可以使用 => 定义。

eval 是一个全局函数,它会将字符串作为 JavaScript 代码执行。在这里,eval 被用来将 str 变量中的字符串解析为实际的 JavaScript 函数。

eval.call(null, str) 的作用是将 str 作为参数传递给 eval 函数执行,call(null, ...) 是为了确保 eval 的上下文 (this) 被设置为 null。在这种情况下,this 的值是无关紧要的,但使用 call 可以确保 eval 的上下文不会受到干扰。

eval 执行后,str 字符串中的箭头函数会被解析为一个真正的 JavaScript 函数,并赋值给变量 func

实例:模板编译

模板编译是一个将模板字符串(通常包含动态数据插值和逻辑代码)转换成可执行的 JavaScript 代码的过程。这个过程的目的是生成最终的 HTML 代码或其他格式的输出,以便于动态内容的渲染。

var template = `
<ul>
	<% for(var i=0; i < data.supplies.length; i++) {%>
		<li><%= data.supplies[i] %></li>
	<% } %>
</ul>
`;     //这个代码片段定义了一个模板字符串,它包含 HTML 结构和嵌入的 JavaScript 逻辑。模板引擎会将这个模板字符串编译成一个函数,函数可以接受数据并生成动态的 HTML 内容。

<% ... %>: 这是一个包含 JavaScript 逻辑的代码块。这个块内的代码会被执行,但不会直接输出到结果中。

  • 在这个例子中,使用了一个 for 循环来遍历 data.supplies 数组的元素。

<%= ... %>: 这是一个输出表达式,用于将 JavaScript 表达式的结果插入到模板中。在这个例子中,它将 data.supplies[i] 的值插入到 <li> 元素中。

编译和渲染

要将这个模板字符串转换为实际的 HTML,需要使用模板引擎将其编译成函数,并将数据传递给这个函数进行渲染。许多 JavaScript 模板引擎(如 Underscore.js, EJS, Handlebars, 或 Mustache)都提供了这种功能。

const {JSDOM} = require('jsdom');       //npm install jsdom

const dom = new JSDOM(
`<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <div id="myDiv">
        </div>
    </body>
</html>`)

const { window } = dom;

var template = `
<ul>
	<% for(var i=0; i < data.supplies.length; i++) {%>
		<li><%= data.supplies[i] %></li>
	<% } %>
</ul>
`;      //模板字符串

// echo('<ul>');
// for (var i=0; i < DataTransfer.supplies.length; i++) {
//     echo('<li>');
//     echo(data.supplies[i]);
//     echo('</li>');
// };
// echo('</ul>');

function compile(template) {
    var evalExpr = /<%=(.+?)%>/g;
    var expr = /<%([\s\S]+?)%>/g;

    template = template.replace(evalExpr, '`); \n  echo( $1 ); \n  echo(`').replace(expr, '`); \n  $1 \n  echo(`');
    template = 'echo(`' + template + '`);';

    var script = 
    `(function parse(data){
        var output = "";
        function echo(html){
            output += html;
        }
        ${ template}

        return output;
    })`;

    return script;
}

const div = window.document.getElementById('myDiv');

var parse = eval(compile(template));
div.innerHTML = parse({supplies : ["broom", "mop", "cleaner"]});

console.log(window.document.documentElement.outerHTML);
<!DOCTYPE html>
<html>
	<head>
		<script>
		    alert("Hello,World!");
		</script>
	</head>
	<body>
		<div id="myDiv"></div>
	</body>
	<script>
		var template = `
		<ul>
			<% for(var i=0; i < data.supplies.length; i++) {%>
				<li><%= data.supplies[i] %></li>
			<% } %>
		</ul>
		`;
		function compile(template) {
		    var evalExpr = /<%=(.+?)%>/g;
		    var expr = /<%([\s\S]+?)%>/g;
		
		    template = template.replace(evalExpr, '`); \n  echo( $1 ); \n  echo(`').replace(expr, '`); \n  $1 \n  echo(`');
		    template = 'echo(`' + template + '`);';
		
		    var script = 
		    `(function parse(data){
		        var output = "";
		        function echo(html){
		            output += html;
		        }
		        ${ template}
		
		        return output;
		    })`;
		
		    return script;
		}
		var parse = eval(compile(template));
		var div = this.document.getElementById('myDiv');
		div.innerHTML = parse({supplies: ["broom", "mop", "cleaner"]});
	</script>
</html>

正则表达式

Regular Expressions (regex) 用于匹配、查找和替换字符串中字符模式的工具。

创建正则表达式

使用斜杠 (/) 包围正则表达式的模式:

let regex = /pattern/flags;

pattern 是正则表达式的模式。

flags 是可选的标志,用于控制正则表达式的行为。

构造函数

可以通过 RegExp 构造函数动态创建正则表达式:

let regex = new RegExp('pattern', 'flags');

正则表达式标志

g: 全局匹配,不仅匹配第一个,还匹配所有符合条件的字符串。

i: 忽略大小写匹配。

m: 多行匹配,使 ^$ 匹配行的开头和结尾。

s: 点号(.)匹配包括换行符在内的所有字符。

u: Unicode 模式,支持 Unicode 字符集。

y: 粘附模式,确保正则表达式从指定的位置开始匹配。

正则表达式模式

基本字符类
  • \d: 匹配数字,等价于 [0-9]
  • \D: 匹配非数字字符。
  • \w: 匹配字母、数字和下划线,等价于 [a-zA-Z0-9_]
  • \W: 匹配非字母、数字和下划线字符。
  • \s: 匹配任何空白字符(空格、制表符、换行符等)。
  • \S: 匹配非空白字符。
字符集和范围
  • [abc]: 匹配字符 abc
  • [^abc]: 匹配除了字符 abc 之外的字符。
  • [a-z]: 匹配小写字母中的任何一个。
量词
  • \*: 匹配前面的字符零次或多次。
  • +: 匹配前面的字符一次或多次。
  • ?: 匹配前面的字符零次或一次。
  • {n}: 匹配前面的字符恰好 n 次。
  • {n,}: 匹配前面的字符至少 n 次。
  • {n,m}: 匹配前面的字符至少 n 次,但不超过 m 次。
边界匹配
  • ^: 匹配输入字符串的开始。
  • $: 匹配输入字符串的结束。
捕获组和非捕获组
  • (): 捕获组,用于提取匹配的子字符串。
  • (?:): 非捕获组,用于分组但不捕获匹配的内容。

正则表达式方法

RegExp.prototype.test

测试一个字符串是否匹配正则表达式:

let regex = /hello/;
console.log(regex.test('hello world')); // 输出: true
RegExp.prototype.exec

执行正则表达式匹配,返回一个数组或 null

let regex = /(\d+)/;
let result = regex.exec('The number is 42');
console.log(result); // 输出: [ '42', '42', index: 16, input: 'The number is 42', groups: undefined ]
String.prototype.match

使用正则表达式匹配字符串,返回匹配结果的数组:

let str = 'The numbers are 42 and 37';
let regex = /\d+/g;
let result = str.match(regex);
console.log(result); // 输出: [ '42', '37' ]
String.prototype.replace

使用正则表达式替换字符串中的匹配项:

let str = 'Hello world';
let result = str.replace(/world/, 'there');
console.log(result); // 输出: Hello there
String.prototype.split

使用正则表达式拆分字符串:

let str = 'one, two, three';
let result = str.split(/,\s*/);
console.log(result); // 输出: [ 'one', 'two', 'three' ]
String.prototype.search

返回值:

  • 返回第一个匹配的索引位置(从 0 开始),如果没有匹配项,则返回 -1

只能使用正则表达式的特性:

  • search 方法只支持正则表达式,并且不能使用正则表达式的 g(全局) 标志。如果提供的正则表达式有 g 标志,search 方法会忽略这个标志。
let str = 'Hello world!';
let index = str.search(/world/);
console.log(index); // 输出: 6 (world 在字符串中的起始位置是第 6 个字符)
let str = 'Hello World!';
let index = str.search(/world/i); // 使用 i 标志来忽略大小写
console.log(index); // 输出: 6 (world 在字符串中的起始位置是第 6 个字符)
let str = 'Hello!';
let index = str.search(/world/);
console.log(index); // 输出: -1 (没有找到匹配项)

例子

验证邮箱地址
let emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(emailRegex.test('example@example.com')); // 输出: true
1. ^ 字符串开始
  • 含义: 匹配输入字符串的开始部分。确保整个字符串都符合正则表达式的模式。
2. [a-zA-Z0-9._%+-]+ 本地部分
  • [a-zA-Z0-9._%+-]
    匹配一个字符集,其中包括:
    • a-z:所有小写字母
    • A-Z:所有大写字母
    • 0-9:所有数字
    • ._%+-:特殊字符点(.)、下划线(_)、百分号(%)、加号(+)和减号(-
  • +: 匹配前面的字符集一次或多次。意味着本地部分可以包含一个或多个允许的字符。

3. @ 电子邮件的分隔符
  • 含义: 匹配电子邮件地址中的 @ 符号,用于分隔本地部分和域名部分。
4. [a-zA-Z0-9.-]+ 域名部分
  • [a-zA-Z0-9.-]
    匹配一个字符集,其中包括:
    • a-z:所有小写字母
    • A-Z:所有大写字母
    • 0-9:所有数字
    • .:点号(用于分隔域名部分)
    • -:减号(用于分隔域名的不同部分)
  • +: 匹配前面的字符集一次或多次。意味着域名部分可以包含一个或多个允许的字符。

5. \. 点号
  • 含义: 匹配一个实际的点号 .。由于点号在正则表达式中有特殊含义(匹配任何字符),所以在这里需要用反斜杠 \ 转义,表示字面量点号。
6. [a-zA-Z]{2,} 顶级域名
  • [a-zA-Z]
    匹配一个字符集,其中包括:
    • a-z:所有小写字母
    • A-Z:所有大写字母
  • {2,}: 匹配前面的字符集至少 2 次。表示顶级域名(如 .com, .org, .net 等)必须包含至少两个字母字符。

7. $ 字符串结束
  • 含义: 匹配输入字符串的结束部分。确保整个字符串都符合正则表达式的模式。
提取日期
let dateRegex = /(\d{4})-(\d{2})-(\d{2})/;
let dateStr = '2024-08-20';
let match = dateRegex.exec(dateStr);
if (match) {
  console.log(`Year: ${match[1]}, Month: ${match[2]}, Day: ${match[3]}`);
}
1. (\d{4}) 年份部分
  • \d: 匹配一个数字字符,相当于 [0-9]
  • {4}: 表示匹配前面的字符(数字)恰好 4 次。即,匹配 4 位数字。
  • (): 圆括号用于捕获组,将匹配的内容存储到一个数组中,以便后续引用。在这个例子中,它捕获了年份部分。
2. - 分隔符
  • 含义: 匹配一个字面量的连字符 -。用于分隔日期的不同部分(年、月、日)。
3. (\d{2}) 月份部分
  • \d: 匹配一个数字字符,相当于 [0-9]
  • {2}: 表示匹配前面的字符(数字)恰好 2 次。即,匹配 2 位数字。
  • (): 圆括号用于捕获组,将匹配的内容存储到一个数组中。在这个例子中,它捕获了月份部分。
4. - 分隔符
  • 含义: 匹配一个字面量的连字符 -,用于分隔日期的不同部分(年、月、日)。
5. (\d{2}) 日期部分
  • \d: 匹配一个数字字符,相当于 [0-9]
  • {2}: 表示匹配前面的字符(数字)恰好 2 次。即,匹配 2 位数字。
  • (): 圆括号用于捕获组,将匹配的内容存储到一个数组中。在这个例子中,它捕获了日期部分。

RegExp 构造函数

var regex = new RegExp("xyz", "i");
//等价于
var regex = /xyz/i;
//RegExp()接收正则表达式
var regex = new RegExp(/xyz/i);
var regex = /xyz/i;
exStr = "66623asdfggexyz";
console.log(regex.exec(exStr));

//run:
[ 'xyz', index: 12, input: '66623asdfggexyz', groups: undefined ]
new RegExp(/abc/ig, 'i').flags      //忽略原有标志ig,改为 i

u标志

// Unicode 模式,处理大于 \uFFFF 的Unicode 字符
console.log('\uD83D\uDC2A')     //🐪
console.log(/^\uD83D/u.test('\uD83D\uDC2A'));    //false, '\uD83D\uDC2A'是一个4字节的字符
console.log(/\uD83D\uDC2A/u.test('asdfaasdf\uD83D\uDC2Afasdfasd'));    //true
console.log(/^\uD83D/.test('\uD83D\uDC2A'));     //true, 不加u标志,'\uD83D\uDC2A'会当成两个字符

点字符

. 匹配除换行以外的任意单个字符。码点大于 0xFFFF 的 Unicode 字符,点字符不能识别,必须加上 u标志。

var s = '𠮷';
console.log(/^.$/.test(s))   //false
console.log(/^.$/u.test(s))  //true

量词

//使用 u 标志,所有两次都会正确识别码点大于 0xFFFF 的Unicode 字符
var ss = '𠮷𠮷';
var aa = 'aa';
console.log(/a{2}/.test(aa));
console.log(/a{2}/u.test(aa));
console.log(/𠮷{2}/.test(ss));
console.log(/𠮷{2}/u.test(ss));

//run:
true
true
false
true
console.log(/^\u{3}$/.test('uuu'))   //true, u被匹配3次
console.log(/^\u{3}$/u.test('uuu'))  //false, \u{3} 被当作一个 Unicode字符

预定义模式

// \S匹配所有不是空格的字符
console.log(/^\S$/.test('𠮷'))   //false
console.log(/^\S$/u.test('𠮷'))  //true

正确返回字符串长度的函数:(字符串包含Unicode 字符)

function codePointLength(text) {
    var result = text.match(/[\s\S]/gu);
    return result ? result.length : 0;
}
var s = '𠮷𠮷\uD83D\uDC2A';
console.log(s.length)    // 6, 一个 𠮷 被当作两个字符
console.log(codePointLength(s))   //3,正确返回了字符串的长度(字符码点大于 0xFFFF)
console.log('\u004B');   // K
console.log('\u212A');   // K   非规范的字符 K
console.log(/[a-z]/i.test('\u212A'));    //false   无法识别非规范字符 K
console.log(/[a-z]/iu.test('\u212A'));   // true

y标志

y标志 - 粘连(sticky)。

var s = "aaa_aa_a"

var r1 = /a+/g;   //a匹配一次或多次,全局匹配
var r2 = /a+/y;   //a匹配一次或多次,粘连匹配

console.log(r1.exec(s));  //['aaa']
console.log(r2.exec(s));  //['aaa']
 
console.log(r1.exec(s));  //['aa']
console.log(r2.exec(s));  // null

var r = /a+_/y;
console.log(r.exec(s));   //['aaa_']
console.log(r.exec(s));   //['aa_']

//run:
[ 'aaa', index: 0, input: 'aaa_aa_a', groups: undefined ]
[ 'aaa', index: 0, input: 'aaa_aa_a', groups: undefined ]
[ 'aa', index: 4, input: 'aaa_aa_a', groups: undefined ]
null
[ 'aaa_', index: 0, input: 'aaa_aa_a', groups: undefined ]
[ 'aa_', index: 4, input: 'aaa_aa_a', groups: undefined ]

g标志:后一次匹配从上一次匹配成功的下一个位置开始,剩余的字符串中只要有符合正则表达式的就匹配。

y标志:后一次匹配从上一次匹配成功的下一个位置开始,但是要连在一起,中间不能有其他字符(上面的案例中中间有个 _ 字符)。

y标志的应用,和 g标志相比,使用y标志可以发现被匹配字符串之间非法的字符。例如:从字符串提取 token 词元,y标志确保匹配之间不会有漏掉的字符。

sticky 属性

// 正则表达式是否设置 sticky
var regex = /hello\d/y;
console.log(regex.sticky);    //true

flags 属性

// 返回正则表达式的标志
console.log(/abc/ig.flags);    //ig
console.log(/abc/ig.source);   //abc

数值

二进制八进制

console.log(0b101 === 5);   // 二进制数值 0b 开头
console.log(0o12 === 10);   // 八进制数值 0o 开头
console.log(Number(0b101), Number(0o12));
//run:
true
true
5 10

Number.isFinite(), Number.isNaN()

// infinite adj. 无穷的
// finite adj. 有限的
Number.isFinite(15);   // 15 是否有限
Number.isFinite(NaN);  // false     Not a Number 不是一个数字 NaN
Number.isFinite(Infinity);    //false
Number.isFinite('15');   //false
Number.isNaN(NaN);   //true
Number.isNaN(666);   //false
Number.isNaN("999"); //false
Number.isNaN(true);  //false

Number.parseInt(), Number.parseFloat()

Number.parseInt('12.34');   //12
Number.parseFloat('3.1415926abc');   // 3.1415926

Number.isInteger()

Number.isInteger(25);  //true    //是否整数
Number.isInteger(25.0);   //true
Number.isInteger(25.3);   //false
Number.isInteger('11');   //false
Number.isInteger(true);   //false

函数

通过解构赋值设置默认值

function func({x, y = 1}) {
    console.log(x, y);
}

func({});    //undefined 1
func({x:1});   //1 1
func({x:1, y:6});   //1 6
func();   //Error

直接指定默认值

function func(x, y = "world") {
    console.log(x, y);
}

func("hello");    //hello world
func("hello", "Jackey Song");    //hello Jackey Song
func("hello", "");       //hello
function fetch(url, { body='', method='GET', headers={} }) {
    console.log(method);
}

fetch('https://jackey-song.com', {});    	//GET   解构赋值参数必须指定
fetch('https://jackey-song.com');           //不指定报错
function fetch(url, { body='', method='GET', headers={} } = {}) {
    console.log(method);
}

fetch('https://jackey-song.com');  //GET    解构赋值指定默认值,不报错
function f1({x = 0, y = 0} = {}) {
    console.log(x, y);
}

function f2({x, y} = {x:0, y:0}) {
    console.log(x, y);
}

f1();    //0 0
f2();    //0 0

f1({x: 5, y:4});     //5 4
f2({x: 5, y:4});     //5 4

f1({x: 5});   //5 0
f2({x: 5})    //5 undefined

f1({});  //0 0
f2({});  //undefined undefined

指定默认值的参数应当放在最后一个

function func(x, y, z=1) {
    console.log(x, y, z);
}

func(3,2);    //3 2 1

function m(x=1, y, z) {
    console.log(x, y, z);
}

m(2,3);     //2 3 undefined 
m(undefined, 2, 3);   //1 2 3   默认值参数放在第一个(或中间),每次使用默认值都要传个 undefined

function n(x, y=1, z) {
    console.log(x, y, z);
}

n(2, undefined, 3);   //2 1 3

函数的 length 参数个数

function func(x, y, z=1) {
    console.log(x, y, z);
}

console.log(func.length)   //2 不会算上默认值

变量作用域

var x = 1;
function f(x, y = x) {
    console.log(y);
}
f(2);   //2    z在这里函数的参数x是一个局部变量,传入了2,y = x,所以y是2,而不是外部的全局变量1
console.log(...[1, 2, 3]);   //1 2 3

函数的name属性:

function func() {
    return 0;
}

var abc = function() {    //匿名函数赋值给变量 abc
    return 0;
}

console.log(func.name);   //func     返回函数名
console.log(abc.name);    //abc
const f = function func() {
    return 0;
}
console.log(f.name);   //func

var abc = function f() {
    return 0;
}
console.log(abc.name);   //f

箭头函数

使用 => 定义函数。

var f = v => v;
//等同于
var f = function(v) {
    return v;
}

var f = () => 5;   //不需要参数,使用圆括号
//等同于
var f = function() {
    return 5
}

var f = (a, b, c) => a + b + c;   //多个参数
console.log(f(1,2,3));    //6
//等同于
var f = function(a, b, c) {
    return a + b + c;
}
var f = (a, b, c) => {a = a + b + c; return a**2};     
//函数体有多个语句,要用花括号,返回值要加 return
console.log(f(1, 1, 1));    //9
var f = name => ({id: 1, name: name});    
//返回值是一个对象,对象本身有花括号包裹,因此外面需要再加一个圆括号。
console.log(f("Jackey Song"));    //{ id: 1, name: 'Jackey Song' }
[1,2,3]map(function(x) {
   return x*x; 
});
//简写
[1,2,3]map(x => x*x);
const numbers = (...nums) => nums;
console.log(numbers(1,2,3,4,5));    //[ 1, 2, 3, 4, 5 ]
const headAndTail = (head, ...tail) => [head, tail];
console.log(headAndTail(1,2,3,4,5));   //[ 1, [ 2, 3, 4, 5 ] ]

箭头函数没有自己的 this 绑定。它们从定义时的上下文继承 this,即箭头函数的 this 是在函数定义时确定的,不是调用时确定的。

function Counter() {
  this.value = 0;
  setInterval(() => {
    this.value++; // `this` 指向 Counter 实例
    console.log(this.value);
  }, 1000);    //每1000毫秒执行一次
}

new Counter();

箭头函数不能用作构造函数,不能使用 new 关键字实例化对象。

箭头函数没有 arguments 对象。如果需要访问函数的参数,可以使用 rest 参数(...args)。

function traditionalFunction() {
  console.log(arguments); // [1, 2, 3]
}

const arrowFunction = () => {
  // console.log(arguments); // 会抛出错误
};

traditionalFunction(1, 2, 3);
arrowFunction(1, 2, 3); // `arguments` 在箭头函数中是不可用的

由于箭头函数不会创建自己的 this 绑定,它们在事件处理程序和其他需要 this 绑定的场景中特别有用。

class MyClass {
  constructor() {
    this.name = 'MyClass';
  }

  greet() {
    setTimeout(() => {
      console.log(`Hello from ${this.name}`); // `this` 正确地指向 MyClass 实例
    }, 1000);
  }
}

new MyClass().greet();

箭头函数不可使用 yield 命令,箭头函数不能用作 Generator 函数。


箭头函数不能使用 super 关键字,因为它们没有自己的 this,因此无法访问父类的构造函数或方法。

class Parent {
  constructor() {
    this.name = 'Parent';
  }

  method() {
    return () => {
      console.log(super.method()); // 错误: `super` 不能在箭头函数中使用
    };
  }
}

class Child extends Parent {
  constructor() {
    super();
    this.name = 'Child';
  }

  method() {
    return 'Hello from Child';
  }
}

const child = new Child();
child.method()();

function foo() {
    setTimeout( () => {
        console.log("id:", this.id);
    }, 100);  //100毫秒后执行
}

//两种调用方式
foo()
foo.call( {id: 42});   //id: 42

1. foo() 调用方式

接调用函数 foo() 时,它会在当前的上下文中执行,而不是使用任何特定的 this 值。

function foo() {
    setTimeout(() => {
        console.log("id:", this.id);
    }, 100);  // 100 毫秒后执行
}

foo();  // 在全局上下文中调用
  • this 绑定:
    • 在浏览器中,直接调用 foo() 时,this 通常会绑定到全局对象 window(在严格模式下,thisundefined)。
    • 由于在全局上下文中 id 不存在,这段代码在浏览器的非严格模式下会输出 id: undefined,在严格模式下会抛出错误,因为 thisundefined,无法读取 id 属性。

2. foo.call({id: 42}) 调用方式

当使用 foo.call({id: 42}) 调用函数时,foo 会被执行,并且 this 会被显式地设置为 call 方法中的第一个参数。

function foo() {
    setTimeout(() => {
        console.log("id:", this.id);
    }, 100);  // 100 毫秒后执行
}

foo.call({id: 42});  // 在指定的上下文中调用
  • this 绑定:
    • 在这种情况下,thisfoo 函数体内会被绑定到 {id: 42} 对象。这意味着箭头函数内部的 this 也是 {id: 42}
    • 因此,setTimeout 回调中的 this.id 会正确地输出 42

关键区别总结

  • foo(): 直接调用时,this 是调用 foo 的上下文(全局对象或 undefined),特别是在异步操作中,如 setTimeoutthis 的绑定可能会与期望的上下文不同。
  • foo.call(context): 使用 call 方法调用时,this 被显式地设置为 context 参数。在异步操作中,this 的值将根据 call 的参数来确定。

示例对比

示例 1: foo()
function foo() {
    setTimeout(() => {
        console.log("id:", this.id);
    }, 100);
}

foo();  // 输出: "id: undefined" 或者在严格模式下可能报错
示例 2: foo.call({id: 42})
function foo() {
    setTimeout(() => {
        console.log("id:", this.id);
    }, 100);
}

foo.call({id: 42});  // 输出: "id: 42"

其他代码

var handler = {
    id: "123456",
    init: function() {
        document.addEventListener("click", event => this.doSomething(event.type), false);    //箭头函数内部的 this 指向 handler 对象
    },
    doSomething: function(type) {
        console.log("Handling " + type + " for " + this.id);     //这里的 this 指向全局对象
    }
};
function Timer () {
    this.seconds = 0;     //this 指向new Timer() 创建的对象
    setInterval(() => this.seconds++, 1000);    //this 指向该对象
}

var timer = new Timer();
setTimeout(() => console.log(timer.seconds), 3100);   //3
function foo() {
    return () => {
        return () => {
            return () => {
                console.log('id:', this.id);
            };
        };
    };
}
foo.call({id: 42})()()();  //id: 42
//箭头函数本身没有自己的 this,所以无论箭头函数嵌套多少层,它都是外层的 this。正因为它没有自己的this,所以它不能用作构造函数。

构造函数 VS 普通函数

1. 函数名称的约定

  • 构造函数:

    • 构造函数通常以大写字母开头,以便与普通函数区分。这是一个约定而非强制规则。例如:Person, Car, Timer 等。
    function Person(name) {
        this.name = name;
    }
    
  • 普通函数:

    • 普通函数的名称通常以小写字母开头,虽然这并不是强制的,但可以帮助区分。例如:calculateTotal, printMessage 等。
    function calculateTotal(a, b) {
        return a + b;
    }
    

2. new 关键字

  • 构造函数:

    • 构造函数应使用 new 关键字调用。当用 new 调用构造函数时,它会创建一个新对象并将 this 绑定到这个新对象。
    const person = new Person('Alice'); // 使用 new 调用构造函数
    
  • 普通函数:

    • 普通函数不应该用 new 关键字调用。如果用 new 调用普通函数,它将不会按预期工作,通常会返回 undefined 或导致错误。
    const total = calculateTotal(5, 10); // 普通函数的调用方式
    

3. this 绑定

  • 构造函数:

    • 在构造函数内部,this 指向正在被创建的新对象。构造函数可以将属性和方法添加到这个新对象上。
    function Person(name) {
        this.name = name; // `this` 指向新创建的 Person 对象
    }
    
  • 普通函数:

    • 在普通函数中,this 的值取决于函数的调用方式。在非严格模式下,this 通常指向全局对象(在浏览器中是 window),在严格模式下,thisundefined
    function printName() {
        console.log(this.name); // `this` 取决于调用上下文
    }
    

4. 返回值

  • 构造函数:

    • 构造函数通常不显式返回值。它们自动返回新创建的对象。如果显式地返回一个对象,那个对象将作为构造函数的结果被返回。
    function Person(name) {
        this.name = name;
        // 不需要返回值,自动返回新对象
    }
    
  • 普通函数:

    • 普通函数可以返回任何值,包括原始数据类型、对象或其他函数。返回值必须显式地指定。
    function add(a, b) {
        return a + b; // 显式返回结果
    }
    

5. 使用场景

  • 构造函数:

    • 主要用于创建和初始化对象实例。它们用于定义对象的属性和方法,通常通过 new 关键字创建新实例。
    function Car(make, model) {
        this.make = make;
        this.model = model;
    }
    
    const myCar = new Car('Toyota', 'Corolla');
    
  • 普通函数:

    • 主要用于执行特定的任务或计算结果。普通函数可以被调用多次,但不创建对象实例。
    function multiply(a, b) {
        return a * b;
    }
    
    const result = multiply(4, 5);
    

总结

  • 构造函数:

    • 名称以大写字母开头(通常为约定)。
    • 使用 new 关键字调用。
    • 在函数内部,this 绑定到新创建的对象。
    • 通常不显式返回值(自动返回新对象)。
  • 普通函数:

    • 名称以小写字母开头(虽然这不是强制的)。
    • 不使用 new 关键字调用。
    • this 取决于调用上下文。
    • 可以显式返回值。

函数绑定

函数绑定是 JavaScript 中处理 this 的一种机制,允许创建一个新的函数并预设其中的 this 值和/或参数。函数绑定主要有三种实现方式:Function.prototype.bind() 方法、.call().apply() 方法的使用,以及箭头函数的 this 绑定行为。

1. Function.prototype.bind() 方法

bind() 方法用于创建一个新的函数,这个新函数会将 this 绑定到指定的对象,并且可以预先设置一些参数。

  • 语法:

    function.bind(thisArg[, arg1[, arg2[, ...]]])
    
  • 参数:

    • thisArg: 新函数执行时 this 的值。
    • arg1, arg2, …: 预设的参数,调用新函数时这些参数会被默认传入。
  • 示例:

    function greet(greeting, name) {
        console.log(`${greeting}, ${name}!`);
    }
    
    const greetHello = greet.bind(null, 'Hello');
    greetHello('Alice'); // 输出: "Hello, Alice!"
    

    在这个例子中,greetHello 是一个新的函数,它的 this 被绑定为 null,并且 greeting 参数被预设为 'Hello'。调用 greetHello('Alice') 实际上等价于 greet(null, 'Hello', 'Alice')

2. .call().apply() 方法

call()apply() 方法用于立即调用函数,并可以显式地设置函数的 this 值和参数。

  • call() 方法:

    function functionName(arg1, arg2, ...) {
        // function body
    }
    functionName.call(thisArg, arg1, arg2, ...);
    
  • apply() 方法:

    function functionName(arg1, arg2, ...) {
        // function body
    }
    functionName.apply(thisArg, [arg1, arg2, ...]);
    
  • 区别:

    • call() 接受参数列表。
    • apply() 接受一个参数数组。
  • 示例:

    function greet(greeting, name) {
        console.log(`${greeting}, ${name}!`);
    }
    
    greet.call(null, 'Hi', 'Bob'); // 输出: "Hi, Bob!"
    greet.apply(null, ['Hi', 'Bob']); // 输出: "Hi, Bob!"
    

    在这两个例子中,greet 函数的 this 被设置为 null,并且传入的参数被分别通过 call()apply() 方法传递。

3. 箭头函数的 this 绑定

箭头函数的 this 绑定规则不同于普通函数。箭头函数不创建自己的 this 上下文,它会从外围上下文中继承 this。因此,箭头函数中的 this 是静态绑定的,在函数创建时就已经确定。

总结

  • bind(): 创建一个新函数,设置 this 和预设参数。不会立即执行。
  • call()apply(): 立即调用函数,设置 this 和传递参数。call() 接受参数列表,apply() 接受参数数组。

函数绑定是 JavaScript 中处理 this 绑定和函数调用的关键技术,在各种上下文中控制 this 的值和行为。

对象

创建对象

  1. 对象字面量(Object Literal):
    这是创建对象最直接和常用的方法。

    const person = {
        name: 'John',
        age: 30,
        greet: function(name) {
            console.log(`Hello! ${name}`);
        }
    };
    
    person.greet("Jackey Song");
    
  2. 构造函数(Constructor Function):
    定义一个构造函数,然后用 new 关键字创建对象。

    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.greet = function() {
            console.log('Hello!');
        };
    }
    
    const person = new Person('John', 30);
    person.greet();
    
  3. Object.create() 方法:
    使用 Object.create() 方法可以创建一个新的对象,指定其原型。

    const personPrototype = {
        greet: function() {
            console.log('Hello!');
        }
    };
    
    const person = Object.create(personPrototype);
    person.name = 'John';
    person.age = 30;
    person.greet = function () {     //重写原型中的 greet() 方法
        console.log(`hello, my name is${this.name}, and I am ${this.age} years old.`);
    }
    
    person.greet();
    
  4. class 语法:
    使用 ES6 的 class 语法来定义一个类,然后创建对象实例。

    class Person {
        constructor(name, age) {
            this.name = name;
            this.age = age;
     }
    
        greet() {
            console.log(`Hello, my name is ${this.name}, My age is ${this.age}`);
        }
    }
    
    const person = new Person('John', 30);
    
    person.greet();
    
  5. Object.assign() 方法:
    Object.assign() 可以用于复制对象的属性。虽然它不是直接用来创建对象的方法,但可以用来从现有对象创建新对象。

    const person = Object.assign({}, { name: 'John', age: 30, greet: function() { console.log(`I am ${this.name}. My age is ${this.age}.`); } });
    
    person.greet();
    
  6. 工厂函数(Factory Function):
    工厂函数是一个普通函数,用于创建并返回一个新的对象。

    function createPerson(name, age) {
        return {
            name: name,
            age: age,
            greet: function() {
                console.log('Hello!');
            }
        };
    }
    
    const person = createPerson('John', 30);
    
    person.greet();
    

这些方法中,对象字面量class 语法是最直观和常用的,而 Object.create()Object.assign() 提供了更多的灵活性。

var foo = 'bar';
var baz = {foo};   //对象的属性直接设置为变量名
console.log(baz);   //{ foo: 'bar' }
function f(x, y) {
    return {x, y};    //将变量直接封装到对象中返回
}
console.log(f("abc", "123"));   //{ x: 'abc', y: '123' }
var o = {
    method() {     //对象封装方法
        return "hello world!";
    }
}

属性赋值器 setter 、取值 getter

//工厂函数
function createStorage() {
    const storage = {};

    return {
        getItem: function(key) {
            return key in storage ? storage[key] : null;
        },
        setItem: function(key, value) {
            storage[key] = value;
        },
        clear: function() {
            for (let key in storage) {
                if (storage.hasOwnProperty(key)) {
                    delete storage[key];
                }
            }
        }
    };
}

const storage = createStorage();
storage.setItem("name", "Jackey Song");
storage.setItem("age", "18");
console.log(storage.getItem("name")); // 输出: Jackey Song
console.log(storage.getItem("age"));  // 输出: 18
storage.clear();
console.log(storage.getItem("name")); // 输出: null
console.log(storage.getItem("age"));  // 输出: null
//直接写入对象中
var obj = {
    setItem: function(key, value) {
        this[key] = value;
    },
    getItem: function(key) {
        return key in this ? this[key] : null;
    },
    clear: function() {
        for (let key in this) {
            if (this.hasOwnProperty(key)) {
                delete this[key];
            }
        }
    }
};

obj.setItem("name", "Jackey Song");
obj.setItem("age", "18");
console.log(obj.getItem("name")); // 输出: Jackey Song
console.log(obj.getItem("age"));  // 输出: 18
obj.clear();
console.log(obj); // 输出: {}
//封装成类
class Storage {
    constructor() {
        this.storage = {};
    }

    getItem(key) {
        return key in this.storage ? this.storage[key] : null;
    }

    setItem(key, value) {
        this.storage[key] = value;
    }

    clear() {
        this.storage = {};
    }
}

const storage = new Storage();
storage.setItem("name", "Jackey Song");
storage.setItem("age", "18");
console.log(storage.getItem("name")); // 输出: Jackey Song
console.log(storage.getItem("age"));  // 输出: 18
storage.clear();
console.log(storage.getItem("name")); // 输出: null
console.log(storage.getItem("age"));  // 输出: null

在 JavaScript 中,类(class)是 ES6 引入的用于创建对象和处理继承的一种语法糖。它提供了一种更简洁的方式来定义构造函数和继承结构。

1. 定义类

使用 class 关键字来定义一个类。一个类包含构造函数和方法。

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    greet() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
}

const person = new Person('Jackey', 1000);
person.greet(); // 输出: Hello, my name is Jackey and I am 1000 years old.

2. 构造函数

constructor 是一个特殊的方法,用于初始化类的新实例。每次创建类的实例时,constructor 方法会被调用。

3. static

//静态方法
class MathUtils {
    static add(a, b) {     //static 修饰的方法是静态方法
        return a + b;
    }

    static multiply(a, b) {     //静态方法只能通过类名调用
        return a * b;
    }

    constructor(name){      //构造方法会在创建对象的时候调用
        this.name = name;
    }

    sub(a, b) {      //普通方法
        return a - b;
    }
}

console.log(MathUtils.add(2, 3)); // 输出: 5
console.log(MathUtils.multiply(2, 3)); // 输出: 6
var mth = new MathUtils();
console.log(mth.sub(1,2));    //可以调用普通方法
console.log(mth.add(1,2));    //静态方法不能通过对象调用
//静态属性
//静态属性也是通过 static 关键字定义。静态属性属于类本身,而不是类的实例。它们可以用于存储与类相关的数据,但不是特定于任何实例的。
class Car {
    static wheels = 4;

    static getWheels() {
        return Car.wheels;
    }
}

console.log(Car.wheels); // 输出: 4
console.log(Car.getWheels()); // 输出: 4
//静态方法可以被子类继承和调用。子类可以直接访问父类的静态方法,但子类不会继承父类实例方法。
class Vehicle {
    static start() {
        console.log('Vehicle started');
    }
}

class Car extends Vehicle {}

Car.start(); // 输出: Vehicle started

4. 继承

JavaScript 的类可以继承其他类。使用 extends 关键字来创建子类,并使用 super 关键字来调用父类的构造函数和方法。

class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        console.log(`${this.name} makes a noise.`);
    }
}

class Dog extends Animal {
    speak() {
        super.speak();    //在子类的方法内部使用 super 来调用父类的方法。
        console.log(`${this.name} barks.`);
    }
}

const dog = new Dog('Rex');
dog.speak(); 
// 输出: Rex makes a noise.
//       Rex barks

5. Getter 和 Setter

类中的 getter 和 setter 用于定义对象属性的访问和设置逻辑。

class Person {
    constructor(name) {
        this._name = name;
    }

    get name() {
        return this._name;
    }

    set name(value) {
        if (value.length > 0) {
            this._name = value;
        }
    }
}

const person = new Person('Jackey');
console.log(person.name); // 输出: Jackey
person.name = 'John';
console.log(person.name); // 输出: John

6. 私有属性

ES2022 引入了私有字段,用于定义类的私有属性。这些属性只能在类的内部访问。

class Person {
    #name;

    constructor(name) {
        this.#name = name;
    }

    getName() {
        return this.#name;
    }
}

const person = new Person('Jackey');
console.log(person.getName()); // 输出: Jackey
// console.log(person.#name); // 语法错误

异步

在 JavaScript 中,异步编程允许执行长时间运行的操作(如网络请求、文件读取等)而不会阻塞程序的其他部分。异步编程有助于提高应用的响应性和性能。以下是 JavaScript 中异步编程的主要概念和技术:

1. 回调函数(Callbacks)

回调函数是最基础的异步编程技术。将一个函数作为参数传递给另一个函数,并在某个操作完成后调用这个回调函数。

示例:

function fetchData(callback) {
    setTimeout(() => {
        const data = 'Some data';
        callback(data);
    }, 1000);
}

fetchData((data) => {
    console.log(data); // 输出: Some data
});

console.log("hello");   //先打印 hello ,一秒后箭头函数被回调,才打印 Some data

2. Promise

Promise 是一种更现代的异步处理机制,代表一个操作的最终完成(或失败)及其结果值。它允许你链式地处理多个异步操作。

创建 Promise:

const fetchData = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const data = 'Some data';
            resolve(data); // 成功时调用 resolve
            // reject('Error occurred'); // 失败时调用 reject
        }, 1000);
    });
};

fetchData()
    .then(data => {
        console.log(data); // 输出: Some data
    })
    .catch(error => {
        console.error(error);
    });

console.log("hello")

Promise 状态:

  • Pending(待定):初始状态,既不是成功也不是失败。
  • Fulfilled(已兑现):操作成功完成。
  • Rejected(已拒绝):操作失败。

3. Promise.all 和 Promise.race

  • Promise.all:接受一个包含多个 Promise 对象的数组,只有所有 Promise 都成功时,才会成功,返回一个包含每个 Promise 结果的数组。
const promise1 = Promise.resolve('Data from promise1');
const promise2 = Promise.resolve('Data from promise2');

Promise.all([promise1, promise2])
    .then(results => {
        console.log(results); // 输出: ['Data from promise1', 'Data from promise2']
    })
    .catch(error => {
        console.error(error);
    });
  • Promise.race:接受一个包含多个 Promise 对象的数组,返回第一个完成的 Promise 的结果(不论成功还是失败)。
const promise1 = new Promise((resolve, reject) => setTimeout(resolve, 500, 'Result from promise1'));
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 100, 'Result from promise2'));

Promise.race([promise1, promise2])
    .then(result => {
        console.log(result); // 输出: Result from promise2
    });

4. async/await

asyncawait 是 ES2017(ES8)引入的异步编程语法糖,使异步代码看起来更像同步代码。async 用于定义异步函数,await 用于等待 Promise 解析。

示例:

const fetchData = () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve('Some data');
        }, 1000);
    });
};

const processData = async () => {
    try {
        const data = await fetchData(); // 等待 fetchData 完成
        console.log(data); // 输出: Some data
    } catch (error) {
        console.error(error);
    }
};

processData();

特点:

  • async:声明函数为异步函数,返回一个 Promise。
  • await:在异步函数内部,暂停代码执行直到 Promise 解决,简化了异步操作的代码。

5. 错误处理

在异步编程中,错误处理是非常重要的。对于 Promise,可以使用 .catch 处理错误;对于 async/await,可以使用 try/catch

Promise 错误处理:

fetchData()
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.error(error);
    });

async/await 错误处理:

const processData = async () => {
    try {
        const data = await fetchData();
        console.log(data);
    } catch (error) {
        console.error(error);
    }
};

6. 其他异步 API

JavaScript 还提供了其他异步 API,例如:

  • setTimeoutsetInterval:用于延迟执行代码或定期执行代码。
  • fetch API:用于网络请求,基于 Promises。
  • XMLHttpRequest:传统的网络请求 API,较老,使用回调函数处理异步操作。

  • 回调函数:最基础的异步处理方式。
  • Promise:提供链式调用和更好的错误处理。
  • Promise.allPromise.race:用于处理多个 Promise。
  • async/await:使异步代码看起来像同步代码,简化异步编程。
  • 错误处理:无论使用何种异步技术,正确的错误处理是关键。

模块

JavaScript 的模块系统允许将代码组织成独立的、可重用的部分,这样可以更好地管理和维护大型应用程序。

ES6 模块提供了 exportimport 关键字,用于在不同的 JavaScript 文件之间共享代码。

打开或创建 package.json 文件,添加 "type": "module" 属性。

{
  "type": "module"
}
1. 导出(export

export 关键字用于将模块中的变量、函数或类暴露给其他模块。可以有两种导出方式:

  • 具名导出(Named Exports):导出一个或多个具体的变量、函数或类。
// math.js
export const pi = 3.14;

export function add(a, b) {
    return a + b;
}

export class Calculator {
    multiply(a, b) {
        return a * b;
    }
}
  • 默认导出(Default Export):每个模块只能有一个默认导出。默认导出用于导出一个主值(如函数、类或对象)。
// utils.js
export default function greet(name) {
    return `Hello, ${name}!`;
}

结合具名导出和默认导出:

// utils.js
export const version = '1.0';

export default function greet(name) {
    return `Hello, ${name}!`;
}
2. 导入(import

import 关键字用于从其他模块中导入导出的内容。导入时,需遵循相应的导出方式:

  • 导入具名导出
// app.js
import { pi, add, Calculator } from './math.js';

console.log(pi); // 输出: 3.14
console.log(add(2, 3)); // 输出: 5

const calc = new Calculator();
console.log(calc.multiply(2, 3)); // 输出: 6
  • 导入默认导出
// app.js
import greet from './utils.js';

console.log(greet('World')); // 输出: Hello, World!
  • 导入所有内容:使用 * as 语法可以导入一个模块的所有导出,并将它们作为一个对象来使用。
// app.js
import * as math from './math.js';

console.log(math.pi); // 输出: 3.14
console.log(math.add(2, 3)); // 输出: 5

3. 重新导出

可以在一个模块中重新导出其他模块的导出内容:

// index.js
export { pi, add } from './math.js';
export { default as greet } from './utils.js';

4. 动态导入

ES6 模块还支持动态导入,这允许在运行时按需加载模块。动态导入返回一个 Promise 对象,因此可以使用 .then()await 处理。

// app.js
async function loadModule() {
    const { default: greet } = await import('./utils.js');
    console.log(greet('Dynamic Import'));
}

loadModule();

5. 模块的作用域

每个模块在 JavaScript 中都有自己的作用域。模块中的变量和函数默认是私有的,只有通过 export 才能公开给其他模块。

  • export:用于导出模块中的内容,可以是具名导出或默认导出。
  • import:用于从其他模块导入内容,可以导入具名导出、默认导出,或将所有导出作为一个对象导入。
  • 动态导入:允许在运行时按需加载模块。
  • 模块作用域:每个模块有自己的作用域,避免了全局命名冲突。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jackey_Song_Odd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值