Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

JavaScript基础知识点

JavaScript 实现网站动态交互功能,提供网站动态交互性 一般应于html文档中

通常通过事件对网页进行交互

<script></script>之间的代码行包含了 JavaScript

const声明 声明作用域内的局部常量 常量的值不能通过赋值改变

var let声明 声明作用域内的局部变量 变量的值可以更改

var 可以多次声明相同名称的变量 let不需要

var 一般定义全局变量 , let一般用于局部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const number = 42;
// console.log('hello world');
// console.log(number)
try {
number = 99;
} catch (err) {
console.log(err);
}
console.log(number)
let number2 = 24;
try{
number2= 66;
} catch (err) {
console.log(err);
}
console.log(number2)
1
2
3
4
5
6
7
8
(function () {
"use strict";
function greetMe(yourName) {
alert("Hello " + yourName);
}

greetMe("World");
})();

写一个简单 Hello World 程序,这里其他的都很简单,就两个点注意下,

**”use strict”**声明为严格模式,严格模式下不能调用为定义的变量,但该声明只对于局部作用域,即只在函数内生效

1
2
3
4
5
6
7
x = 1;       // 不报错
myFunction();

function myFunction() {
"use strict";
y = 2; // 报错 (y 未定义)
}

还有一个就是

1
2
3
4
5
6
7
8
(function () {
....
function fun_namne(file) {
....
}

fun_name(name);
})();

这个结构IIFE(立即调用函数表达式),在定义是就可以执行,也叫自执行匿名函数,这个可以有效避免全局污染

JavaScript是区分大小写的

529

通过varlet定义的变量未赋值时为 undefined 数值类型环境中 undefined 值会被转换为 NaN

这里发现一个有意思的东西

530

当声明变量后,变量可以返回赋值,但进行变量提升后,变量重新被声明,覆盖前面的赋值(变量提升即先调用变量再定义函数,JavaScript对这种语法不会报错)

JavaScript对于函数的定义其实可以算有两种办法,一种就是常规的定义方法 声明为函数,另一种就是通过表达式进行赋值(函数表达式实际是定义一个匿名函数从而进行赋值)

531

这里就是对两种不同定义方法进行函数提升,对于函数声明可以进行提升,而表达式则会报错

遍历跟常见语句存在一定差别,for ... in ... 语句读取数组下标 , 遍历数组值需要使用 for ... of ...语句

532

嵌套函数:函数内嵌套另一个函数 嵌套(内部)函数对其容器(外部)函数是私有的

闭包:闭包是可以拥有独立变量以及绑定了这些变量的环境的表达式(即封闭了表达式)

既然嵌套函数是一个闭包,就意味着一个嵌套函数可以“继承”容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域

简单来说内部嵌套的函数可以访问外部函数的参数和变量,外部函数不能访问内部函数的参数和变量,但可以访问到该嵌套函数

533

用个简单的代码来看更容易理解,这里要注意一个点

534

每一次对外部函数的调用实际上重新创建了一遍闭包。只有当返回的 inside 没有再被引用时,内存才会被释放

多层嵌套后,形成多个作用域,这里称之为作用域链,而作用域链中如果存在变量冲突,采取就近原则,即越近拥有越高的优先级

535

arguments对象

函数所有的实际参数会被存储到arguments对象,其类似一个数组

JavaScript存在连个特殊参数:默认参数剩余参数

forEach()方法提供了遍历数组元素的其他方法

536

但是遍历过程中遇到默认参数,元素不会被输出

稀疏数组中删除元素,元素删除,位置保留

537

在 JavaScript 中 objects 是一种引用类型。两个独立声明的对象永远也不会相等,即使他们有相同的属性,只有在比较一个对象和这个对象的引用时,才会返回 true

538

JavaScript 私有字段是以 #(井号)开头的标识符号是这个字段名的必要部分,这也就意味着私有字段永远不会与公共属性发生命名冲突。为了在类中的任何地方引用一个私有字段,你必须在类体中声明它(你不能在类体外部创建私有字段)。除此之外,私有字段与普通属性几乎是等价的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Color {
#values;
constructor(r, g, b, a = 1) {
this.#values = [r, g, b, a];
}
get alpha() {
return this.#values[3];
}
set alpha(value) {
if (value < 0 || value > 1) {
throw new RangeError("Alpha 值必须在 0 与 1 之间");
}
this.#values[3] = value;
}
}

JavaScript中继承有一个要注意的点,继承父类的析构函数时,需要先调用super()来初始化 , this 这里大致相当于 this = new Color(r, g, b) super() 之前也可以有代码,但你不能在 super() 之前访问 this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ColorWithAlpha extends Color {
#alpha;
constructor(r, g, b, a) {
super(r, g, b);
this.#alpha = a;
}
get alpha() {
return this.#alpha;
}
set alpha(value) {
if (value < 0 || value > 1) {
throw new RangeError("Alpha 值必须在 0 与 1 之间");
}
this.#alpha = value;
}
}

链式调用

1
2
3
4
5
6
7
doSomething(function (result) {
doSomethingElse(result, function (newResult) {
doThirdThing(newResult, function (finalResult) {
console.log(`得到最终结果:${finalResult}`);
}, failureCallback);
}, failureCallback);
}, failureCallback);

代码中同时调用多个方法时会出现判断语句过于冗杂的问题(一般称之为回调地狱)在JavaScript中提供了一个很好的办法Promise对象

1
2
const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);

每个 Promise 都代表了链中的一个异步过程的完成

1
function(参数).then(function1,function2)

链式调用需要在链子的各个部分存在返回值,从而为下一个回调函数提供参数,若存在条件竞争状态, 下一个then会被提前调用

嵌套是一种可以限制 catch 语句的作用域的控制结构写法,嵌套的 catch 只会捕获其作用域及以下的错误,而不会捕获链中更高层的错误

1
2
3
4
5
6
7
8
doSomethingCritical()
.then((result) =>
doSomethingOptional()
.then((optionalResult) => doSomethingExtraNice(optionalResult))
.catch((e) => {}),
) // 即便可选操作失败了,也会继续执行
.then(() => moreCriticalStuff())
.catch((e) => console.log(`严重失败:${e.message}`));

JavaScript为了最大程度的灵活性和效率,将类型化数组的实现拆分为缓冲和视图两部分,缓冲是一种表示了数据块的对象,它没有格式可言,也没有提供访问其内容的机制,视图提供了上下文——即数据类型、起始偏移量和元素数量

缓冲分为两种类型

ArrayBuffer 对象用来表示通用的原始二进制数据缓冲区,将 ArrayBuffer 传递给另一个执行上下文,它会被转移,原始的 ArrayBuffer 将不再可用

SharedArrayBuffer 对象用来表示一个通用的原始二进制数据缓冲区,类似于 ArrayBuffer对象,但它可以用来在共享内存上创建视图。与可转移的 ArrayBuffer 不同,SharedArrayBuffer 不是可转移对象

缓冲无法直接读取,它只包含原始数据,需要通过视图来访问缓冲所表示的内存

缓冲支持以下操作:

  • 分配:创建一个新的缓冲时,会分配一个新的内存块,并且初始化为 0
  • 复制:使用 slice()方法,你可以高效地复制缓冲中的一部分数据,而不需要创建视图来手动复制每一个字节。
  • 转移:使用 transfer()transferToFixedLength()方法,可以将内存块的所有权转移给一个新的缓冲对象。若你想要在不同的执行上下文间转移数据,又不想复制,这些方法就很有用。转移后,原始缓冲将不再可用。SharedArrayBuffer 不能被转移(因为缓冲已经被所有执行上下文共享)。
  • 调整大小:使用 resize()方法,可以调整内存块的大小(只要不超过预设的 maxByteLength限制)。SharedArrayBuffer 只能增长,不能缩小。

视图主要也分为两种 类型化数组DataView

类型化数组可以方便地转换二进制数据 DataView 更底层,可以精确控制数据的访问方式

buffer视图所引用的底层缓冲。

byteOffset视图相对于缓冲起始位置的偏移量(以字节为单位)。

byteLength视图的长度(以字节为单位)

迭代器

在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能附带一个返回值,其返回值具有两个属性

value

迭代序列的下一个值

done

如果已经迭代到序列中的最后一个值,则它为 true,如果 valuedone 一起出现,则它就是迭代器的返回值

迭代器创建好后,迭代器对象可以通过重复调用 next() 显式地迭代,在产生终值后,对 next() 的额外调用应该继续返回 {done:true}

迭代器可以表示无限大小的序列,例如 0 和 Infinity(全局属性 Infinity 是一个数值,表示无穷大)之间的整数范围

这里就用一个简单的例子来方便理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function makeRangeIterator(start = 0, end = Infinity, step = 1) {
let nextIndex = start;
let iterationCount = 0;

const rangeIterator = {
next() {
let result;
if (nextIndex < end) {
result = { value: nextIndex, done: false };
nextIndex += step;
iterationCount++;
return result;
}
return { value: iterationCount, done: true };
},
};
return rangeIterator;
}

简单理解一下,其实迭代器就是一次遍历,每一次遍历都会对一个变量进行重新赋值,从而达到迭代的效果,同时确定输出值,但由于JavaScript的特性使得可以将方法赋值,所以称之为迭代器,

生成器

生成器其实是迭代器的一种强大替换,它允许你定义一个非连续执行的函数作为迭代算法

生成器函数使用 function*语法(**function*** 声明创建一个绑定到给定名称的新生成器函数。生成器函数可以退出,并在稍后重新进入,其上下文(变量绑定)会在重新进入时保存)编写

539

这样的话,我们优化一下前面那个函数定义

1
2
3
4
5
6
7
8
function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
let iterationCount = 0;
for (let i = start; i < end; i += step) {
iterationCount++;
yield i;
}
return iterationCount;
}

每调用一次,虽然都返回一个新的生成器,但每个生成器只能迭代一次,都是变量的值绑定,由上一次调用时改变的值确定

540

最初调用时,生成器函数不执行任何代码,而是返回一种称为生成器的特殊迭代器。通过调用 next() 方法消耗该生成器时,生成器函数将执行,遇到遇到 yield 关键字进行返回

对象实现可迭代必须具有一个键值为 Symbol.iterator的属性(**Symbol.iterator** 为每一个对象定义了默认的迭代器。该迭代器可以被 for...of 循环使用)

541

然后我们利用for...of 循环去进行迭代

542

这个其实有一个在其他语言中不常见的语法,来展开迭代[...""],通过这种方法来展开数组

543

生成器的 return() 方法可返回给定的值并终结这个生成器

闭包

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment词法环境)的引用的组合,闭包让开发者可以从内部函数访问外部函数的作用域

1
2
3
4
5
6
7
8
9
10
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}

var myFunc = makeFunc();
myFunc();

内部函数 displayName() 在执行前,从外部函数返回,这里就可以很好的理解闭包的作用, 闭包是由函数以及声明该函数的词法环境组合而成的,该环境包含了这个闭包创建时作用域内的任何局部变量

displayName 的实例维持了一个对它的词法环境(变量 name 存在于其中)的引用,因此,当 myFunc 被调用时,变量 name 仍然可用,其值 Mozilla 就被传递到alert

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function showHelp(help) {
document.getElementById("help").innerHTML = help;
}

function setupHelp() {
var helpText = [
{ id: "email", help: "Your e-mail address" },
{ id: "name", help: "Your full name" },
{ id: "age", help: "Your age (you must be over 16)" },
];

for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function () {
showHelp(item.help);
};
}
}

setupHelp();

数组 helpText 中定义了三个有用的提示信息,每一个都关联于对应的文档中的 input 的 ID,但无论焦点在哪个 input 上,显示的都是关于年龄的信息

原因是赋值给 onfocus 的是闭包。这些闭包是由他们的函数定义和在 setupHelp 作用域中捕获的环境所组成的。这三个闭包在循环中被创建,但他们共享了同一个词法作用域,在这个作用域中存在一个变量 item。这是因为变量 item 使用 var 进行声明,由于变量提升,所以具有函数作用域。当 onfocus 的回调执行时,item.help 的值被决定。由于循环在事件触发之前早已执行完毕,变量对象 item(被三个闭包所共享)已经指向了 helpText 的最后一项

这里进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function showHelp(help) {
document.getElementById("help").innerHTML = help;
}

function makeHelpCallback(help) {
return function () {
showHelp(help);
};
}

function setupHelp() {
var helpText = [
{ id: "email", help: "Your e-mail address" },
{ id: "name", help: "Your full name" },
{ id: "age", help: "Your age (you must be over 16)" },
];

for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
}
}

setupHelp();

这里就是利用JavaScript的特性,前面也有提到每调用一次都生成一个独立的环境,从而使得所有回调不再共享一个环境

利用匿名闭包也可以解决这一问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function showHelp(help) {
document.getElementById("help").innerHTML = help;
}

function setupHelp() {
var helpText = [
{ id: "email", help: "Your e-mail address" },
{ id: "name", help: "Your full name" },
{ id: "age", help: "Your age (you must be over 16)" },
];

for (var i = 0; i < helpText.length; i++) {
(function () {
var item = helpText[i];
document.getElementById(item.id).onfocus = function () {
showHelp(item.help);
};
})(); // 马上把当前循环项的 item 与事件回调相关联起来
}
}

setupHelp();

原型链

每个对象,都有一个私有属性指向另一个名为原型(prototype)的对象,原型对象也有一个自己的原型,层层向上直到一个对象的原型为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节

用一个简单的例子来解释原型链

1
2
3
4
5
6
7
8
const o = {
a: 1,
b: 2,
__proto__: {
b: 3,
c: 4,
},
};
1
{ a: 1, b: 2 } ---> { b: 3, c: 4 } ---> Object.prototype ---> null

o具有属性 a 和 b

o.[[Prototype]] 具有属性 b 和 c

**o.[[Prototype]].[[Prototype]] **是 Object.prototype

**o.[[Prototype]].[[Prototype]].[[Prototype]] **是 null

548

当对象本身找不到相应属性时,就会去检查其原型,知道检索到null为止,为搜索到该属性,返回undefined

还有一个点就是,检索会优先访问本身的属性,当本身属性和原型的属性相冲突时,本身属性优先级高于原型属性, 从而实现属性遮蔽

549

1
2
3
4
5
6
7
8
9
function Box(value) {
this.value = value;
}

Box.prototype.getValue = function () {
return this.value;
};

const boxes = [new Box(1), new Box(2), new Box(3)];

一些隐式构造

551550

默认情况下,Constructor.prototype 是一个普通对象——即 Object.getPrototypeOf(Constructor.prototype) === Object.prototype。唯一的例外是 Object.prototype 本身,其 [[Prototype]]null——即 Object.getPrototypeOf(Object.prototype) === null

1
2
3
4
function Constructor() {}

const obj = new Constructor();
// obj ---> Constructor.prototype ---> Object.prototype ---> null

552

原型链构造

语法构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const o = { a: 1 };
// 新创建的对象 o 以 Object.prototype 作为它的 [[Prototype]]
// Object.prototype 的原型为 null。
// o ---> Object.prototype ---> null

const b = ["yo", "whadup", "?"];
// 数组继承了 Array.prototype(具有 indexOf、forEach 等方法)
// 其原型链如下所示:
// b ---> Array.prototype ---> Object.prototype ---> null

function f() {
return 2;
}
// 函数继承了 Function.prototype(具有 call、bind 等方法)
// f ---> Function.prototype ---> Object.prototype ---> null

const p = { b: 2, __proto__: o };
// 可以通过 __proto__ 字面量属性将新创建对象的
// [[Prototype]] 指向另一个对象。
// (不要与 Object.prototype.__proto__ 访问器混淆)
// p ---> o ---> Object.prototype ---> null

析构函数

1
2
3
4
5
6
7
8
9
10
11
12
function Graph() {
this.vertices = [];
this.edges = [];
}

Graph.prototype.addVertex = function (v) {
this.vertices.push(v);
};

const g = new Graph();
// g 是一个带有自有属性“vertices”和“edges”的对象。
// 在执行 new Graph() 时,g.[[Prototype]] 是 Graph.prototype 的值。

Object.create()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const a = { a: 1 };
// a ---> Object.prototype ---> null

const b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (inherited)

const c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

const d = Object.create(null);
// d ---> null(d 是一个直接以 null 为原型的对象)
console.log(d.hasOwnProperty);
// undefined,因为 d 没有继承 Object.prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}

class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}

get area() {
return this.height * this.width;
}

set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}

const square = new Square(2);
// square ---> Square.prototype ---> Polygon.prototype ---> Object.prototype ---> null

Object.setPrototypeOf()

1
2
3
4
const obj = { a: 1 };
const anotherObj = { b: 2 };
Object.setPrototypeOf(obj, anotherObj);
// obj ---> anotherObj ---> Object.prototype ---> null

proto

1
2
3
4
5
6
const obj = {};
// 请不要使用该方法:仅作为示例。
obj.__proto__ = { barProp: "bar val" };
obj.__proto__.__proto__ = { fooProp: "foo val" };
console.log(obj.fooProp);
console.log(obj.barProp);

评论