js.md
script 元素
-
async: 会立即下载次脚本,不会妨碍阻塞其他操作,比如下载其他资源和加载其他脚本仅对外部文件有效
-
defer: 表示 脚本可以延迟到文档完全被解析和显示之后再执行;仅对外部脚本有效,兼容性好
基本概念
typeof
var message = "some string";
alert(typeof message); // "string"
alert(typeof(message)); // "string"
alert(typeof 95); // "number"
- typeof 操作符 是一个 操作符 不是函数;
null
null值 表示一个空对象指针所以使用typeof操作符检测时会返回“object”
var car = null;
alert(typeof car); // "object"
if (car != null){
//
}
alert(null == undefined); //true
如果定义的变量将来用来保存对象,最好将该变量初始化为null而不是其他只;只要检测null值就额可以知道相应的变量是否已经保存了一个对象的引用if (car != null)。
无论什么情况下都没必要将一个变量显示地设置为undefined.
Number类型
if (a + b == 0.3){ // 不要做这样的测试, 因此, 永远不要测试某个特点的浮点数值。
alert("You got 0.3.");
}
数值转换
有三个函数可以把非数值转换为数值:Number(). parseInt(), parseFloat()
-
Number() 可以用于任何数据类型;parseInt(), parseFloat()则专门用于把字符串转换成数值。
-
Number().parseInt() 具有第二个参数,表示进制,parseFloat()只解析十进制因此没有第二个参数
-
Number:
- 布尔值ture false 返回 1、0
- null/"" => 0
- undefined => NaN
- 字符串:
var num1 = Number("Hello world!"); //NaN var num2 = Number(""); //0 var num3 = Number("000011"); //11 var num4 = Number(true); //1
Number转换各种数据类型确实有点复杂且不够合理,因此在处理整数的时候更常用parseInt()
-
parseInt(): 转换时,他会 忽略字符串前面的空格,如果第一个字符不是数字或者负号,就会返回NaN。
-
toString() / String()
-
数值。布尔。对象和字符串都有toString()方法,null/ undefined 没有这个方法
toString()里面可以传 基数,表示进制 -
在不知是null,undefined。下 可以用
String()
,他能够将任何类型的值转换为字符串,遵循(如果有toString()则调,如果是null,undefined则返回"null,undefined") -
值转字符串也可以用
+ ""
var value1 = 10;
var value2 = true;
var value3 = null;
var value4;
alert(String(value1)); // "10"
alert(String(value2)); // "true"
alert(String(value3)); // "null"
alert(String(value4)); // "undefined"
一元加和减操作符
var num = 25;
num = +num; // 25
- 一元加 +放在数值前面不会对数值产生任何影响;在对非数值时,则会像Number()一样进行转换。
var s1 = "01";
var s2 = "1.1";
var s3 = "z";
var b = false;
var f = 1.1;
var o = {
valueOf: function() {
return -1;
}
};
s1 = +s1; // 1
s2 = +s2; // 1.1
s3 = +s3; // NaN
b = +b; // 0
f = +f; // 1.1
o = +o; // -1
- 一元减
var num = 25;
num = -num; // -25 - 在对数值时会将其变为负数,在对非数值时规则同一元加最后再得到的数值转换为负数。
***传递参数 ***
ECMAScript 中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数
中,就像从一个变量复制到另一个变量一样。
***变量声明 ***
- 使用 var 的函数作用域声明
在使用 var 声明变量时,变量会被自动添加到最接近的上下文。在函数中,最接近的上下文就是函
数的局部上下文。在 with 语句中,最接近的上下文也是函数上下文。如果变量未经声明就被初始化了,
那么它就会自动被添加到全局上下文,
Date
比如,要创建一个表示“2019 年 5 月 23 日”的日期对象,可以使用以下代码:
let someDate = new Date(Date.parse(“May 23, 2019”));
如果传给 Date.parse()的字符串并不表示日期,则该方法会返回 NaN。如果直接把表示日期的字
符串传给 Date 构造函数,那么 Date 会在后台调用 Date.parse()。换句话说,下面这行代码跟前面
那行代码是等价的:
let someDate = new Date(“May 23, 2019”);
这两行代码得到的日期对象相同。
ECMAScript 还提供了 Date.now()方法,返回表示方法执行时日期和时间的毫秒数。
RegExp
g:全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束。
i:不区分大小写,表示在查找匹配时忽略 pattern 和字符串的大小写。
m:多行模式,表示查找到一行文本末尾时会继续查找。
y:粘附模式,表示只查找从 lastIndex 开始及之后的字符串。
u:Unicode 模式,启用 Unicode 匹配。
s:dotAll 模式,表示元字符.匹配任何字符(包括\n 或\r)。
// 匹配字符串中的所有"at"
let pattern1 = /at/g;
// 匹配第一个"bat"或"cat",忽略大小写
let pattern2 = /[bc]at/i;
// 匹配所有以"at"结尾的三字符组合,忽略大小写
let pattern3 = /.at/gi;
所有元字符在模式中也必须转义,包括:( [ { \ ^ $ | ) ] } ? * + .
slice()、substr()和 substring()
ECMAScript 提供了 3 个从字符串中提取子字符串的方法:slice()、substr()和 substring()。这
3个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。第一个参数表示子字符串开
始的位置,第二个参数表示子字符串结束的位置。对 slice()和 substring()而言,第二个参数是提取结
束的位置(即该位置之前的字符会被提取出来)。对 substr()而言,第二个参数表示返回的子字符串数量。
任何情况下,省略第二个参数都意味着提取到字符串末尾。与 concat()方法一样,slice()、substr()
和 substring()也不会修改调用它们的字符串,而只会返回提取到的原始新字符串值
let stringValue = “hello world”;
console.log(stringValue.slice(3)); // “lo world”
console.log(stringValue.substring(3)); // “lo world”
console.log(stringValue.substr(3)); // “lo world”
console.log(stringValue.slice(3, 7)); // “lo w”
console.log(stringValue.substring(3,7)); // “lo w”
console.log(stringValue.substr(3, 7)); // “lo worl”
trim()方法
ECMAScript 在所有字符串上都提供了 trim()方法。这个方法会创建字符串的一个副本,删除前、
后所有空格符,再返回结果。比如:
let stringValue = " hello world ";
let trimmedStringValue = stringValue.trim();
console.log(stringValue); // " hello world "
console.log(trimmedStringValue); // “hello world”
由于 trim()返回的是字符串的副本,因此原始字符串不受影响,即原本的前、后空格符都会保留。
另外,trimLeft()和 trimRight()方法分别用于从字符串开始和末尾清理空格符。
repeat()方法
ECMAScript 在所有字符串上都提供了 repeat()方法。这个方法接收一个整数参数,表示要将字
符串复制多少次,然后返回拼接所有副本后的结果。
let stringValue = "na ";
console.log(stringValue.repeat(16) + “batman”);
// na na na na na na na na na na na na na na na na batman
toLocaleLowerCase()和 toLocaleUpperCase()方法旨在基于
特定地区实现。在很多地区,地区特定的方法与通用的方法是一样的。但在少数语言中(如土耳其语),
Unicode 大小写转换需应用特殊规则,要使用地区特定的方法才能实现正确转换。
let stringValue = “hello world”;
console.log(stringValue.toLocaleUpperCase()); // “HELLO WORLD”
console.log(stringValue.toUpperCase()); // “HELLO WORLD”
console.log(stringValue.toLocaleLowerCase()); // “hello world”
console.log(stringValue.toLowerCase()); // “hello world”
如果不知道代码涉及什么语言,则最好使用地区特定的转换方法。
URL 编码方法
这两个方法的主要区别是,encodeURI()不会编码属于 URL 组件的特殊字符,比如冒号、斜杠、问号、
井号,而 encodeURIComponent()会编码它发现的所有非标准字符。来看下面的例子:
let uri = “http://www.wrox.com/illegal value.js#start”;
// “http://www.wrox.com/illegal%20value.js#start”
console.log(encodeURI(uri));
// “http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start”
console.log(encodeURIComponent(uri));
这里使用 encodeURI()编码后,除空格被替换为%20 之外,没有任何变化。而 encodeURIComponent()方法将所有非字母字符都替换成了相应的编码形式。这就是使用 encodeURI()编码整个
URI,但只使用 encodeURIComponent()编码那些会追加到已有 URI 后面的字符串的原因。
Math
let max = Math.max(1,2,4) // 4
let max = Math.min(1,2,4) // 1
let values = [1, 2, 3, 4, 5, 6, 7, 8];
let max = Math.max(…values)
function selectFrom(lowerValue, upperValue) {
let choices = upperValue - lowerValue + 1;
return Math.floor(Math.random() * choices + lowerValue);
}
let num = selectFrom(2,10);
console.log(num); // 2~10 范围内的值,其中包含 2 和 10
try/catch 语句
即使在 catch 块中不使用错误对象,也必须为它定义名称。错误对象中暴露的实际信息因浏览器而异,但至少包含保存错误消息的 message
属性。
try {
window.someFunction();
} catch (error){
console.log(error.message);
}
message 属性是唯一一个在 IE、Firefox、Safari、
Chrome 和 Opera 中都有的属性,
- finally 子句
try 或 catch 块无法阻止 finally 块执行,包括 return 语句。
- 错误类型
ECMA-262 定义了以下 8 种错误类型:
Error
InternalError
EvalError
RangeError
ReferenceError
SyntaxError
TypeError
URIError
Error 是基类型,其他错误类型继承该类型。因此,所有错误类型都共享相同的属性(所有错误对象上的方法都是这个默认类型定义的方法)。浏览器很少会抛出 Error 类型的错误,该类型主要用于开发者抛出自定义错误。
InternalError 例如,递归过多导致了栈溢出。这个类型并不是代码中通常要处理的错误
EvalError 类型的错误会在使用 eval()函数发生异常时抛出。几乎不出现。
RangeError 错误会在数值越界时抛出。例如,定义数组时如果设置了并不支持的长度。
ReferenceError 会在找不到对象时发生。(这就是著名的"object expected"浏览器错误的原因。)这种错误经常是由访问不存在的变量而导致的,比如:
let obj = x; // 在 x 没有声明时会抛出 ReferenceError
SyntaxError 经常在给 eval()传入的字符串包含 JavaScript 语法错误时发生
TypeError 在 JavaScript 中很常见,主要发生在变量不是预期类型,或者访问不存在的方法时。很
多原因可能导致这种错误,尤其是在使用类型特定的操作而变量类型不对时。
URIError,只会在使用 encodeURI()或 decodeURI()但传入了格式错误的URI 时发生。
这个错误恐怕是 JavaScript 中难得一见的错误了,因为上面这两个函数非常稳健。
在 try/catch 语句
的 catch 块中,可以使用 instanceof 操作符确定错误的类型,比如:
try {
someFunction();
} catch (error){
if (error instanceof TypeError){
// 处理类型错误
} else if (error instanceof ReferenceError){
// 处理引用错误
} else {
// 处理所有其他类型的错误
}
}
自定义错误类型,需要提供 name , message 属性
class customError extends Error{
constructor(message){
super(message)
this.name = name;
this.message = message;
}
}
throw new CustomError(“my message”);
把错误记录到服务器中
function logError(sev, msg) {
let img = new Image(),
encodedSev = encodeURIComponent(sev),
encodedMsg = encodeURIComponent(msg);
img.src = ‘log.php?sev=KaTeX parse error: Expected 'EOF', got '&' at position 13: {encodedSev}&̲msg={encodedMsg}’;
}
logError()函数接收两个参数:严重程度和错误消息。严重程度可以是数值或字符串,具体取决
于使用的日志系统。这里使用 Image 对象发送请求主要是从灵活性方面考虑的。所有浏览器都支持 Image 对象,
即使不支持 XMLHttpRequest 对象也一样。不受跨域规则限制。通常,接收错误消息的应该是多个服务器中的一个,而 XMLHttpRequest
此时就比较麻烦。
记录错误的过程很少出错。大多数 Ajax 通信借助 JavaScript 库的包装来处理。如果这个库本身
出错,而你又要利用它记录错误,那么显然错误消息永远不会发给服务器。
只要是使用 try/catch 语句的地方,都可以把相关错误记录下来
####抛出错误
function divide(num1, num2) {
if (typeof num1 != “number” || typeof num2 != “number”){
throw new Error(“divide(): Both arguments must be numbers.”);
}
return num1 / num2;
}
在大型应用程序中,自定义错误通常使用 assert()函数抛出错误。这个函数接收一个应该为 true
的条件,并在条件为 false 时抛出错误。下面是一个基本的 assert()函数:
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
function divide(num1, num2) {
assert(typeof num1 == “number” && typeof num2 == “number”,
“divide(): Both arguments must be numbers.”);
return num1 / num2;
}
客户端存储
- cookie
cookie 是与特定域绑定的。设置 cookie 后,它会与请求一起发送到创建它的域。这个限制能保证
cookie 中存储的信息只对被认可的接收者开放,不被其他域访问。
集合引用类型
- object
console.log(person[“name”]); // “Nicholas”
console.log(person.name); // “Nicholas”
从功能上讲,这两种存取属性的方式没有区别。使用中括号的主要优势就是可以通过变量访问属性
let propertyName = “name”;
console.log(person[propertyName]); // “Nicholas”
- Array
Array 构造函数还有两个 ES6 新增的用于创建数组的静态方法:from()和 of()。from()用于将
类数组结构转换为数组实例,而 of()用于将一组参数转换为数组实例。
// 字符串会被拆分为单字符数组
console.log(Array.from(“Matt”)); // [“M”, “a”, “t”, “t”]
// Array.from()对现有数组执行浅复制
const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1);
console.log(a1); // [1, 2, 3, 4]
alert(a1 === a2); // false
// from()也能转换带有必要属性的自定义对象
const arrayLikeObject = {
0: 1,
1: 2,
2: 3,
3: 4,
length: 4
};
console.log(Array.from(arrayLikeObject)); // [1, 2, 3, 4]
const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1, x => x**2);//[1, 4, 9, 16]
Array.of()可以把一组参数转换为数组。这个方法用于替代在 ES6之前常用的 Array.prototype.slice.call(arguments),一种异常笨拙的将 arguments 对象转换为数组的写法:
console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
console.log(Array.of(undefined)); // [undefined]
数组索引
数组 length 属性的独特之处在于,它不是只读的。通过修改 length 属性,可以从数组末尾删除或添加元素。
let colors = [“red”, “blue”, “green”]; // 创建一个包含 3 个字符串的数组
colors.length = 2;
alert(colors[2]); // undefined
let colors = [“red”, “blue”, “green”]; // 创建一个包含 3 个字符串的数组
colors.length = 4;
alert(colors[3]); // undefined
let colors = [“red”, “blue”, “green”]; // 创建一个包含 3 个字符串的数组
colors[colors.length] = “black”; // 添加一种颜色(位置 3)
colors[colors.length] = “brown”; // 再添加一种颜色(位置 4)
检测数组
是判断一个对象是不是数组。在只有一个网页(因而只有一个全局作
用域)的情况下,使用 instanceof 操作符就足矣:
if (value instanceof Array){
// 操作数组
}
使用 instanceof 的问题是假定只有一个全局执行上下文.
如果网页里有多个框架,则可能涉及两
个不同的全局执行上下文,因此就会有两个不同版本的 Array 构造函数
为解决这个问题ECMAScript 提供了Array.isArray()方法。这个方法的目的就是确定一个值是
否为数组,而不用管它是在哪个全局执行上下文中创建的。
if (Array.isArray(value)){
// 操作数组
}
迭代器方法
在 ES6 中,Array 的原型上暴露了 3 个用于检索数组内容的方法:keys()、values()和
entries()。keys()返回数组索引的迭代器,values()返回数组元素的迭代器,而 entries()返回
索引/值对的迭代器
const a = [“foo”, “bar”, “baz”, “qux”];
// 因为这些方法都返回迭代器,所以可以将它们的内容
// 通过 Array.from()直接转换为数组实例
const aKeys = Array.from(a.keys());
const aValues = Array.from(a.values());
const aEntries = Array.from(a.entries());
console.log(aKeys); // [0, 1, 2, 3]
console.log(aValues); // [“foo”, “bar”, “baz”, “qux”]
console.log(aEntries); // [[0, “foo”], [1, “bar”], [2, “baz”], [3, “qux”]]
搜索和位置方法
find()和 findIndex()方法使用了断言函数。这两个方法都从数组的最小索引开始。find()返回
第一个匹配的元素,findIndex()返回第一个匹配元素的索引。这两个方法也都接收第二个可选的参数,
用于指定断言函数内部 this 的值。
const people = [
{
name: “Matt”,
age: 27
},
{
name: “Nicholas”,
age: 29
}
];
alert(people.find((element, index, array) => element.age < 28));
// {name: “Matt”, age: 27}
alert(people.findIndex((element, index, array) => element.age < 28));
// 0
迭代方法
every():对数组每一项都运行传入的函数,如果对每一项函数都返回 true,则这个方法返回 true。
filter():对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回。
forEach():对数组每一项都运行传入的函数,没有返回值。
map():对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组。
some():对数组每一项都运行传入的函数,如果有一项函数返回 true,则这个方法返回 true。
这些方法都不改变调用它们的数组。
####Map
作为 ECMAScript 6 的新增特性,Map 是一种新的集合类型,为这门语言带来了真正的键/值存储机
制.
初始化之后,可以使用 set()方法再添加键/值对。另外,可以使用 get()和 has()进行查询,可
以通过 size 属性获取映射中的键/值对的数量,还可以使用 delete()和 clear()删除值。
const m = new Map();
alert(m.has(“firstName”)); // false
alert(m.get(“firstName”)); // undefined
alert(m.size);
set()方法返回映射实例,因此可以把多个操作连缀起来,
与 Object 只能使用数值、字符串或符号作为键不同,Map 可以使用任何 JavaScript 数据类型作为
键。Map 内部使用 SameValueZero 比较操作(ECMAScript 规范内部定义,语言中不能使用),基本上相
当于使用严格对象相等的标准来检查键的匹配性。与 Object 类似,映射的值是没有限制的。
const m = new Map();
const functionKey = function() {};
const symbolKey = Symbol();
const objectKey = new Object();
m.set(functionKey, “functionValue”);
m.set(symbolKey, “symbolValue”);
m.set(objectKey, “objectValue”);
alert(m.get(functionKey)); // functionValue
alert(m.get(symbolKey)); // symbolValue
alert(m.get(objectKey)); // objectValue
对象 object
Object.defineProperty()
读取属性的特性:使用 Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。
ECMAScript 2017 新增了 Object.getOwnPropertyDescriptors()静态方法。这个方法实际上
会在每个自有属性上调用 Object.getOwnPropertyDescriptor()并在一个新对象中返回它们。
合并对象
ECMAScript 6 专门为合并对象提供了 Object.assign()方法。是浅复制;如果多个源对象都有相同的属性,则使
用最后一个复制的值。
如果赋值期间出错,则操作会中止并退出,同时抛出错误。Object.assign()没有“回滚”之前
赋值的概念,因此它是一个尽力而为、可能只会完成部分复制的方法。
let dest, src, result;
/**
- 错误处理
*/
dest = {};
src = {
a: ‘foo’,
get b() {
// Object.assign()在调用这个获取函数时会抛出错误
throw new Error();
},
c: ‘bar’
};
try {
Object.assign(dest, src);
} catch(e) {}
// Object.assign()没办法回滚已经完成的修改
// 因此在抛出错误之前,目标对象上已经完成的修改会继续存在:
console.log(dest); // { a: foo }
#####对象标识及相等判定
// 这些是===符合预期的情况
console.log(true === 1); // false
console.log({} === {}); // false
console.log(“2” === 2); // false
// 这些情况在不同 JavaScript 引擎中表现不同,但仍被认为相等
console.log(+0 === -0); // true
console.log(+0 === 0); // true
console.log(-0 === 0); // true
// 要确定 NaN 的相等性,必须使用极为讨厌的 isNaN()
console.log(NaN === NaN); // false
console.log(isNaN(NaN)); // true
为改善这类情况,ECMAScript 6 规范新增了 Object.is(),这个方法与===很像,但同时也考虑
到了上述边界情形。这个方法必须接收两个参数
console.log(Object.is(true, 1)); // false
console.log(Object.is({}, {})); // false
console.log(Object.is(“2”, 2)); // false
// 正确的 0、-0、+0 相等/不等判定
console.log(Object.is(+0, -0)); // false
console.log(Object.is(+0, 0)); // true
console.log(Object.is(-0, 0)); // false
// 正确的 NaN 相等判定
console.log(Object.is(NaN, NaN)); // true
要检查超过两个值,递归地利用相等性传递即可:
function recursivelyCheckEqual(x, …rest) {
return Object.is(x, rest[0]) &&
(rest.length < 2 || recursivelyCheckEqual(…rest));
}
增强的对象语法
old:
const jobKey = ‘job’;
let person = {};
person[jobKey] = ‘Software engineer’;
new es6 有了可计算属性
const jobKey = ‘job’;
let person = {
[jobKey]: ‘Software engineer’
};
因为被当作 JavaScript 表达式求值所以可计算属性本身可以是复杂的表达式,在实例化时再求值
const nameKey = ‘name’;
const ageKey = ‘age’;
const jobKey = ‘job’;
let uniqueToken = 0;
function getUniqueKey(key) {
return ${key}_${uniqueToken++}
;
}
let person = {
[getUniqueKey(nameKey)]: ‘Matt’,
[getUniqueKey(ageKey)]: 27,
[getUniqueKey(jobKey)]: ‘Software engineer’
};
######简写方法名
let person = {
sayName: function(name) {
console.log(My name is ${name}
);
}
};
let person = {
sayName(name) {
console.log(My name is ${name}
);
}
};
简写方法名与可计算属性键相互兼容:
const methodKey = ‘sayName’;
let person = {
methodKey {
console.log(My name is ${name}
);
}
}
####对象解构
对象解构就是使用与对象匹配的结构来实现对象属性赋值
// 使用对象解构
let person = {
name: ‘Matt’,
age: 27
};
let { name: personName, age: personAge } = person;
console.log(personName); // Matt
console.log(personAge); // 27
简写: let { name, age } = person;
而如果引用的属性不存在,则该变量的值就是 undefined:
let { name, job } = person; console.log(job); // undefined
也可以在解构赋值的同时定义默认值let { name, job=‘Software engineer’ } = person;
console.log(job); // Software engineer
解构在内部使用函数 ToObject()(不能在运行时环境中直接访问)把源数据结构转换为对象。这
意味着在对象解构的上下文中,原始值会被当成对象。这也意味着(根据 ToObject()的定义),null
和 undefined 不能被解构否则会抛出错误
let { length } = ‘foobar’;
console.log(length); // 6
let { _ } = null; // TypeError
let { _ } = undefined; // TypeError
解构赋值可以使用嵌套结构,以匹配嵌套的属性:
let person = {
name: ‘Matt’,
age: 27,
job: {
title: ‘Software engineer’
}
};
// 声明 title 变量并将 person.job.title 的值赋给它
let { job: { title } } = person;
console.log(title); // Software engineer
在外层属性没有定义的情况下不能使用嵌套解构。无论源对象还是目标对象都一样。
部分解构
如果一个解构表达式涉及多个赋值,开始的赋值成功而后面的赋值出错,则整个解构赋值只会完成一部分
参数上下文匹配
对参数的解构赋值不会影响 arguments 对象,但可以在函数签名中声明在函数体内使用局部变量:
let person = {
name: ‘Matt’,
age: 27
};
function printPerson(foo, {name, age}, bar) {
console.log(arguments);
console.log(name, age);
}
printPerson(‘1st’, person, ‘2nd’);
// [‘1st’, { name: ‘Matt’, age: 27 }, ‘2nd’]
// ‘Matt’, 27
创建对象
- 工厂模式
function createPerson(name, age, job) {
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
console.log(this.name);
};
return o;
}
let person1 = createPerson(“Nicholas”, 29, “Software Engineer”);
这种工厂模式虽
然可以解决创建多个类似对象的问题,但没有解决对象标识问题(即新创建的对象是什么类型)。
- 构造函数模式:ECMAScript 中的构造函数是用于创建特定类型对象的
比如,前面的例子使用构造函数模式可以这样写:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
};
}
let person1 = new Person(“Nicholas”, 29, “Software Engineer”);
Person()内部
的代码跟 createPerson()基本是一样的,只是有如下区别。
没有显式地创建对象。
属性和方法直接赋值给了 this。
没有 return。
另外,要注意函数名 Person 的首字母大写了。
要创建 Person 的实例,应使用 new 操作符。以这种方式调用构造函数会执行如下操作:
- 在内存中创建一个对象
- 这个新对象的内部的 Prototype 特性被复制为构造函数的prototype属性。
- 构造函数内部的this被赋值为这个新对象(即this指向新对象)
- 执行构造函数内部的代码(给新对象添加属性)
- 如果构造函数返回非空对象,则返回该对象,否则返回刚创建的新对象。
person1 和 person2 分别保存着 Person 的不同实例。这两个对象都有一个
constructor 属性指向 Person,如下所示:
console.log(person1.constructor == Person); // true
console.log(person2.constructor == Person); // true
构造函数不一定要写成函数声明的形式。赋值给变量的函数表达式也可以表示构造函数:
let Person = function(name, age, job) {}
在实例化时,如果不想传参数,那么构造函数后面的括号可加可不加。只要有 new 操作符,就可以
调用相应的构造函数
let person1 = new Person();
let person2 = new Person;
任何函数只要使用 new 操作符调用就是构造函数,而不使用 new 操作符调用的函数就是普通函数
在调用一个函数而没有明确设置 this 值的情况下(即没有作为对象的方法调用,或
者没有使用 call()/apply()调用),this 始终指向 Global 对象(在浏览器中就是 window 对象)。
构造函数的问题:
构造函数虽然有用,但也不是没有问题。构造函数的主要问题在于,其定义的方法会在每个实例上
都创建一遍。
#####原型模式
每个函数都会创建一个 prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例
共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处
是,在它上面定义的属性和方法可以被对象实例共享。原来在构造函数中直接赋给对象实例的值,可以
直接赋值给它们的原型,
function Person() {}
Person.prototype.name = “Nicholas”;
Person.prototype.age = 29;
Person.prototype.job = “Software Engineer”;
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
person1.sayName(); // “Nicholas”
let person2 = new Person();
person2.sayName(); // “Nicholas”
console.log(person1.sayName == person2.sayName); // true
类 class
// 类声明
class Person {}
// 类表达式
const Animal = class {};
与函数表达式类似,类表达式在它们被求值前也不能引用。不过,与函数定义不同的是,虽然函数
声明可以提升,但类定义不能
另一个跟函数声明不同的地方是,函数受函数作用域限制,而类受块作用域限制
//class
// new:调用类的构造函数
// 1. 在内存中创建一个对象
// 2. 这个新对象内部的属性指针被赋值为构造函数的prototype属性
// 3. 构造函数的内的this指向新对象
// 4. 执行构造函数内部的代码
// 5. 如果构造函数返回非空对象,则返回该对象,否则返回刚创建的对象
// 空类的定义
class Foo {}
// you 构造函的类
class Bar {
constructor() {}
}
// 有获取函数的类
class BAsx {
get myXT() {}
}
// 有静态方法
class QEX {
static myEWR() {}
}
// 作用域
let Person = class PersonNAme {
identify() {
console.log(Person.name, PersonNAme.name);
}
}
let p = new Person()
p.identify()
console.log(Person.name);
// console.log(PersonNAme.name); // xx is not definde
// 类构造函数
// constructor 用于在类模块内部创建类的构造函数,方法名字会告诉解释器在用new操作符的创建类的新实例的时候,应该调用这个函数,构造函数的定义不是必须的,不定义构造函数相当于讲构造函数定位为空函数
class Animal {}
class Persons {
constructor() {
console.log("EEXER");
}
}
class Vaegtabl {
constructor() {
this.corlor = "FSDFDS"
}
}
var a = new Animal()
var b = new Persons()
var c = new Vaegtabl()
console.log(c.corlor);
// 类实例话时候传入的参数会作用于构造函数的参数, 不传参数的话,类后面的括号可选
class Psrkfdsn {
constructor(name) {
console.log(arguments.length);
this.name = name || null;
}
}
var spr = new Psrkfdsn;
var fddsn = new Psrkfdsn()
var fddsndfsd = new Psrkfdsn("neam")
// 构造函数和类构造函数的区别是 调用类构造函数必须用new操作符,而普通构造函数不需要new,那么就会以全局的this(通常是window)作为内部对象。调用类构造函数如忘记new 则跑出错误。
function Prwesdfs() {}
class Anmeifd {}
// 吧window作为this。来构建实例
var dfsdk = Prwesdfs()
// 静态方法,每个类上只能有一个
class Rgfdsg {
constructor() {
this.locate = () => {
console.log("AAAAAAa", this);
}
}
locate() {
console.log("prototype", this);
}
static locate() {
console.log("STatic", this);
}
}
var dofd = new Rgfdsg()
dofd.locate()
Rgfdsg.prototype.locate();
Rgfdsg.locate()
// 静态方法非常适合做实例工厂
class Dhkms {
constructor(age) {
this.age_ = age,
this.name_ = name
}
sayAge() {
console.log(this.age_);
}
static create() {
return new Dhkms(Math.floor(Math.random() * 100))
}
}
console.log(Dhkms.create())
// 继承
// extends 关键字可以继承任何拥有,construct 和 原型的对象.
// 派生类都会通过原型链访问到类和原型上的方法,this的值会反应电泳相应方法的实例和类:
class Vehdfs {
identiiifddRorerpe(id) {
console.log(id, this);
}
static indefdkClass(id) {
console.log(id, this);
}
}
class Bus extends Vehdfs {}
var fuds = new Vehdfs()
var fngssd = new Bus()
fuds.identiiifddRorerpe("ver")
fngssd.identiiifddRorerpe("Bus")
Bus.indefdkClass("BS")
Vehdfs.indefdkClass("Ve")
// 派生类的方法可以通过调用super关键字引用他们的原型,super只能在派生类中使用,且限制于类构造函数和实例方法和静态方法内部,在类构造函数中调用可以访问父类的构造函数
class Vae {
constructor() {
this.hasEngine = true;
}
}
class Busvae extends Vae {
constructor() {
// 不要在调用super ()之前调用this,否则跑出ReferenceError错误
super() // 相当于 super.constructor
console.log(this instanceof Vae);
console.log(this);
}
}
new Busvae()
// 在静态方法中可以通过 super 调用继承的类上定义的静态方法:
class Efdssfs {
static identify() {
console.log("VE");
}
}
class Bise extends Efdssfs {
static identify() {
super.identify()
}
}
Bise.identify()
// super 只能在派生类(extends 继承时)构造函数和静态方法中使用。
// 不能单独引用 super 关键字,要么用它调用构造函数super(),要么用它引用静态方法super.xx。
// 调用 super()会调用父类构造函数,并将返回的实例赋值给 this。
// super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入。
// 如果没有定义类构造函数,在实例化派生类时会调用 super(),而且会传入所有传给派生类的参数。隐式自动调用
// 在类构造函数中,不能在调用 super()之前引用 this。
// class Vehicless {}
// class Bussss extends Vehicless {
// constructor() {
// console.log(this);
// }
// }
// new Bussss();
// ReferenceError: Must call super constructor in derived class
// before accessing 'this' or returning from derived constructor
// 如果在派生类中显式定义了构造函数,则要么必须在其中调用 super(),要么必须在其中返回
// 一个对象。
// class Vehicle {}
// class Car extends Vehicle {}
// class Bus extends Vehicle {
// constructor() {
// super();
// }
// }
// class Van extends Vehicle {
// constructor() {
// return {};
// }
// }
// console.log(new Car()); // Car {}
// console.log(new Bus()); // Bus {}
// console.log(new Van()); // {}
// 继承 内置类型
class SuperArray extends Array {
shuffle() {
for (let i = this.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[this[i], this[j]] = [this[j], this[i]];
}
}
}
var xipai = new SuperArray(1, 2, 3, 4, 5)
console.log("xipai", xipai);
xipai.shuffle()
console.log(xipai);
DOM
HTML 中的每段标记都可以表示为这个树形结构中的一个节点。元素节点表示 HTML 元素,属性
节点表示属性,文档类型节点表示文档类型,注释节点表示注释。DOM 中总共有 12 种节点类型,这些
类型都继承一种基本类型。
每个节点都有 nodeType 属性,表示该节点的类型。节点类型由定义在 Node 类型上的 12 个数值
常量表示
Node.ELEMENT_NODE(1)
Node.ATTRIBUTE_NODE(2)
Node.TEXT_NODE(3)
Node.CDATA_SECTION_NODE(4)
Node.ENTITY_REFERENCE_NODE(5)
Node.ENTITY_NODE(6)
Node.PROCESSING_INSTRUCTION_NODE(7)
Node.COMMENT_NODE(8)
Node.DOCUMENT_NODE(9)
Node.DOCUMENT_TYPE_NODE(10)
Node.DOCUMENT_FRAGMENT_NODE(11)
Node.NOTATION_NODE(12)
if (someNode.nodeType == Node.ELEMENT_NODE){
alert(“Node is an element.”);
}
这个例子比较了 someNode.nodeType 与 Node.ELEMENT_NODE 常量。如果两者相等,则意味着
someNode 是一个元素节点。
浏览器并不支持所有节点类型。开发者最常用到的是元素节点和文本节点。
- 节点关系
每个节点都有一个 childNodes 属性,其中包含一个 NodeList 的实例。NodeList 是一个类数组
对象,,NodeList 并不是 Array 的实例,但可以使用中括
号访问它的值,而且它也有 length 属性。NodeList 对象独特的地方在于,它其实是一个对 DOM 结
构的查询,因此 DOM 结构的变化会自动地在 NodeList 中反映出来。我们通常说 NodeList 是实时的
活动对象,而不是第一次访问时所获得内容的快照
let firstChild = someNode.childNodes[0];
let secondChild = someNode.childNodes.item(1);
let count = someNode.childNodes.length;
使用 Array.prototype.slice()可以像前面介绍 arguments 时一样把 NodeList 对象转换为数组
let arrayOfNodes = Array.prototype.slice.call(someNode.childNodes,0);
使用 ES6 的 Array.from()静态方法,可以替换这种笨拙的方式:
let arrayOfNodes = Array.from(someNode.childNodes);
用 previousSibling 和 nextSibling 可以在这个列
表的节点间导航。这个列表中第一个节点的 previousSibling 属性是 null,最后一个节点的
nextSibling 属性也是 null,
:firstChild 和 lastChild 分别指向
childNodes 中的第一个和最后一个子节点
someNode.lastChild 的值始终等于 someNode.childNodes[someNode.
childNodes.length-1]。
这是因为利用这些关系
指针,几乎可以访问到文档树中的任何节点,而这种便利性是 childNodes 的最大亮点。还有一个便利
的方法是 hasChildNodes(),这个方法如果返回 true 则说明节点有一个或多个子节点。相比查询
childNodes 的 length 属性,这个方法无疑更方便。
- 操纵节点
DOM 又提供了一些操纵节点的方法。最常用的方法是appendChild(),用于在 childNodes 列表末尾添加节点
appendChild()方法返回新添加的节点,
let returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); // true
alert(someNode.lastChild == newNode); // true
如果把文档中已经存在的节点传给 appendChild(),则这个节点会从之前的位置被转移到新位置。
如果想把节点放到 childNodes 中的特定位置而不是末尾,则可以使用 insertBefore()方法。
这个方法接收两个参数:要插入的节点和参照节点。调用这个方法后,要插入的节点会变成参照节点的
前一个同胞节点,并被返回。如果参照节点是 null,则 insertBefore()与 appendChild()效果相
同,
appendChild() 和 insertBefore() 在插入节点时不会删除任何已有节点。相对地,
replaceChild()方法接收两个参数:要插入的节点和要替换的节点。要替换的节点会被返回并从文档
树中完全移除,要插入的节点会取而代之。
与 replaceChild()方法一样,通过 removeChild()被移除的节点从技术上说仍然被同一个文档
所拥有,但文档中已经没有它的位置。
上面介绍的 4 个方法都用于操纵某个节点的子元素,也就是说使用它们之前必须先取得父节点(使
用前面介绍的 parentNode 属性)。并非所有节点类型都有子节点,如果在不支持子节点的节点上调用
这些方法,则会导致抛出错误。
所有节点类型还共享了两个方法。第一个是 cloneNode(),会返回与调用它的节点一模一样的节
点。cloneNode()方法接收一个布尔值参数,表示是否深复制。在传入 true 参数时,会进行深复制,
即复制节点及其整个子 DOM 树。如果传入 false,则只会复制调用该方法的节点。
let deepList = myList.cloneNode(true);
alert(deepList.childNodes.length); // 3(IE9 之前的版本)或 7(其他浏览器)
let shallowList = myList.cloneNode(false);
alert(shallowList.childNodes.length); // 0
getElementsByTagName:是另一个常用来获取元素引用的方法这个方法返回一个
HTMLCollection 对象
window.location.href和document.location.href、document.URL的区别
1、document表示的是一个文档对象,window表示的是一个窗口对象,一个窗口下可以有多个文档对象。
所以一个窗口下只有一个window.location.href,但是可能有多个document.URL、document.location.href
2、window.location.href和document.location.href可以被赋值,然后跳转到其它页面,document.URL只能读不能写
-
getElementById():参数 ID 必须跟元素在页面中的 id 属性值完全匹配,包括大小写
-
getElementsByTagName():在 HTML 文档中,这个方法返回一个
HTMLCollection 对象。
HTMLCollection 与 NodeList

共同点:都可以 item()、 [x] length
alert(images.length); // 图片数量
alert(images[0].src); // 第一张图片的 src 属性
alert(images.item(0).src); // 同上
区别
HTMLCollection 对象还有一个额外的方法 namedItem()
let myImage = images.namedItem(“myImage”);
let myImage = images[“myImage”];
文档写入
write()和 writeln()方法经常用于动态包含外部资源
过 document.write()向文档中输出内容。如果是在页面加
载完之后再调用 document.write(),则输出的内容会重写整个页面
let div = document.getElementById(“myDiv”);
alert(div.tagName); // “DIV”
div.tagName 实际上返回的是"DIV"而不是
“div”。在 HTML 中,元素标签名始终以全大写表示;在 XML(包括 XHTML)中,标签名始终与源
代码中的大小写一致
if (element.tagName.toLowerCase() == “div”){ // 推荐,适用于所有文档
// 做点什么
}
let div = document.getElementById(“myDiv”);
alert(div.id); // “myDiv”
alert(div.className); // “bd”!!!!!!!!!!!!
alert(div.title); // “Body text”
alert(div.lang); // “en”
alert(div.dir); // “ltr”
取得属性
getAttribute()、setAttribute()和 removeAttribute()。
let div = document.getElementById(“myDiv”);
alert(div.getAttribute(“id”)); // “myDiv”
alert(div.getAttribute(“class”)); // “bd”
alert(div.getAttribute(“title”)); // “Body text”
alert(div.getAttribute(“lang”)); // “en”
alert(div.getAttribute(“dir”)); // “ltr”
“className”(className 是作为对象属性时才那么拼写的)。如果给定的属性不存在,则 getAttribute()
返回 null。
let value = div.getAttribute(“my_special_attribute”);
attributes 属性
Element 类型是唯一使用 attributes 属性的 DOM 节点类型
- getNamedItem(name),返回 nodeName 属性等于 name 的节点;
- removeNamedItem(name),删除 nodeName 属性等于 name 的节点;
- setNamedItem(node),向列表中添加 node 节点,以其 nodeName 为索引;
- item(pos),返回索引位置 pos 处的节点。
attributes 属性中的每个节点的 nodeName 是对应属性的名字,nodeValue 是属性的值。比如,
要取得元素 id 属性的值,可以使用以下代码:let id = element.attributes.getNamedItem(“id”).nodeValue;
下面是使用中括号访问属性的简写形式:
let id = element.attributes[“id”].nodeValue;
一般来说,因为使用起来更简便,通常开发者更喜欢使用 getAttribute()、removeAttribute()
和 setAttribute()方法,而不是刚刚介绍的 以上的NamedNodeMap 对象的方法。
attributes 属性最有用的场景是需要迭代元素上所有属性的时候。这时候往往是要把 DOM 结构
序列化为 XML 或 HTML 字符串
function outputAttributes(element) {
let pairs = [];
for (let i = 0, len = element.attributes.length; i < len; ++i) {
const attribute = element.attributes[i];
pairs.push(${attribute.nodeName}="${attribute.nodeValue}"
);
}
return pairs.join(" ");
}
这个函数使用数组存储每个名/值对,迭代完所有属性后,再将这些名/值对用空格拼接在一起。(这
个技术常用于序列化为长字符串。)
创建元素
let div = document.createElement(“div”);
div.id = “myNewDiv”;
div.className = “box”;
要把元素添加到文档树,可以使用 appendChild()、insertBefore()或 replaceChild()。
元素被添加到文档树之后,浏览器会立即将其渲染出来。之后再对这个元素所做的任何修改,都会立即在浏览器中反映出来。
for (let i = 0, len = element.childNodes.length; i < len; ++i) {
if (element.childNodes[i].nodeType == 1) {
// 执行某个操作
}
}
以上代码会遍历某个元素的子节点,并且只在 nodeType 等于 1(即 Element 节点)时执行某个操作。
动态脚本
动态脚本就是在页面初始加载时不存在,之后又通过 DOM 包含的脚本
有两种方式通过
可以像这样通过 DOM 编程创建这个节点:
let script = document.createElement(“script”);
script.src = “foo.js”;
document.body.appendChild(script)
另一个动态插入 JavaScript 的方式是嵌入源代码
let script = document.createElement(“script”);
script.appendChild(document.createTextNode(“function sayHi(){alert(‘hi’);}”));
document.body.appendChild(script);
var script = document.createElement(“script”);
var code = “function sayHi(){alert(‘hi’);}”;
try {
script.appendChild(document.createTextNode(“code”));
} catch (ex){
script.text = “code”;
}
document.body.appendChild(script);
于是,我们就可以
抽象出一个跨浏览器的函数:
function loadScriptString(code){
var script = document.createElement(“script”);
script.type = “text/javascript”;
try {
script.appendChild(document.createTextNode(code));
} catch (ex){
script.text = code;
}
document.body.appendChild(script);
}
这个函数可以这样调用:
loadScriptString(“function sayHi(){alert(‘hi’);}”);
动态css
let style = document.createElement(“style”);
style.type = “text/css”;
try{
style.appendChild(document.createTextNode(“body{background-color:red}”));
} catch (ex){
style.styleSheet.cssText = “body{background-color:red}”;
}
let head = document.getElementsByTagName(“head”)[0];
head.appendChild(style);
function loadStyleString(css){
let style = document.createElement(“style”);
style.type = “text/css”;
try{
style.appendChild(document.createTextNode(css));
} catch (ex){
style.styleSheet.cssText = css;
}
let head = document.getElementsByTagName(“head”)[0];
head.appendChild(style);
}
可以这样调用这个函数:
loadStyleString(“body{background-color:red}”);
DOM 扩展
querySelector()
// 取得元素
let body = document.querySelector(“body”);
// 取得 ID 为"myDiv"的元素
let myDiv = document.querySelector("#myDiv");
// 取得类名为"selected"的第一个元素
let selected = document.querySelector(".selected");
// 取得类名为"button"的图片
let img = document.body.querySelector(“img.button”);
在 Document 上使用 querySelector()方法时,会从文档元素开始搜索;在 Element 上使用
querySelector()方法时,则只会从当前元素的后代中查询。
querySelectorAll()
querySelectorAll()方法跟 querySelector()一样,也接收一个用于查询的参数,但它会返回
所有匹配的节点,而不止一个。这个方法返回的是一个 NodeList 的静态实例。
再强调一次,querySelectorAll()返回的 NodeList 实例一个属性和方法都不缺,但它是一
个静态的“快照”,而非“实时”的查询。这样的底层实现避免了使用 NodeList对象可能造成的性能问题。
matches()方法(在规范草案中称为 matchesSelector())接收一个 CSS 选择符参数,如果元素
匹配则该选择符返回 true,否则返回 false。例如:
if (document.body.matches(“body.page1”)){
// true
}
使用这个方法可以方便地检测某个元素会不会被 querySelector()或 querySelectorAll()方
法返回。
所有主流浏览器都支持 matches()
H5 新增
之前 要操作类名,可以通过 className 属性实现添加、删除和替换。
// 要删除"user"类
let targetClass = “user”;
// 把类名拆成数组
let classNames = div.className.split(/\s+/);
// 找到要删除类名的索引
let idx = classNames.indexOf(targetClass);
// 如果有则删除
if (idx > -1) {
classNames.splice(i,1);
}
// 重新设置类名
div.className = classNames.join(" ");
HTML5 通过给所有元素增加 classList 属性
- add(value)向类名列表中添加指定的字符串值value。如果这个值已经存在,则什么也不做。
- contains(value),返回布尔值,表示给定的 value 是否存在。
- remove(value),从类名列表中删除指定的字符串值 value。
- toggle(value),如果类名列表中已经存在指定的 value,则删除;如果不存在,则添加。
这样以来,前面的例子中那么多行代码就可以简化成下面的一行:
div.classList.remove(“user”);
// 删除"disabled"类
div.classList.remove(“disabled”);
// 添加"current"类
div.classList.add(“current”);
// 切换"user"类
div.classList.toggle(“user”);
// 检测类名
if (div.classList.contains(“bd”) && !div.classList.contains(“disabled”)){
// 执行操作
)
// 迭代类名
for (let class of div.classList){
doStuff(class);
}
scrollIntoView()
scrollIntoView()方法存在于所有 HTML 元素上,可以滚动浏览器窗口或容器元素以便包含元
素进入视口
alignToTop 是一个布尔值。
- true:窗口滚动后元素的顶部与视口顶部对齐。
- false:窗口滚动后元素的底部与视口底部对齐。
- scrollIntoViewOptions 是一个选项对象。
- behavior:定义过渡动画,可取的值为"smooth"和"auto",默认为"auto"。
- block:定义垂直方向的对齐,可取的值为"start"、“center”、“end"和"nearest”,默
认为 “start”。 - inline:定义水平方向的对齐,可取的值为"start"、“center”、“end"和"nearest”,默
认为 “nearest”。 - 不传参数等同于 alignToTop 为 true。
// 确保元素可见
document.forms[0].scrollIntoView();
// 同上
document.forms[0].scrollIntoView(true);
document.forms[0].scrollIntoView({block: ‘start’});
// 尝试将元素平滑地滚入视口
document.forms[0].scrollIntoView({behavior: ‘smooth’, block: ‘start’});
这个方法可以用来在页面上发生某个事件时引起用户关注。把焦点设置到一个元素上也会导致浏览
器将元素滚动到可见位置。
contains()方法
如果目标节点是被搜索节点的后代,contains()返回 true,否则返回 false。下面看一个例子:
console.log(document.documentElement.contains(document.body)); // true
如前所述,滚动是 HTML5 之前 DOM 标准没有涉及的领域。虽然 HTML5 把 scrollIntoView()
标准化了,但不同浏览器中仍然有其他专有方法。比如,scrollIntoViewIfNeeded()作 为
HTMLElement 类型的扩展可以在所有元素上调用。scrollIntoViewIfNeeded(alingCenter)会在
元素不可见的情况下,将其滚动到窗口或包含窗口中,使其可见;如果已经在视口中可见,则这个方法
什么也不做。如果将可选的参数 alingCenter 设置为 true,则浏览器会尝试将其放在视口中央。Safari、
Chrome 和 Opera 实现了这个方法。
下面使用 scrollIntoViewIfNeeded()方法的一个例子:
// 如果不可见,则将元素可见
document.images[0].scrollIntoViewIfNeeded();
考虑到 scrollIntoView()是唯一一个所有浏览器都支持的方法,所以只用它就可以了。
DOM 样式属性和方法
myDiv.style.cssText = “width: 25px; height: 100px; background-color: green”;
console.log(myDiv.style.cssText);
滚动尺寸
有些元素,比如
无须任何代码就可以自动滚动,而其他元素则需要使用 CSS 的 overflow 属性令其滚动。滚动 尺寸相关的属性有如下 4 个。 scrollHeight,没有滚动条出现时,元素内容的总高度。 scrollLeft,内容区左侧隐藏的像素数,设置这个属性可以改变元素的滚动位置。 scrollTop,内容区顶部隐藏的像素数,设置这个属性可以改变元素的滚动位置。 scrollWidth,没有滚动条出现时,元素内容的总宽度。确定元素尺寸
- getBoundingClientRect()包含6 个属性:left、top、right、bottom、height 和width。
事件
因为属性的值是 JavaScript 代码,所以不能在未经转义的情况下使用 HTML 语 法字符,比如和号(&)、双引号(")、小于号(<)和大于号(>)。此时,为了避免使用 HTML 实体, 可以使用单引号代替双引号。如果确实需要使用双引号,则要把代码改成下面这样:以这种方式指定的事件处理程序有一些特殊的地方。首先,会创建一个函数来封装属性的值。这个
函数有一个特殊的局部变量 event,其中保存的就是 event 对象
-
addEventListener
-
removeEventListener
主要优势是可以为同一个事件添加多个事件处理程序。
这个事件处理程序同样在被附加到的元素的作用域中运行 this 指当前元素。
let btn = document.getElementById(“myBtn”);
btn.addEventListener(“click”, () => {
console.log(this.id);
}, false);
btn.addEventListener(“click”, () => {
console.log(“Hello world!”);
}, false);
这里给按钮添加了两个事件处理程序。多个事件处理程序以添加顺序来触发,因此前面的代码会先
打印元素 ID,然后显示消息“Hello world!”。
通过 addEventListener()添加的事件处理程序只能使用 removeEventListener()并传入与添
加时同样的参数来移除。这意味着使用 addEventListener()添加的匿名函数无法移除,如下面的例
子所示:
let btn = document.getElementById(“myBtn”);
btn.addEventListener(“click”, () => {
console.log(this.id);
}, false);
// 其他代码
btn.removeEventListener(“click”, function() { // 没有效果!
console.log(this.id);
}, false);
在事件处理程序内部,this 对象始终等于 currentTarget 的值,而 target 只包含事件的实际
目标。如果事件处理程序直接添加在了意图的目标,则 this、currentTarget 和 target 的值是一样
的。下面的例子展示了这两个属性都等于 this 的情形:
let btn = document.getElementById(“myBtn”);
btn.onclick = function(event) {
console.log(event.currentTarget === this); // true
console.log(event.target === this); // true
};
上面的代码检测了 currentTarget 和 target 的值是否等于 this。因为 click 事件的目标是按
钮,所以这 3 个值是相等的。如果这个事件处理程序是添加到按钮的父节点(如 document.body)上,
那么它们的值就不一样了。比如下面的例子在 document.body 上添加了单击处理程序:
document.body.onclick = function(event) {
console.log(event.currentTarget === document.body); // true
console.log(this === document.body); // true
console.log(event.target === document.getElementById(“myBtn”)); // true
};
这种情况下点击按钮,this 和 currentTarget 都等于 document.body,这是因为它是注册事件
处理程序的元素。而 target 属性等于按钮本身,这是因为那才是 click 事件真正的目标。由于按钮
本身并没有注册事件处理程序,因此 click 事件冒泡到 document.body,从而触发了在它上面注册的
处理程序。
event.type:click、mouseover 和 mouseout。。。。。。。
preventDefault()方法用于阻止特定事件的默认动作
stopPropagation()
load 事件
第一种是 JavaScript 方式,如下所示:
window.addEventListener(“load”, (event) => {
console.log(“Loaded!”);
});
第二种指定 load 事件处理程序的方式是向元素添加 onload 属性
图片上也会触发load事件,包括DOM中的图片和非DOM中的图片。可以在HTML中直接给
元素的 onload 属性指定事件处理程序,比如:
使用 JavaScript 也可以为图片指定事件处理程序:
let image = document.getElementById(“myImage”);
image.addEventListener(“load”, (event) => {
console.log(event.target.src);
});
在通过 JavaScript 创建新元素时,也可以给这个元素指定一个在加载完成后执行的事件处理
程序。在这里,关键是要在赋值 src 属性前指定事件处理程序,如下所示:
window.addEventListener(“load”, () => {
let image = document.createElement(“img”);
image.addEventListener(“load”, (event) => {
console.log(event.target.src);
});
document.body.appendChild(image);
image.src = “smile.gif”;
});
同样的技术也适用于 DOM0 的 Image 对象。在 DOM 出现之前,客户端都使用 Image 对象预先加载图片。可以像使用前面(通过 createElement()方法创建)的元素一样使用 Image 对象,只
是不能把后者添加到 DOM 树。下面的例子使用新 Image 对象实现了图片预加载:
window.addEventListener(“load”, () => {
let image = new Image();
image.addEventListener(“load”, (event) => {
console.log(“Image loaded!”);
});
image.src = “smile.gif”;
});
。
但也要注意,在事件处理程序中删除按钮会阻止事件冒泡。只有事件目标仍然存在于文档中时,事件才会冒泡。
##表单
有几种方式可以取得对元素的引用。最常用的是将表单当作普通元素为它指定一个 id 属
性,从而可以使用 getElementById()来获取表单,比如:
let form = document.getElementById(“form1”);
// 取得页面中的第一个表单
let firstForm = document.forms[0];
// 取得名字为"form2"的表单
let myForm = document.forms[“form2”];
表单元素可以像页面中的其他元素一样使用原生 DOM 方法来访问。此外,所有表单元素都是表单
elements 属性(元素集合)中包含的一个值
let form = document.getElementById(“form1”);
// 取得表单中的第一个字段
let field1 = form.elements[0];
// 取得表单中名为"textbox1"的字段
let field2 = form.elements[“textbox1”];
// 取得字段的数量
let fieldCount = form.elements.length;
提交表单
Submit Form 如果表单中有上述任何一个按钮,且焦点在表单中某个控件上,则按回车键也可以提交表单。(textarea 控件是个例外,当焦点在它上面时,按回车键会换行。) 阻止这个事件的默认行为可以取消提交表单。例如, 下面的代码会阻止表单提交: let form = document.getElementById("myForm"); form.addEventListener("submit", (event) => { // 阻止表单提交 event.preventDefault(); });也可以通过编程方式在 JavaScript 中调用 submit()方法来提交表单。可以在任何时候调用
这个方法来提交表单,而且表单中不存在提交按钮也不影响表单提交。
let form = document.getElementById(“myForm”);
// 提交表单
form.submit();
检测有效性
使用 checkValidity()方法可以检测表单中任意给定字段是否有效。这个方法在所有表单元素上
都可以使用,如果字段值有效就会返回 true,否则返回 false。判断字段是否有效的依据是本节前面
提到的约束条件,因此必填字段如果没有值就会被视为无效,而字段值不匹配 pattern 属性也会被视
为无效
if (document.forms[0].elements[0].checkValidity()){
// 字段有效,继续
} else {
// 字段无效
}
要检查整个表单是否有效,可以直接在表单上调用 checkValidity()方法。这个方法会在所有字
段都有效时返回 true,有一个字段无效就会返回 false:
if(document.forms[0].checkValidity()){
// 表单有效,继续
} else {
// 表单无效
}
禁用验证
通过指定 novalidate 属性可以禁止对表单进行任何验证:
如果一个表单中有多个提交按钮,那么可以给特定的提交按钮添加 formnovalidate 属性,指定 通过该按钮无须验证即可提交表单: 在这个例子中,第一个提交按钮会让表单像往常一样验证数据,第二个提交按钮则禁用了验证,可 以直接提交表单。我们也可以使用 JavaScript 来设置这个属性: // 关闭验证 document.forms[0].elements["btnNoValidate"].formNoValidate = true;选择框是使用和元素创建的。
表单字段的公共能力之外又提供了以下属性和方法。
add(newOption, relOption):在 relOption 之前向控件中添加新的。
multiple:布尔值,表示是否允许多选,等价于 HTML 的 multiple 属性。
options:控件中所有元素的 HTMLCollection。
remove(index):移除给定位置的选项。
selectedIndex:选中项基于 0 的索引值,如果没有选中项则为–1。对于允许多选的列表,始
终是第一个选项的索引。
size:选择框中可见的行数,等价于 HTML 的 size 属性
函数
箭头函数虽然语法简洁,但也有很多场合不适用。箭头函数不能使用 arguments、super 和
new.target,也不能用作构造函数。此外,箭头函数也没有 prototype 属性
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通