2022前端青训营笔记-第三讲

154 阅读12分钟

这是我参与「第四届青训营」笔记创作活动的的第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();
// 兼容性很好
// 返回一个对象,包含lefttoprightbottomheightwidth等属性。lefttop代表该元素左上角的X坐标和Y坐标,rightbottom代表该元素右下角的X坐标和Y坐标
// heightwidth老版本IE并未实现(使用right-leftbottom-top获取宽高)
// 返回的结果并不是"实时的"
​
domEle.offsetWidth, domEle.offsetHeight // 返回元素尺寸,大小为盒子的宽高
dom.offsetLeft, dom.offsetTop   // 对于无定位的父级元素,返回相对文档的坐标。对于有定位的父级元素,返回相对于最近的有定位的父级的坐标
​
//△ 上面的方法会使得domTree重排,效率较低
​
dom.offsetParent    // 返回最近的有定位的父级,如果没有则返回bodybody.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模式,表示元字符,匹配任何字符。

\