JavaScript 笔记
知识点
DOM
文档对象模型是一个应用编程接口,用于在HTML中使用扩展的XML。DOM将整个页面抽象为一组分层节点。HTML或XML页面的每个组成部分都是一种节点,包含不同的数据。
BOM
浏览器对象模型,用于支持访问和操作浏览器的窗口。使用BOM,开发者可以操控浏览器显示页面之外的部分。
变量
var 关键字
1. var 声明作用域
使用var操作符定义的变量会成为包含它的函数的局部变量。比如,使用var在一个函数内容定义一个变量,就意味着该变量将在函数退出时被销毁。
例如:
function jsTest() {
console.log(num);
var num = 1;
}
jsTest();
之所以不会报错,是因为ECMAScript运行时把它看成等价于如下代码:
function jsTest() {
var num;
console.log(num);
num = 1;
}
jsTest();
2. var 声明提升
所谓的“提升”,也就是把所有变量声明都拉到函数作用域的顶部。此外,反复多次使用var声明同一个变量也没有问题。
let 关键字
let跟var的作用差不多,但有着非常重要的区别。最明显的区别是,let声明的方位是块作用域,而var声明的范围是函数作用域。
let不允许同一块作用域中出现冗余声明。这样会导致报错:
let name;
let name;
let 与 var 区别
1. 暂时性死区
let 与 var 的重要区别就是let声明的变量不会在作用域中被提升。
在解析代码时,JavaScript引擎也会注意出现在块后面的let声明,只不过在此之前不能以任何方式来引用未声明的变量。在let声明之前的执行瞬间被称为“暂时性死区”,在此阶段引用任何后面才声明的变量都会抛出ReferenceError。
2. 全局声明
与var关键字不同,使用let在全局作用域中声明的变量不会成为window对象的属性(var声明的变量则会)。
var name = "张三";
console.log(window.name);
let name = "张三";
console.log(window.name);
const 声明
const 的行为与let 基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误。
数据类型
ECMAScript一共有7种数据类型,包括:
6种简单数据类(也称为原始类型):Undefined、Null、Boolean、Number、String 和 Symbol。
1种复杂数据类型叫Object(对象)。
元素
<script>
元素
属性:
将JavaScript插入HTML的主要方式是使用<script>元素。
<script> 元素有以下8个属性:
1. async:可选。表示应该立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或等待其他脚本加载。只对外部文件有效。
2. charset:可选。使用src属性指定的代码字符集。这个属性很少使用,因为大多数浏览器不会在乎它的值。
3. crossorigin:可选。配置相关请求的CORS(跨域资源共享)设置。默认不使用COPS。crossorigin=“anonymous”配置文件请求不必设置凭据标志。crossorigin=“use-credentials”设置凭据标志,意味着出站请求会包含凭据。
4. defer:可选。允许接收到的资源的签名与这个属性指定的签名不匹配,则页面会报错,脚本不会执行。这个属性可以用于确保内容分发网络不会提供恶意内容。
5. language:废弃。最初用于标识代码块中的脚本语言(如“JavaScript”,“JavaScript 1.2” 或“VBScript”)。大多数浏览器都会忽略这个属性,不应该在使用它。
6. src:可选。表示包含执行的代码的外部文件。
7. type:可选。代替language,表示代码块中脚本语言的内容类型(也称MIME类型)。按照惯例,这个值始终都是“text/javascript”,尽管“text/javascript”和“text/ecmascript”都已经废弃了。JavaScript文件的MIME类型通常是“application/x-javascript”,不过给type属性这个值很有可能导致脚本被忽略。在IE的浏览器中有效的其他值还有“application/javascript”和“application/ecmascript”。如果这个值是module,则代码会被当成ES6模块,而且只有这时候代码中才能出现import和export关键字。
使用<script>的方式有两种:
1. 通过它直接在网页中嵌入JavaScript代码
2. 通过它在网页中包含外部JavaScript文件
动态加载脚本
只要创建一个 script 元素并将其添加到DOM即可。
let script = document.createElement('script');
script.src = 'test.js';
document.head.appendChild(script);
以这种方式获取的资源对浏览器预加载器是不可见的。这会严重影响它们在资源获取队列中的优先级。根据应用程序的工作方式以及怎么使用,这种方式可能会严重影响性能。要想让预加载器知道这些动态请求文件的存在,可以在稳当头部显示的声明它们:
<link rel="preload" href="test.js" />
<noscript>
元素
<noscript>元素的出现,被用于给不支持JavaScript的浏览器提供替代内容。虽然如今的浏览器已经100%指出JavaScript,单对于禁用JavaScript的浏览器来说,这个元素仍然有它的用处。
<noscript>元素可以包含泳衣可以出现在<body>中的HTML元素,<script>除外。
在下列两种情况下,浏览器将显示包含在<noscript>中的内容:
1. 浏览器不支持脚本
2. 浏览器对脚本的支持被关闭
JavaScript
with 语句
with 语句的用途是将代码作用域设置为特点的对象。
使用with语句的主要场景是针对一个对象反复操作,这时候将代码作用域设置为该对象能提供遍历,如下:
const data = {
value: {
name: '张三',
age: 20,
sex: '男'
}
}
var user = {
name: data.value.name,
age: data.value.age,
sex: data.value.sex
}
console.log(user);
上面代码中都有data.value对象,如果使用with语句,就可以少些一些代码:
const data = {
value: {
name: '张三',
age: 20,
sex: '男'
}
}
with (data.value) {
var user = { name, age, sex }
}
console.log(user);
数组的复制和填充方法
ES6新增了两个方法:批量复制方法 copyWithin(),以及填充数组方法 fill()。这两个方法的函数签名类似,都需要指定既有数组实例上的一个范围,包含开始索引,不包含结束索引。使用这个方法不会改变数组的大小。
例如:
const datas = [0, 0, 0, 0, 0];
datas.fill(5, 1, 3);
console.log(datas);
例如:
const datas = [0, 1, 2, 3, 4, 5, 6, 7];
console.log(datas.copyWithin(2, 0, 3));
改变this指向的方法
方法一:call
const user = {
name: '略略略'
}
function fn(x, y) {
console.log(x, y);
console.log(this.name);
}
fn.call(user, 1, 2);
方法二:apply
apply() 与call() 非常相似, 不同之处在于提供参数的方式, apply() 使用参数数组, 而不是参数列表
const user = {
name: '略略略'
}
function fn(x, y) {
console.log(this.name);
}
fn.apply(user, [1, 2]);
方法三:bind
bind() 创建的是一个新的函数( 称为绑定函数), 与被调用函数有相同的函数体, 当目标函数被调用时this的值绑定到 bind() 的第一个参数上
const user = {
name: '略略略'
}
function fn(x, y) {
console.log(this.name);
}
fn.bind(user, 1, 2);
fn.bind(user, 1, 2)();
创建对象的方式
let person = new Object();
person.name = '略略略';
person.age = 20;
person.sayName = function() {
console.log(this.name);
}
let person = {
name: '略略略';
age: 20;
sayName() {
console.log(this.name);
}
}
数据属性
1. [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特
性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特
性都是 true,如前面的例子所示。
2. [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对
象上的属性的这个特性都是 true,如前面的例子所示。
3. [[Writable]]:表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的
这个特性都是 true,如前面的例子所示。
4. [[Value]]:包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性
的默认值为 undefined。
这个方法接收 3 个参数:要给其添加属性的对象、属性的名称和一个描述符对象。
最后一个参数,即描述符对象上的属性可以包含:configurable、enumerable、writable 和 value,跟相关特性的名称一一对应。
let person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
console.log(person.name);
person.name = "Greg";
console.log(person.name);
let book = {};
Object.defineProperties(book, {
year_: {
value: 2017
},
edition: {
value: 1
},
year: {
get() {
return this.year_;
},
set(newValue) {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
}
});
对象的合并
const obj1 = {
id: 1
}
const obj2 = {
name: '略略略'
}
const obj3 = {
age: 20
}
const result = Object.assign(obj1, obj2, obj3);
console.log(result === obj1);
console.log(result === obj2);
console.log(result === obj3);
对象标识及相等判定
代理与反射
代理
代理是使用 Proxy 构造函数创建的。
这个构造函数接收两个参数:目标对象和处理程序对象。
缺少其中任何一个参数都会抛出 TypeError。
要创建空代理,可以传一个简单的对象字面量作为处理程序对象,从而让所有操作畅通无阻地抵达目标对象。
const person = {
name: '略略略'
}
const handle = {}
const proxy = new Proxy(person, handle);
proxy.id = 1;
console.log(proxy.id);
console.log(person.id);
定义捕获器
使用代理的主要目的是可以定义捕获器(trap)。
捕获器就是在处理程序对象中定义的“基本操作的拦截器”。
每个处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接或间接在代理对象上调用。
每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。
const person = {
name: '略略略'
}
const handle = {
get() {
return 'Hello';
}
}
const proxy = new Proxy(person, handle);
console.log(person.name);
console.log(proxy.name);
所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为。
比如,get()捕获器会接收到目标对象、要查询的属性和代理对象三个参数。
const handle = {
get(trapTarget, property, receiver) {
return trapTarget[property];
}
}
所有捕获器都可以基于自己的参数重建原始操作,但并非所有捕获器行为都像 get()那么简单。
因此,通过手动写码如法炮制的想法是不现实的。
实际上,开发者并不需要手动重建原始行为,而是可以通过调用全局 Reflect 对象上(封装了原始行为)的同名方法来轻松重建。
甚至还可以写得更简洁一些:
const handle = {
get: Reflect.get,
}
事实上,如果真想创建一个可以捕获所有方法,然后将每个方法转发给对应反射 API 的空代理,那么甚至不需要定义处理程序对象:
const proxy = new Proxy(person, Reflect);
可撤销代理
有时候可能需要中断代理对象与目标对象之间的联系。
对于使用 new Proxy()创建的普通代理来说,这种联系会在代理对象的生命周期内一直持续存在。
Proxy 也暴露了 revocable()方法,这个方法支持撤销代理对象与目标对象的关联。
撤销代理的操作是不可逆的。
而且,撤销函数(revoke())是幂等的,调用多少次的结果都一样。
撤销代理之后再调用代理会抛出 TypeError。
const person = {
name: '略略略'
}
const handle = {
get: Reflect.get,
}
const { proxy, revoke } = Proxy.revocable(person, handle);
revoke();
console.log(person.name);
console.log(proxy.name);
new.target
ECMAScript 中的函数始终可以作为构造函数实例化一个新对象,也可以作为普通函数被调用。
ECMAScript 6 新增了检测函数是否使用 new 关键字调用的 new.target 属性。
如果函数是正常调用的,则 new.target 的值是 undefined;
如果是使用 new 关键字调用的,则 new.target 将引用被调用的构造函数。
function King() {
if (!new.target) {
throw '没有使用new调用'
}
console.log('使用new调用了');
}
new King();
King();
BOM
导航与打开新窗口
window.open()方法可以用于导航到指定 URL,也可以用于打开新浏览器窗口。
这个方法接收 4个参数:要加载的 URL、目标窗口、特性字符串和表示新窗口在浏览器历史记录中是否替代当前加载页面的布尔值。
通常,调用这个方法时只传前 3 个参数,最后一个参数只有在不打开新窗口时才会使用。
如果 window.open()的第二个参数是一个已经存在的窗口或窗格(frame)的名字,则会在对应的窗口或窗格中打开 URL。