css
css应用于文档的三种方法:使用外部样式表、使用内部样式表和使用内联样式。
css的层叠(cascade)和优先级(specificity):
层叠:
在css中,顺序很重要,当应用两条同级别的规则到一个元素的时候,写在后面的就是实际使用的规则。
优先级:
越具体的选择器,优先级越高。
一个选择器的优先级可以说是由三个不同的值(或分量)相加,可以认为是百(ID)十(类)个(元素)——三位数的三个位数:
- ID:选择器中包含 ID 选择器则百位得一分。
- 类:选择器中包含类选择器、属性选择器或者伪类则十位得一分。
- 元素:选择器中包含元素、伪元素选择器则个位得一分。
内联样式:
内联样式就是style属性内的样式说明,优先于所有普通样式,无论其优先级如何。
JavaScript
脚本调用策略
在html的标签元素中添加onclick处理器,相当于js污染了html,尽量不要这样做。
这样效率低下,对每一个需要应用js的按钮都需要添加onclick。
与其在html中添加js,不如使用纯js构造,比如addeventlistener。
html元素是按照其在页面中出现的次序调用的,如果用js来管理页面上的元素(dom,文档对象模型),如果js加载于欲操作的html元素之前,则代码将出错。
旧的解决办法:
将脚本元素放在文档体的底端(也就是标签之前,与之相邻),这样脚本就可以在html解析完毕后加载了。问题是,所有的html dom加载完成后才开始脚本的加载/解析过程,对于有大量js代码的大型网站,可能会带来显著的性能损耗。
新的解决办法:
js放在文档头部,在标签之前,可以是内部js也可以是外部js。
内部js:
document.addEventListener("DOMContentLoaded", () => {
// …
});
这个监听器监听的是浏览器的**DOMContentLoaded**
事件,该事件标志了html文档完全加载和解析。该代码块中的js在这个事件被触发之后才运行,因此避免了错误。
外部js:
使用js的一项现代技术(defer属性),它告知浏览器在遇到<script>
元素时继续下载html内容。
<script src="script.js" defer></script>
在这种情况下,脚本和html将一并加载,代码将顺利运行。
defer只适用于外部脚本,所以在内部js中只用**DOMContentLoaded**
事件的方法,而不用defer。
async和defer
脚本阻塞的问题实际上有两种解决办法,async
和defer
。
async:
浏览器遇到async脚本时不会阻塞页面渲染,而是直接下载然后运行。脚本下载完成后立即执行,在此刻将阻止页面的渲染。多个脚本同时进行的时候,脚本的运行次序是不可控制的。当页面的脚本之间彼此独立且不依赖于本页面的其他任何脚本时,async是最理想的选择。
<script async src="js/vendor/jquery.js"></script>
<script async src="js/script2.js"></script>
<script async src="js/script3.js"></script>
这三个使用async的脚本,彼此的加载顺序是不确定的。
async应该在有大量后台脚本需要加载,并且只想尽快加载到位的情况下使用。例如,加载游戏数据文件,这些文件在游戏真正开始时是需要的,但现在只需要显示游戏介绍、标题和大厅,而不想被脚本加载阻塞。
defer:
使用defer属性的脚本将按照他们在页面上出现的顺序加载,在页面完全加载完毕之前,脚本不会运行,如果脚本依赖于dom的存在(例如,脚本修改了页面上的一个或者多个元素),这一点非常有用。
<script defer src="js/vendor/jquery.js"></script>
<script defer src="js/script2.js"></script>
<script defer src="js/script3.js"></script>
这三个使用defer的脚本加载顺序是jquery.js,script2.js,script3.js。并且在页面内容全部加载完成之前,他们不会运行。
函数
对象的函数叫做方法。
在编写一个函数时,希望支持可选参数,可以在参数后添加一个=
并在其后添加默认值,来指定参数的默认值。
function hello(name = "克里斯") {
console.log(`你好,${name}!`);
}
hello("阿里"); // 你好,阿里!
hello(); // 你好,克里斯!
函数名后面的小括号叫函数调用运算符(function invocation operater)。只有在想直接调用函数的地方才这么写。
btn.onclick = displayMessage();
// 在触发点击事件之前直接执行函数。
btn.onclick = displayMessage;
// 点击事件触发之后,运行函数中的代码。
注意,匿名函数中的代码也不是直接执行的。
btn.onclick = function () {
displayMessage("Woo, this is a different message!");
};
// 点击事件触发之后,函数被执行。
事件监听机制
使用addEventListener
来注册事件处理器,是最强大的方法,在复杂的程序中,他的扩展性最好。
另外还有两种,事件处理器属性和内联事件处理器。
事件处理器属性:
可以触发事件的对象(比如:按钮)通常也有属性,其名称是on
,后面跟的是事件名。为了处理事件,可以将处理函数分配给该属性。
const btn = document.querySelector("button");
function random(number) {
return Math.floor(Math.random() * (number + 1));
}
btn.onclick = () => {
const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
document.body.style.backgroundColor = rndCol;
};
// 也可以将处理器属性分配给具名函数。
const btn = document.querySelector("button");
function random(number) {
return Math.floor(Math.random() * (number + 1));
}
function bgChange() {
const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
document.body.style.backgroundColor = rndCol;
}
btn.onclick = bgChange;
与addEventListener
可以在同一个元素上多次调用不同,对于事件处理器属性,不能给一个事件添加一个以上的处理程序。
element.onclick = function1;
element.onclick = function2;
// 写在后面的会覆盖前面的。
内联事件处理器–不要用:
<button onclick="bgChange()">按下我</button>
<button onclick="alert('你好,这是来自旧式事件处理器的一条消息');">
按下我
</button>
function bgChange() {
const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
document.body.style.backgroundColor = rndCol;
}
过时了,不要用,低效,不好维护。
对象
对象是一个包含相关数据和方法的集合(通常由一些变量和函数组成,我们称之为对象里面的属性和方法)。
当对象的成员是函数时,可以用functionName()
来代替functionName: function()
,这样更简短。
手动写出一个对象的内容,来创建一个对象,称之为对象字面量(object literal)。与之不同的是通过类实例化一个对象。
发送一个对象,比分别发送其中的数据更有效率;使用名字表示其中的数据时,对象比数组更容易使用。
点表示法
person.name;
使用了点表示法(dot notation)来访问对象的属性和方法。
第一位是对象名,表现为命名空间,紧接着一个点,后面是要访问的目标,可以是属性名,也可以是数组属性的一个子元素,或者是对象的方法调用,也可以是另一个命名空间(当对象的值是另一个对象时)。
括号表示法
person.age;
person.name.first;
// 改为括号表示法
person["age"];
person["name"]["first"];
数组是将数字映射到值,而这里是将对象的值的名称(字符串)映射到值,所以对象有时候也叫关联数组,使用关联了值的名称来选择元素,而不是索引。
const person = {
name: ["Bob", "Smith"],
age: 32,
};
function logProperty(propertyName) {
console.log(person[propertyName]);
}
logProperty("name");
// ["Bob", "Smith"]
logProperty("age");
// 32
当对象属性的名称保存在变量中,这种情况只能用括号表示法。
点表示法只能接受字面量的成员的名字,不能接受表示成员名称的变量。
括号表示法可以动态地设置成员的名字。
// 用户动态地输入成员的名字和值
const myDataName = nameInput.value;
const myDataValue = nameValue.value;
// 将成员的名字和值添加到对象中
person[myDataName] = myDataValue;
this
关键字this
指向了当前代码运行时的对象。
introduceSelf() {
console.log(`你好!我是 ${this.name[0]}。`);
}
手动编写对象字面量时看起来作用不大,而在使用构造函数从单个对象定义创建多个对象时,这很有用。
构造函数
构造函数是使用new
关键字调用的函数,当我们调用构造函数时:
- 创建一个新对象
- 将
this
绑定到新对象,以便在构造函数代码中引用this
- 运行构造函数的代码
- 返回新的对象
原型与原型链
JavaScript中所有的对象都有一个内置属性,称为它的prototype(原型)。
其本身时一个对象,所以原型对象也会有它自己的原型,逐渐形成了原型链。原型链终止于拥有null
作为其原型的对象上。
注意:指向对象原型的属性不是prototype。它的名字不是标准的,但实际上所有的浏览器都使用_proto_
。访问对象原型的标准方法是Object.getProtottypeOf().
。
当我们试图访问一个对象的属性时,如果对象本身没有这个属性,就会在原型中搜索该属性;如果仍然找不到该属性,就搜索原型的原型,以此类推,直到找到该属性,或者达到原型链的末端,在这种情况下将返回undefined
。
有个对象叫Object.prototype
,它是最基础的原型,所有对象默认都拥有它。Object.prototype
的原型是null
,所以它位于原型链的终点。
当对象中定义了一个属性,在该对象的原型中定义了一个相同的属性,结果是对象中的属性生效,而原型中的属性被“遮蔽”,这叫做属性的遮蔽。
设置原型
两种方法:Object.create()
和构造函数
Object.create()
方法创建一个新的对象,并允许你指定一个将被用作新对象原型的对象。
const personPrototype = {
greet() {
console.log("hello!");
},
};
const carl = Object.create(personPrototype);
carl.greet(); // hello!
JavaScript中所有函数都有一个名为prototype
的属性。当我们调用一个函数作为构造函数时,这个属性会被设置为新构造函数对象的原型(按照惯例,在名为_proto_
的属性中)。
const personPrototype = {
greet() {
console.log(`你好,我的名字是 ${this.name}!`);
},
};
function Person(name) {
this.name = name;
}
Object.assign(Person.prototype, personPrototype);
// 或
// Person.prototype.greet = personPrototype.greet;
- 创建
personPrototype
对象 - 创建
Person()
构造函数 - 使用Object.assign将
personPrototype
中定义的方法绑定到Person()
函数的prototype
属性上。
这段代码之后,使用Person()
创建的对象将获得Person.prototype
作为其原型。其中包含greet
方法。
这里的Person
构造函数创建的对象由两个属性:
name
属性,在构造函数中设置,在Person
对象中可以直接看到greet()
方法,在原型中设置
这种模式,方法在原型上定义,数据属性在构造函数中定义。
方法对我们创建的每一个函数都是一样的,但每个对象的数据属性都有自己的值。
直接在对象中定义的属性,叫做自有属性,可以用静态方法Object.hasOwn().
检查一个属性是否是自有属性。
const irma = new Person("Irma");
console.log(Object.hasOwn(irma, "name")); // true
console.log(Object.hasOwn(irma, "greet")); // false
注意:也可以使用非静态方法object.hasOwnProperty().
,但更推荐静态方法Object.hasOwn().
方法。