ECMA Script 6
文章目录
- DOM (Document Object Model)
- BOM (Browser Object Model)
- let 和 const 命令
- const
- Object.freeze方法
- 跨模块常量
- 全局对象的属性
- 变量的结构赋值
- 数组结构赋值
- 对象解构赋值
- 字符串解构赋值
- 数值和布尔值的解构赋值
- 函数参数解构赋值
- 圆括号的问题
- 解构赋值的用途
- 字符串的扩展
- Unicode 表示方法
- codePointAt()
- String.fromCodePoint()
- 字符串遍历接口
- at()
- normalize()
- includes(), startsWith(), endsWith()
- repeat()
- padStart(), padEnd()
- 模板字符串
- 实例:模板编译
- 正则表达式
- 正则表达式标志
- 正则表达式模式
- 基本字符类
- 字符集和范围
- 量词
- 边界匹配
- 捕获组和非捕获组
- 正则表达式方法
- `RegExp.prototype.test`
- `RegExp.prototype.exec`
- `String.prototype.match`
- `String.prototype.replace`
- `String.prototype.split`
- `String.prototype.search`
- 例子
- 验证邮箱地址
- 1. `^` 字符串开始
- 2. `[a-zA-Z0-9._%+-]+` 本地部分
- 3. `@` 电子邮件的分隔符
- 4. `[a-zA-Z0-9.-]+` 域名部分
- 5. `\.` 点号
- 6. `[a-zA-Z]{2,}` 顶级域名
- 7. `$` 字符串结束
- 提取日期
- 1. `(\d{4})` 年份部分
- 2. `-` 分隔符
- 3. `(\d{2})` 月份部分
- 4. `-` 分隔符
- 5. `(\d{2})` 日期部分
- RegExp 构造函数
- u标志
- y标志
- 数值
- Number.parseInt(), Number.parseFloat()
- Number.isInteger()
- 函数
- 箭头函数
- 1. **`foo()` 调用方式**
- 2. **`foo.call({id: 42})` 调用方式**
- 关键区别总结
- 示例对比
- 示例 1: `foo()`
- 示例 2: `foo.call({id: 42})`
- 构造函数 VS 普通函数
- 1. **函数名称的约定**
- 2. **`new` 关键字**
- 3. **`this` 绑定**
- 4. **返回值**
- 5. **使用场景**
- 总结
- 函数绑定
- 1. **`Function.prototype.bind()` 方法**
- 2. **`.call()` 和 `.apply()` 方法**
- 3. **箭头函数的 `this` 绑定**
- 总结
- :: 函数绑定运算符
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 iconsole.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); //undefinedtmp = 123;console.log(tmp); //123
}//run:
D:\jsPro\work\test.js:3tmp = '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 = bconsole.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) //1this.a = 2
console.log(this.a, a) //2 1window.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];
:
- 使用解构赋值更新
a
和b
的值。a
被更新为b
,b
被更新为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和yreturn 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
是假值(undefined
、null
、false
、0
、NaN
或空字符串 ""
),则 ||
运算符会返回其右侧的值。
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'
-
假值: 逻辑或运算符
||
只会在左侧值是“假值”时使用右侧的值。假值包括undefined
、null
、false
、0
、NaN
和空字符串""
。这意味着,如果config.foo
的值是这些假值中的一个,默认值将会被使用。 -
更严格的默认值: 如果想在
config.foo
为undefined
或null
时使用默认值,可以使用空值合并运算符??
(在 ES2020 中引入):??
运算符仅在左侧的值为undefined
或null
时使用右侧的值。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
xy
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(`abcd1234yesno`) //字符换原模原样输出,包括空格、换行等//run:
abcd1234yesno
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 jsdomconst 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]
: 匹配字符a
、b
或c
。[^abc]
: 匹配除了字符a
、b
或c
之外的字符。[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)); // nullvar 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 0f1({x: 5, y:4}); //5 4
f2({x: 5, y:4}); //5 4f1({x: 5}); //5 0
f2({x: 5}) //5 undefinedf1({}); //0 0
f2({}); //undefined undefined
指定默认值的参数应当放在最后一个:
function func(x, y, z=1) {console.log(x, y, z);
}func(3,2); //3 2 1function m(x=1, y, z) {console.log(x, y, z);
}m(2,3); //2 3 undefined
m(undefined, 2, 3); //1 2 3 默认值参数放在第一个(或中间),每次使用默认值都要传个 undefinedfunction 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() { //匿名函数赋值给变量 abcreturn 0;
}console.log(func.name); //func 返回函数名
console.log(abc.name); //abc
const f = function func() {return 0;
}
console.log(f.name); //funcvar 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
(在严格模式下,this
是undefined
)。 - 由于在全局上下文中
id
不存在,这段代码在浏览器的非严格模式下会输出id: undefined
,在严格模式下会抛出错误,因为this
是undefined
,无法读取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
绑定:- 在这种情况下,
this
在foo
函数体内会被绑定到{id: 42}
对象。这意味着箭头函数内部的this
也是{id: 42}
。 - 因此,
setTimeout
回调中的this.id
会正确地输出42
。
- 在这种情况下,
关键区别总结
foo()
: 直接调用时,this
是调用foo
的上下文(全局对象或undefined
),特别是在异步操作中,如setTimeout
,this
的绑定可能会与期望的上下文不同。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
),在严格模式下,this
是undefined
。
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
的值和行为。
:: 函数绑定运算符
foo::bar; //将::左边的对象作为上下文环境绑定到右边的函数上。
//等同于
bar.bind(foo);foo::bar(...arguments);
//等同于
bar.apply(foo, arguments);var method = obj::obj.foo;
//等同于
var method = ::obj.foo;let log = ::console.log;
//等同于
var log = console.log.bind(console);
相关文章:

ECMA Script 6
文章目录 DOM (Document Object Model)BOM (Browser Object Model) let 和 const 命令constObject.freeze方法跨模块常量全局对象的属性 变量的结构赋值数组结构赋值对象解构赋值字符串解构赋值数值和布尔值的解构赋值函数参数解构赋值圆括号的问题 解构赋值的用途 字符串的扩展…...

如何在不破产的情况下训练AI模型
在当今的人工智能领域,训练复杂的AI模型——特别是大型语言模型(LLM)——需要巨大的算力支持。对于许多中小型企业来说,高昂的成本常常成为一个难以逾越的障碍。然而,通过采用一些策略和最佳实践,即使是在资源有限的情况下,也能有效地训练出高质量的AI模型。本文将介绍几…...

常用开发组件Docker部署保姆级教程
说明 本文总结了一些常用组件的Docker启动命令及过程,在开发过程中只需花费数分钟下载和配置即可完美使用这些服务。 Mysql MySQL 是一种开源关系数据库管理系统(RDBMS),目前由 Oracle 公司维护。MySQL 以其高性能、可靠性和易用…...

MySql高级视频笔记
索引 索引 : 是帮助MySql高效查询数据的数据结构 优势&劣势 优势: 提高数据检索的效率, 降低数据库的IO成本通过索引列队数据进行排序, 降低数据的排序成本, 降低CPU的消耗 劣势: 索引维护了主键信息, 并指向表中数据记录, 也是占用磁盘空间的索引提高了查询效率, 但索引也…...

二十二、状态模式
文章目录 1 基本介绍2 案例2.1 Season 接口2.2 Spring 类2.3 Summer 类2.4 Autumn 类2.5 Winter 类2.6 Person 类2.7 Client 类2.8 Client 类的运行结果2.9 总结 3 各角色之间的关系3.1 角色3.1.1 State ( 状态 )3.1.2 ConcreteState ( 具体的状态 )3.1.3 Context ( 上下文 )3.…...

Spark环境搭建-Local
目录 Local下的角色分布: Anaconda On Linux 安装 (单台服务器) 1.下载安装 2.国内源 下载Spark安装包 1.下载 2.解压 3.环境变量 测试 监控 Local下的角色分布: 资源管理: Master:Local进程本身 Worker:L…...

使用FModel提取黑神话悟空的资产
使用FModel提取黑神话悟空的资产 前言设置效果展示闲聊可能遇到的问题没有相应的UE引擎版本选项 前言 黑神话悟空昨天上线了,解个包looklook。 本文内容比较简洁,仅介绍解包黑神话所需的专项配置,关于FModel的基础使用流程,请见…...

MYSQL定时任务使用手册
开发和管理数据库时,经常需要定时执行某些任务,比如每天备份数据库、每周统计报表等。MySQL提供了一个非常有用的工具,即事件调度器(Event Scheduler),可以帮助我们实现定时任务调度的功能。本文将介绍如何…...

SAP 预扣税配置步骤文档【Withholding Tax]
1. 配置预扣税的基本概念 预扣税是对某些支付进行扣除的税,可能适用于各种财务交易(例如,供应商支付、股息支付等)。预扣税通常包括几种类型,如个人所得税、企业所得税和其他税务种类。 2. 配置步骤 以下是一般的预…...

Ubuntu ssh配置
下面给出配置和使用ubuntu ssh的指南。 环境 Ubuntu22.04 安装Install sudo apt update && sudo apt upgrade sudo apt install openssh-server使用start service ssh status sudo systemctl enable --now ssh sudo ufw allow ssh连接Connect search "conn…...

Spring Boot OAuth2.0应用
本文展示Spring Boot中,新版本OAuth2.0的简单实现,版本信息: spring-boot 2.7.10 spring-security-oauth2-authorization-server 0.4.0 spring-security-oauth2-client 5.7.7 spring-boot-starter-oauth2-resource-server 2.7.10展示三个服务…...

Java | Leetcode Java题解之第363题矩形区域不超过K的最大数值和
题目: 题解: class Solution {public int maxSumSubmatrix(int[][] matrix, int k) {int ans Integer.MIN_VALUE;int m matrix.length, n matrix[0].length;for (int i 0; i < m; i) { // 枚举上边界int[] sum new int[n];for (int j i; j <…...

AI作画提示词(Prompts)工程:技巧与最佳实践
在人工智能领域,AI作画已成为一个令人兴奋的创新点,它结合了艺术与科技,创造出令人惊叹的视觉作品。本文将探讨在使用AI作画时的提示词工程,提供技巧与最佳实践。 理解AI作画 AI作画通常依赖于深度学习模型,尤其是生成…...

leetcode滑动窗口问题
想成功先发疯,不顾一切向前冲。 第一种 定长滑动窗口 . - 力扣(LeetCode)1456.定长子串中的元音的最大数目. - 力扣(LeetCode) No.1 定长滑窗套路 我总结成三步:入-更新-出。 1. 入:下标为…...

QT 控件使用案例
常用控件 表单 按钮 Push Button 命令按钮。Tool Button:工具按钮。Radio Button:单选按钮。Check Box:复选框按钮。Command Link Button:命令链接按钮。Dialog Button Box:按钮盒。 容器组控件(Containers) Group Box…...

【MySQL 10】表的内外连接 (带思维导图)
文章目录 🌈 一、内连接⭐ 0. 准备工作⭐ 1. 隐式内连接⭐ 2. 显式内连接 🌈 二、外连接⭐ 0. 准备工作⭐ 1. 左外连接⭐ 2. 右外连接 🌈 一、内连接 内连接实际上就是利用 where 子句对两张表形成的笛卡儿积进行筛选,之前所有的…...

【C语言】:与文件通信
1.文件是什么? 文件通常是在磁盘或固态硬盘上的一段已命名的存储区。C语言把文件看成一系列连续的字节,每个字节都能被单独的读取。这与UNIX环境中(C的 发源地)的文件结构相对应。由于其他环境中可能无法完全对应这个模型&#x…...

HTTPS通讯全过程
HTTPS通讯全过程 不得不说,https比http通讯更加复杂惹。在第一次接触https代码的时候,不知道为什么要用用证书,公钥是什么?私钥是什么?他们作用是什么?非对称加密和对称加密是啥?天,…...

建筑物规则化(实现) --- 特征边分组、重构、直角化
规则化建筑物 一、摘 要 建筑物多边形在地图综合中的两类处理模型:化简与直角化。 建筑物矢量数据来源广泛,在数据获取过程中,受GPS精确度、遥感影像分辨率或人为因素的影响,数据往往存在不同程度的误差。其中,图像分割、深度学习…...

pytorch的优化
在pytorch中,tensor是基于numpy与array的。内存共享。 在pythorch中,自定义层是继承nn.Module。将层与模型看成是模块,层与模型堪称模块,两者之间没有明确界限,定义方式与定义模型一样_init_与forward。 1、先定义全…...

React 入门第一天:从Vue到React的初体验
作为一名合格的前端工程师,怎么能只会Vue呢?学习React不仅是一场新技术的探索,更是对前端开发思维的一次重新审视。在这里,我将分享学习React的心得,希望能帮助那些和我一样从Vue转向React的开发者。 1. 为什么选择Re…...

Golang | Leetcode Golang题解之第357题统计各位数字都不同的数字个数
题目: 题解: func countNumbersWithUniqueDigits(n int) int {if n 0 {return 1}if n 1 {return 10}ans, cur : 10, 9for i : 0; i < n-1; i {cur * 9 - ians cur}return ans }...

【Linux】 gdb-调试器初入门(简单版使用)
🔥系列文章:《Linux入门》 目录 一、背景 二、什么是GDB 🌷定义 🌷GDB调试工具---提供的帮助 三、GDB的安装教程-Ubuntu 🌷gdb的安装 四、哪类程序可被调试 🌷程序的发布方式 🌷Debug版…...

Spring 的事务支持
文章目录 1、Spring如何管理事务2、编程式事务1_基本用法2_创建TransactionTemplate实例3_TransactionTemplate的内部结构4_总结 3、声明式事务1_使用Transactional注解2_事务的传播行为3_配置4_总结 1、Spring如何管理事务 Spring为事务管理提供了一致的编程模板,…...

基于STM32开发的智能家居照明控制系统
目录 引言环境准备工作 硬件准备软件安装与配置系统设计 系统架构硬件连接代码实现 系统初始化传感器数据采集显示与控制逻辑Wi-Fi通信应用场景 家庭智能照明办公室节能照明控制常见问题及解决方案 常见问题解决方案结论 1. 引言 智能家居照明控制系统通过集成光照传感器、继…...

程序员的底层思维~张建飞
前言 ◆ 成人学习的目的不是获取更多的信息量,而是学习更好的思维模型。 ◆ 好的思维能力是可以被复制和迁移的,它应该是普适的,而不应该有行业的界限。 第一部分 基础思维能力 ◆ 因为语言的抽象性,我在团队中会要求大家使用通用…...

美股收涨,半导体板块领涨;苹果iPhone出货预测上调
市场概况 在昨夜的交易中,美股三大股指全线收涨。道琼斯工业平均指数上涨1.39%,纳斯达克综合指数上涨2.34%,标准普尔500指数上涨1.61%。值得注意的是,英伟达股票涨幅近4%,推动了科技股的整体表现。美国十年期国债收益…...

[学习笔记]在不同项目中切换Node.js版本
文章目录 使用 Node Version Manager (NVM)安装 NVM使用 NVM 安装和切换 Node.js 版本为项目指定 Node.js 版本 使用环境变量指定 Node.js安装多个版本的 Node.js设置环境变量验证配置使用 npm 脚本切换 在开发中,可能会遇到不同的Vue项目需要不同的Node.js…...

SOL项目开发代币DApp的基本要求、模式创建与海外宣发策略
Solana(SOL)作为一个高性能区块链平台,以其快速的交易速度和低交易成本吸引了大量开发者和投资者。基于Solana开发的去中心化应用程序(DApp)和代币项目正逐步成为区块链领域的重要组成部分。要成功开发并推广一个SOL项…...

如何在 FastReport .NET 中构建和安装 Postgres 插件
FastReport .NET 是一款全功能的Windows Forms、ASP.NET和MVC报表分析解决方案。 功能非常丰富,功能广泛。今天我们将介绍如何使用报表设计器的 FastReport 插件连接数据库。 FastReport .NET 是适用于.NET Core 3,ASP.NET,MVC和Windows窗体…...