这是我参与「第四届青训营」笔记创作活动的的第3天
JavaScript --解释性语言(跨平台)
js三大部分
ECMAScript、DOM(Document Object Model)、BOM(Browser Object Model)
ECMAScript扩展知识:
① ECMAScript是一个标准,JS只是它的一个实现,其他实现包括ActionScript。
② “ECMAScript可以为不同种类的宿主环境提供核心的脚本编程能力……”,即ECMAScript不与具体的宿主环境相绑定,如JS的宿主环境是浏览器,AS的宿主环境是Flash。
③ECMAScript描述了以下内容:语法、类型、语句、关键字、保留字、运算符、对象。
预编译
一、函数内部
1.生成AO对象
2.将形参和变量声明的名作为AO对象的属性名,并将其全部定义为undefined
3.将实参的值赋给形参
4.将函数声明的名作为AO对象的属性名,值全部修改为函数体
正式编译时会跳过已经预编译的部分,也就是说会跳过变量的声明以及函数的声明
二、全局
1.生成GO对象
2.将所有声明的变量作为GO对象的属性名,并将其全部定义为undefined
3.将函数声明的名作为GO对象的属性名,值全部改为函数体
大致同上
作用域和作用域链
AO和GO就是作用域
[[scope]]:每个函数是一个对象,[[scope]]是其一个属性
初始时:[[scope]] -- > 0 : GO
全局中的a函数执行时:a.[[scope]] -- > 0 : AO
a.[[scope]] -- > 1 : GO
a函数中的b函数执行时:b.[[scope]] -- > 0 : AO
b.[[scope]] -- > 1 : AO
b.[[scope]] -- > 2 : GO
且b.[[scope]]中的AO和GO是a.[[scope]]的引用
function a() {
function b() {
var bbb = 234;
document.write(aaa);
}
var aaa = 123;
return b;
}
var glob = 100;
var demo = a();
demo();
/* 因为b函数被返回给外部的demo接收,所以即便a函数执行完毕后,a函数的AO也并没有消失,而是被demo函数的作用域链所拥有,当demo函数执行时,a函数的AO依然可以被访问
此时可以看成是产生了闭包 */
立即执行函数
只执行一次的函数,执行之后立即被释放
(function () {}()); // 建议使用第一种
(function () {})();
// 只有表达式才能被执行符号执行
+function test() {
console.log('a');
}();
闭包
function test() {
var arr = [];
for (var i = 0; i < 10; i++) {
(function (j) {
arr[j] = function () {
document.write(j + " ");
}
}(i));
}
return arr;
}
var myArr = test();
for (var i = 0; i < 10; i++) {
myArr[i]();
}
原型
当对象通过构造函数创建时,则产生了空的原型对象,该对象相当于所创建对象的父类,所创对象继承其属性和方法。
Test.prototype = {
aaa : "test"
}
function Test() {
var name = "001";
}
var test = new Test();
console.log(test.aaa);
// 控制台打印test
原型对象中有一个隐式的constructor属性,值为该对象的构造函数,并且可以手动更改
对象构造时有隐式属性__proto__,当执行到new Test()时,该属性则会指向原型对象prototype,以此时的prototype为准
call/apply
function Test () {
this.t = 1;
}
function T() {}
var t = new T();
Test.call(t);
// 这时对象t中会有属性t = 1;
// call的作用是改变this的指向
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name, age, sex, tel, grade) {
Person.call(this, name, age, sex);
this.tel = tel;
this.grade = grade;
}
apply和call的区别只是传参列表不同,apply传参为数组
继承:圣杯模式
// 圣杯模式
function inherit(Target, Origin) {
function F(){}
F.propertype = Origin.propertype;
Target.propertype = new F(); // 使用F当做中间层
Target.propertype.constuctor = Target; // 自己的构造类
Target.propertype.uber = Orgin.prototype; // 超类:继承链的顶端
}
Father.prototype = {
this.name = "test";
}
function Father() {
}
function Son() {
}
Son.propertype = new F();
inherit(Son, Father);
var son = new Son();
var father = new Father();
// 结果使得Son完美继承Father的所有属性方法,同时在自己的父类更改时不会影响到Father
对象枚举
将一个对象的所有属性遍历
var obj = {
name: '1',
age: 1,
sex: 'male'
}
for (var prop in obj) {
console.log(obj.[prop]);
// 因为prop是string类型
}
obj.hasOwnProperty(属性名); // 判断是否为自己的属性, false为原型链上的属性
a instanceof A; // 看a对象的原型链上 有没有 A的原型
Dom元素的基本操作
查
document.getElementById('id'); // 通过id选择元素
document.getElementsByTagName('tagName'); // 通过标签名选择元素,在类数组中
document.getElementsByClassName('class'); // 通过类名选择元素,同样在类数组中
document.getElementsByName('name'); // 通过name选择元素,但只有部分标签生效(表单,表单元素,img,input,iframe等等)
document.querySelector(div > span strong); // 与css的选择方式相同
document.querySelectorAll(div > span strong); // 同上,区别在于它在数组中
// 弊端:它所选择出的元素并非实时的,而是副本,所以开发中不常用
**遍历节点树:
parent.Node; // 选择某一个元素的父级节点 父节点
childNodes; // 选择某一个元素的所有子节点 子节点们(这些节点包括文本节点等等)
// 以下则包含了七个节点
`<div>
123
<!-- This is comment -->
<strong></strong>
<span></span>
</div>`
firstChild / lastChild // 分别选择某一个元素的第一个和最后一个子节点
nextSibling / previousSibling // 后一个兄弟结点、前一个兄弟结点
**基于元素节点树的遍历 (除了children之外IE9以下不兼容)
parentElement; // 选择某一个元素的父级元素 父元素
children; // 选择某一个元素的所有子元素
firstElementChild; // 返回第一个子元素(IE不兼容)
lastElementChild; // 返回最后一个子元素(IE不兼容)
nextElementSibling / previousElementSibling // 返回后一个/前一个兄弟元素
增
document.createElement('div'); // 创建元素结点
document.createTextNode('一行字'); // 创建文本节点
document.createComment('this is comment');// 创建注释结点
标签.appendChild(); // 在标签元素中插入一个结点(最后)
标签.inserBefore(a, b); // 在标签元素里的b结点之前插入a结点
删
标签.removeChild(a);// 将标签中的a标签剪切返回(删除)
标签.remove(); // 直接将标签删除
替换
标签.replaceChild(new, origin); // 用new标签替换origin标签
Element节点的一些属性
innerHTML:标签的内容,可以进行修改
innerText(火狐不兼容)/textContent(老版本IE不好使):返回文本内容,赋值时会覆盖标签结构
Element节点的一些方法
setAttribute('属性名', '属性值'):设置标签的属性
getAttribute('属性名'):得到标签某个属性的内容
结点的四个属性
1.nodeName
2.nodeValue(只有文本节点和注释结点才有)
3.nodeType(返回特定值,用来区别节点类型) 结果为1时代表元素结点
4.attributes(该元素属性结点的集合)
Node.hasChildNodes(); // 判断其是否有子节点
Date对象
var date = new Date();
使用date获取到的时间是date对象被创建的时刻,并不会发生变化。
方法
www.w3school.com.cn/js/jsref_ob…
定时器
每隔固定时间执行代码
setInterval(function() {
console.log("你是0我是1");
}, 1000);
// 每隔1000毫秒在控制台打印一个"你是0我是1"
// 如果毫秒数所在的位置是一个变量的话,只是进行一次取值,后续变量的值被修改后并不会影响定时器
// 这个定时器是非常不准的
// 排列机制基于红黑树
// 返回唯一标识
clearInterval(); // 清除定时器
一段时间之后执行代码
setTimeout(function() {
console.log("你是0我是1");
}, 1000);
// 1000毫秒之后打印
// 返回唯一标识
clearTimeout(); // 清除定时器
这些函数全部是对象window上的方法,
获取窗口属性
获取滚动条的距离
window.pageXOffset/pageYOffset; // 获取横向/纵向滚动条的移动距离(单位px) IE8以及以下不兼容,IE9部分不兼容
document.body.scrollLeft/scrollTop;
document.documentElement.scrollLeft/scrollTop; // 这两种适用于IE8及以下浏览器,但是兼容性混乱,当其中一种有值时,另一种为0,所以使用时采用两个相加
// 封装方法,获取滚动条的移动距离
function getScrollOffset() {
if (pageYOffset) { // 或者pageXOffset
return { // 这里注意大括号要跟在return后面,否则相当于return;
x: pageXOffset,
y: pageYOffset
}
} else {
return {
x: document.body.scrollLeft + document.documentElement.scrollLeft,
y: document.body.scrollTop + document.documentElement.scrollTop
}
}
}
获取窗口可视区宽高
window.innerWidth/innerHeight; // 获取窗口可视区的宽度和高度额(单位px) IE8及以下不兼容
document.documentElement.clientWidth/clientHeight; // 标准模式下,任意浏览器都兼容
document.body.clientWidth/clientHeight; // 适用于怪异模式下的浏览器(下方有对其的解释)
// 封装方法,获取可视区窗口宽高
function getViewportOffset() {
if (window.innerWidth) {
return {
width: window.innerWidth,
height: window.innerHeight
}
}
else {
if (document.compatMode == "CSS1Compat") { // compatMode: 返回浏览器模式 1.CSS1Compat正常模式 2.BackCompat怪异模式
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
}
}
else {
return {
width: document.body.clientWidth,
height: document.body.clientHeight
}
}
}
}
浏览器渲染模式:怪异/混杂模式
浏览器会对之前的版本兼容,抛弃当前版本的更新。
开启方式:删除HTML文件开头的
获取dom元素几何尺寸
domEle.getBoundingRect();
// 兼容性很好
// 返回一个对象,包含left、top、right、bottom、height和width等属性。left和top代表该元素左上角的X坐标和Y坐标,right和bottom代表该元素右下角的X坐标和Y坐标
// height和width老版本IE并未实现(使用right-left和bottom-top获取宽高)
// 返回的结果并不是"实时的"
domEle.offsetWidth, domEle.offsetHeight // 返回元素尺寸,大小为盒子的宽高
dom.offsetLeft, dom.offsetTop // 对于无定位的父级元素,返回相对文档的坐标。对于有定位的父级元素,返回相对于最近的有定位的父级的坐标
//△ 上面的方法会使得domTree重排,效率较低
dom.offsetParent // 返回最近的有定位的父级,如果没有则返回body,body.offsetParent返回null
让滚动条滚动
window.scroll(x, y) / window.scrollTo(x, y); // x和y是坐标
window.scrollBy(x, y); // 继续往下滚动多少距离
脚本化css
读写元素css属性
dom.style.prop
dom.style.float -> dom.style.cssFloat
// 复合属性必须拆解,组合单词变成小驼峰式写法,如background-color -> backgroundColor
// 写入的值为字符串格式
读取元素css属性
window.getComputedStyle(dom, null).prop;
// 第二个参数用于获取伪元素的属性,例如:window.getComputedStyle(dom, before).width,获取before伪元素的宽度
// 返回值为当前元素实际显示的效果
// 返回的计算样式的值是绝对值
// IE8及以下不兼容
ele.currentStyle.prop // IE独有
// 返回的计算样式的值不是经过转换的绝对值
// 封装获取元素属性函数
function getStyle(elem, prop) {
if (window.getComputedStyle) {
return window.getComputedStyle(elem, null)[prop];
} else {
return elem.currentStyle[prop];
}
}
事件
事件绑定
1.ele.onxxx = function(event) {}
// 兼容性很好,但是一个元素的同一个事件上只能绑定一个处理程序
// 基本等同于写在HTML行间上
2.obj.addEventListener(事件类型, 处理函数, false);
// IE9以下不兼容,可以为一个事件绑定多个"不同"的处理程序
3.obj.attachEvent('on' + 时间类型, 处理函数);
// 特殊之处:this.指向window
// 解决办法:
obj.attachEvent('onclick', function() {
handle.call(obj);
});
function handle() {
// 事件处理程序
// 此时this指向的一定是obj
}
// *封装添加事件函数
function addEvent(elem, type, handle) {
if (elem.addEventListener) {
elem.addEventListener(type, handle, false);
} else if (elem.attachEvent) {
elem.attachEvent('on' + type, function() {
handle.call(elem);
})
} else {
elem['on' + type] = handle;
}
}
事件解除
// 解除绑定事件
1.ele.onclick = false/''/null
2.ele.removeEventListener(事件类型, 处理函数, false);
3.ele.detachEvent('on' + 事件类型, 处理函数); // 注意这两个里面的处理函数必须是同一个函数,如果绑定匿名函数则无法解除绑定事件
事件处理模型——事件冒泡、捕获
事件冒泡:结构上(非视觉上)嵌套关系的元素,会存在事件冒泡功能,即同一事件,自子元素冒泡向父元素。(自底向上)
事件捕获:结构上(非视觉上)嵌套关系的元素,会存在事件捕获功能,即同一事件,自父元素捕获至子元素(事件源元素)。(自顶向下) // IE不支持事件捕获功能。
触发顺序(当同一个事件类型分别绑定了两个不同的处理函数,并且这两个处理函数分别遵循事件冒泡和事件捕获):先捕获,后冒泡
focus, blur, change, submit, reset, select等事件不冒泡
解除事件冒泡
// 绑定事件时添加参数
ele.onclick() = function(event) {
// event
event.stopPropagation(); // 解除事件冒泡,W3C标准,不支持IE9以下版本
event.cancleBubble = true; // IE支持,谷歌同样支持
}
// 封装解除事件冒泡函数
function stopBubble(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancleBubble = true;
}
}
阻止默认事件:
默认事件——表单提交,a标签跳转,右键菜单等
1.return false; 以对象属性的方式注册的事件才生效,兼容性极好
2.event.preventDefault(); W3C标准,IE9以下不兼容
3.event.returnValue = false; 兼容IE
// 以右键菜单举例
document.oncontextmenu = function(e) {
e.preventDefault();
e.returnValue = false;
return false;
}
// 封装阻止默认事件函数
function cancleHandler(event) {
if (e.preventDefault) {
e.preventDefault();
}
else {
e.returnValue = false;
}
}
事件对象
event || window.event // 后者用于IE
ele.onclick = function(e) {
let event = e || window.event;
console.log(event);
}
事件源对象:触发次事件的对象,如,由一个子元素触发时间冒泡而来,源对象则为该子元素
event || window.event // 后者用于IE
ele.onclick = function(e) {
let event = e || window.event;
// 火狐只有:event.target; IE只有:event.srcElement;谷歌都有(谷歌nb!)
let target = event.target || event.srcElement;
console.log(target); // 事件源对象
}
事件委托
利用事件冒泡,和事件源对象进行处理
优点:1.性能 不需要循环所有的元素依次绑定事件
2.灵活 当有新的子元素时不需要重新绑定事件
鼠标事件
△click、mousedown、mousemove、mouseup、contextmenu、mouseout、△mouseover、mouseenter、mouseleave △用button来区分鼠标的按键,0/1/2 DOM3标准规定:click事件只能监听左键,只能通过mousedown和mouseup来判断鼠标键
键盘事件
△keydown、keyup、keypress △keydown > keypress > keyup △keydown:检测键盘上所有按键,charCode等于0 △keypress:检测键盘上的字符类按键,charCode等于该字符的阿斯克码
文本操作事件
△input、focus、blur、change
窗体操作事件(window上的事件)
onscroll onload:dom树和css树、rander树等全部准备就绪之后执行(不要把主要程序放在其中)
Json,异步加载,时间线
Json
一种传输数据的格式(以对象为样板,本质上就是对象,但用途有区别,对象就是本地用的,json是用来传输的)
JSON.parse(); string -> json
Json.stringify(); json -> string
异步加载js
js加载的缺点:加载工具方法没必要阻塞文档,过多js加载会影响页面效率,一旦网速不好,整个网站将等待js加载而不进行后续渲染工作。
有些工具需要按需加载,用到再加载,不用不加载
js异步加载的三种方案:
1.defer异步加载,但要等到dom文档全部 解析 完才会被执行。只有IE能用,也可以将代码写到内部
<script defer="defer"></script>
2.async异步加载,加载完就执行,async只能加载外部脚本,不能把js写在script标签里,其中的代码也会执行,但不异步
<script async="async"></script>
前两种执行的时候也不阻塞页面
3.创建script,插入到DOM中,加载完毕后callBack,
function loadScript(url, callBack) {
var script = document.creatElement('script');
script.type = "text/javascript";
if (script.readyState) {
script.onreadystatechange = function() {
// IE浏览器兼容方法
if (script.readyState == "complete" || script.readyState == "loaded") {
callBack();
}
}
} else {
script.onload = function() {
// Safari Chrome Firefox Opera
callBack();
}
}
script.src = url; // 先绑定事件,再加载文件
document.head.appendChild(script); // script标签添加到head中
}
loadScript("demo.js", function() {
test(); // 待执行函数
})
// 下面是两种其他的调用方式
eval(callback); // eval函数会将参数内容中的字符串当做函数执行,但不推荐使用
tools[callback](); // callback为字符串
/* var tools = {
test: function() {
console.log('a');
}
}*/
js加载时间线
1.创建Document对象,开始解析web页面。解析HTML元素和它们的文本内容后添加Element对象和Text结点到文档中。这个阶段document.readyState = 'loading';
2.遇到link外部css,创建线程加载,并继续解析文档。
3.遇到script外部js,并且没有设置async、defer,浏览器加载,并阻塞,等待js加载完成并执行该脚本,然后继续解析文档。
4.遇到script外部js,并且设置有async、defer,浏览器创建线程加载,并继续解析文档。 对于async属性的脚本,脚本加载完后立即执行。(异步禁止使用document.write())
5.遇到img等,先正常解析dom结构,然后浏览器异步加载src,并继续解析文档。
6.当文档解析完成,document.readyState = 'interactive';
7.文档解析完成后,所有设置有defer的脚本会按照顺序执行。(注意与async的不同,但同样禁止使用document.write())
8.document对象触发DOMContentLoaded事件,这也标志着程序执行从同步脚本执行阶段,转化为事件驱动阶段。
9.当所有async的脚本加载完成并执行后、img等加载完成后,document.readyState = 'complete',window对象触发onload事件。
10.从此,以一步响应方式处理用户输入、网络事件等。
正则表达式RegExp
正则表达式的作用:匹配特殊字符或有特殊搭配原则的字符的最佳选择。
规则创建方式
let reg = /模式/标记;
let reg = new RegExp("模式", "标记");
标记flags:
i:不区分大小写,表示在查找匹配时忽略pattern和字符串的大小写。
g:全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束。
m:多行模式,表示查到一行文本末尾时会继续查找。
y:粘附模式,表示只查找从lastIndex开始及之后的字符串。
u:Unicode模式,启用Unicode匹配。
s:dotAll模式,表示元字符,匹配任何字符。
\