javascript-2

210 阅读9分钟

DOM

  • DOM是操控JS和HTML、CSS的桥梁
  • 使用节点思维 DOM节点树

image.png

DOM简介

DOM(文档对象模型) 是js操作HTML文档的接口。
DOM最大的特点是将文档表示为节点树

nodeType常用属性值

nodeType值节点类型
1元素节点,例如<p><div>
3文字节点
8注释节点
9document节点
10DTD节点

认识document对象

  1. document对象是DOM中最重要的东西,几乎所有DOM的功能都封装在了document对象中
  2. document对象也表示整个文档,它是DOM节点数的根
  3. document对象的nodeType属性值是9

节点

访问元素节点的方法

  • 所谓访问元素节点,就是指得到,获取页面上的元素节点

document.getElementById()

  • 有相同id的元素,则只能得到第一个
  • 不管元素隐藏的有多深,都能通过id把它找到

延迟运行

  • 在测试DOM代码时,通常JS代码一定要写到HTML节点的后面,否则JS无法找到相应的HTML节点
  • 使用window.onload=function(){}使页面加载完成之后,再执行指定的代码

getElementsByClassName()

  • 从IE9开始兼容
  • 某个节点元素也可以调用getElementsByClassName()方法的,从而得到其内部的某类名的元素节点 querySelector
  • 只能得到页面上一个元素,如果有多个元素符合条件,则只能得到第一个元素
  • IE8开始兼容,IE9开始支持CSS3的选择器 | 方法 |功能 |兼容性| | --- | --- | --- | | document.getElementById() |通过id得到元素 |IE6| |document.getElementsByTagName()|通过标签名得到元素数组|IE6| |document.getElementsByClassName()|通过类名得到元素数组|IE9| |document.querySeletor()|通过选择器得到元素|IE8部分兼容,IE9完全兼容| |document.querySeletorAll()|通过选择器得到元素数组|IE8部分兼容,IE9完全兼容|

节点的关系

父节点考虑所有节点只考虑元素节点
子节点childNodeschilidren
父节点parentNode
第/最后一个子节点firstChild/lastChildfirstElementChild/lastElementChild
前/后一个兄弟节点previousSibling/nextSilibingpreviousElementSibling/nextElementSilibing

只考虑元素节点可以排除文本节点的干扰

封装节点关系函数

 //封装一个函数,这个函数可以返回元素的所有子元素节点
        function getChildren(node){
            var chilidren=[];
            for(var i=0;i<node.childNodes.length;i++){
                if(node.childNodes[i].nodeType==1){
                    chilidren.push(node.childNodes[i])
                }
            }
            return chilidren;
        }
        console.log(getChildren(box))
// 封装一个函数,这个函数可以返回元素的前一个元素兄弟节点(兼容到IE6),类似previousElementSibling的功能
        function getElementPrevSibling(node) {
            var o = node;
            // 使用while语句
            while (o.previousSibling != null) {
                if (o.previousSibling.nodeType == 1) {
                    // 结束循环,找到了
                    return o.previousSibling;
                }

                // 让o成为它的前一个节点,就有点“递归”的感觉
                o = o.previousSibling;
            }
            return null;
        }

        console.log(getElementPrevSibling(para));
        console.log(getElementPrevSibling(fpara));
        // 封装第三个函数,这个函数可以返回元素的所有元素兄弟节点
        function getAllElementSibling(node) {
            // 前面的元素兄弟节点
            var prevs = [];
            // 后面的元素兄弟节点
            var nexts = [];
            
            var o = node;
            // 遍历node的前面的节点
            while(o.previousSibling != null) {
                if(o.previousSibling.nodeType == 1){
                    prevs.unshift(o.previousSibling);
                }
                o = o.previousSibling;
            }

            o = node;

            // 遍历node的后面的节点
            while(o.nextSibling != null) {
                if(o.nextSibling.nodeType == 1){
                    nexts.push(o.nextSibling);
                }
                o = o.nextSibling;
            }

            // 将两个数组进行合并,然后返回
            return prevs.concat(nexts);
        }

        console.log(getAllElementSibling(para));

如何改变元素节点中的内容

改变元素节点中的内容可以使用两个相关属性:
innerHTML innerText

  • innerHTML属性能以HTML语法设置节点中的内容
  • innerText属性只能以纯文本的形式设置节点中的内容

节点的创建、移除和克隆

创建节点var oDiv=document.createElement('div');
注:新创建出的节点是孤儿节点,这意味着它并没有被挂载到DOM树上,我们无法看到他;
必须使用appendChild()或insertBefore()方法将孤儿节点插入到DOM树上;

  • 父节点.appendChild(孤儿节点),此方法将孤儿节点挂载到它的内部,成为它的子节点;
  • 父节点.insertBefore(孤儿节点,标杆节点)成为它的标杆节点之前的子节点
 var oBox = document.getElementById('box');
        var oPs = oBox.getElementsByTagName('p');

        // 创建孤儿节点
        var oP = document.createElement('p');
        // 设置内部文字
        oP.innerText = '我是新来的';
        
        // 上树
        // oBox.appendChild(oP);
        oBox.insertBefore(oP, oPs[0]);

移动节点
如果将已经挂载到DOM树上的节点成为appendChild()或者insertBefore()的参数,这个节点将会被移动

  • 新父节点.appendChild(已经有父亲的节点);
  • 新父节点.insertBefore(已经有父亲的节点,标杆子节点);
    这意味着一个节点不能同时位于DOM树的两个位置

删除节点
removeChild()方法从DOM中删除一个子节点
父节点.removeChild(要删除子节点);
节点不能主动删除自己,必须由父节点删除它

克隆节点

  • var 孤儿节点=老节点.cloneNode();
  • var 孤儿节点=老节点.cloneNode(true);
    参数是一个布尔值,表示是否采用深度克隆
  • 如果为true,则该节点的所有后代节点也都会被克隆
  • 如果为false,则只克隆该节点本身

DOM事件

事件监听

onxxx或者addEventListener

鼠标事件监听

事件名事件描述
onclick鼠标单击某个事件
ondbclick鼠标双击某个事件
onmousedown当某个鼠标按键在某个对象上被按下
onmouseup当某个鼠标按键在某个对象上被松开
onmousemove当某个鼠标按键在某个对象上被移动
onmouseenter当鼠标进入某个对象(相似事件onmouseover )
onmouseleave当鼠标离开某个对象(相似事件onmouseout )

键盘事件监听

事件名事件描述
onkeypress当某个键盘的键被按下(系统按钮如箭头键和功能键无法得到识别)
onkeydown当某个键盘的键被按下(系统按钮可以识别,并且会先于onkeypress发生)
onkeyup当某个键盘的键被松开

表单事件监听

标题
onchange当用户改变域的内容
onfocus当某元素获得焦点(比如tab键或鼠标点击)
onblur当某元素失去焦点
onsubmit当表单被提交
onreset当表单被重置

页面事件监听

事件名事件描述
onload当页面或图像被完全加载
onunload当用户退出页面

事件传播

事件的传播是先从外到内(事件捕获),再从内到外(事件冒泡)

  • DOM0级事件监听:只能监听到冒泡阶段
    oBox.onclick=function(){};
  • DOM2级事件监听 oBox.addEventListener('click',function(){},true);
    //true 监听捕获阶段
    //false 监听冒泡阶段

  • 内部元素不再区分捕获和冒泡阶段,会先执行写在前面的监听,然后执行后写的监听
  • 如果给元素设置相同的两个或多个同名事件,DOM0后面写的会覆盖先写的;而DOM2级会按顺序执行

事件对象

事件处理函数提供一个形参,它是一个对象,封装了本次事件的细节
通常用event或e来表示
var onmousemove=function(e){}//对象e就是这次事件的事件对象

事件对象(1)

属性属性描述
clientX鼠标指针相对于浏览器的水平坐标
clientY鼠标指针相对于浏览器的垂直坐标
pageX鼠标指针相对于整张网页的水平坐标
pageY鼠标指针相对于整张网页的垂直坐标
offsetX鼠标指针相对于事件源元素的水平坐标
offsetY鼠标指针相对于事件源元素的垂直坐标

qq_pic_merged_1623937433702.jpg

事件对象(2)

e.charCode属性和e.keyCode属性

  • e.charCode属性通常用于onkeypress事件中,表示用户输入的字符的“字符码”
  • e.keyCode属性通常用于onkeydown事件和onkeyup中,表示用户按下的按键的“键码”
charCode字符charCode字符码
数字0-948-57
大写字母A-Z65-90
小写字母a-z97-122

keyCode键码

image.png

事件对象(3)

e.preventDefault() 阻止事件产生的默认动作
e.stopPropagation() 阻止事件继续传播

事件委托

(1)批量添加事件监听

  • 代码
 var oList=document.getElementById('list');
        var lis = oList.getElementsByTagName('li');
        for(var i=0;i<lis.length;i++){
            lis[i].onclick=function(){
                this.style.color='red';
            }
        }
  • 批量事件监听的性能问题
  1. 每一个事件监听注册都会消耗一定的系统内存,而批量添加事件会导致监听数量太多,内存消耗会非常大
  2. 实际上,每个<li>的事件处理函数都是不同的函数,这些函数本身也会占用内存

(2)动态绑定事件

    <button id='btn'>点击添加新的列表项</button>
    <ul id='list'></ul>
    <script>
        var oBtn = document.getElementById('btn');
        var oList = document.getElementById('list');
        var lis = document.getElementsByTagName('li');
        oBtn.onclick = function(){
            var oLi=document.createElement('li');
            oLi.innerHTML = '我是列表项';
            oList.appendChild(oLi);
            oLi.onclick = function(){
                this.style.color='red';
            }
        }
    </script>
  • 动态绑定事件的问题
  1. 新增元素必须分别添加事件监听,不能自动获得事件监听
  2. 大量事件监听、大量事件处理函数都会产生大量消耗内存

故:出现了事件委托
利用事件冒泡机制,将后代元素委托给祖先元素

属性属性描述
target触发此事件的最早元素,即‘事件源元素’
currentTarget事件处理程序附加到的元素
var oList = document.getElementById('list');
 oList.onclick = function (e) {
     // e.target表示用户真正点击的那个元素
     e.target.style.color = 'red';
  };
  • 事件委托的使用场景
  1. 当有大量类似元素需要批量添加事件监听时,使用事件委托可以减少内存开销
  2. 当有动态元素节点上树时,使用事件委托可以让新上树的元素具有事件监听
  • 使用事件委托注意事项
  1. 不能委托不冒泡的事件给祖先元素(onmouseenter不冒泡,onmouseover冒泡)
    onmouseenter这个属性天生就是“不冒泡”的,相当于你事件处理函数附加给了哪个DOM节点,就是哪个DOM节点自己触发的事件,没有冒泡过程

  2. 最内层元素不能再有内层元素了

面试-写一个通用的事件侦听器函数

定时器和延时器

定时器

setInterval()函数可以重复调用一个函数,在每次调用之间具有固定的时间间隔

  • 函数的参数
setInterval(function(a,b){   //第一个参数是函数
  //a的值为88,b的值为66
},2000,88,66)                  //第二个参数是间隔时间,以毫米为单位,1000ms是一秒
  • 具名函数也可以传入定时器中
var a=0;
function fun(){
  console.log(++a)
}
setInterval(fun,1000);   //具名函数被当做参数,这里没有(),fun表示的是一个参数
  • 清除定时器
 <h1 id="info">0</h1>
    <button id="btn1">开始</button>
    <button id="btn2">暂停</button>

    <script>
        var oInfo = document.getElementById('info');
        var oBtn1 = document.getElementById('btn1');
        var oBtn2 = document.getElementById('btn2');

        var a = 0;

        // 全局变量
        var timer;

        oBtn1.onclick = function () {
            // 为了防止定时器叠加,我们应该在设置定时器之前先清除定时器
            clearInterval(timer);
            // 更改全局变量timer的值为一个定时器实体
            timer = setInterval(function () {
                oInfo.innerText = ++a;
            }, 1000);
        };

        oBtn2.onclick = function () {
            clearInterval(timer);
        };
    </script>

延时器

指定时间到了,会执行函数一次,不再重复执行 setTimeout(function(){},1000)

清除延时器clearTimeout()

  • 异步语句 serInterval()和setTimeout()是两个异步语句
    异步:不会阻塞CPU继续执行其他语句,当异步完成后,会执行回调函数

使用定时器实现动画

利用的是视觉暂留的原理

  • 使用定时器实现动画较为不便
  1. 不方便根据动画董事兼计算步长
  2. 运动方向要设置正负
  3. 多种运动进行叠加较为困难

JS和CSS3结合实现动画

JS利用CSS3的transition过渡属性实现动画

    <button id="btn">按我运动</button>
    <div id="box"></div>
    <script>
        // 得到元素
        var btn = document.getElementById('btn');
        var box = document.getElementById('box');
        // 标识量,指示当前盒子在左边还是右边
        var pos = 1;    // 1左边,2右边
        // 函数节流锁
        var lock = true;
        // 事件监听
        btn.onclick = function () {
            // 首先检查锁是否是关闭
            if (!lock) return;
            // 把过渡加上
            box.style.transition = 'all 2s linear 0s';
            if (pos == 1) {
                // 瞬间移动,但是由于有过渡,所以是动画
                box.style.left = '1100px';
                pos = 2;
            } else if (pos == 2) {
                // 瞬间移动,但是由于有过渡,所以是动画
                box.style.left = '100px';
                pos = 1;
            }
            // 关锁
            lock = false;
            // 指定时间后,将锁打开
            setTimeout(function() {
                lock = true;
            }, 2000);
        };
    </script
  • 实现的两个小demo
  1. 无限滚动特效
  2. 轮播图特效

BOM

  • BOM(浏览器对象模型),JS与浏览器窗口交互的接口
  • 浏览器改变尺寸,滚动条滚动相关特效,都需要用到BOM技术

window对象

window对象是当前JS脚本运行所处的窗口,而这个窗口中包含DOM结构,window.document属性就是document对象

  • 全局变量是window对象的属性 多个js是共享全局作用域的
  • 内置函数普遍是window的方法 如setInterval()、alert()等内置函数,普遍是window的方法
  • 窗口尺寸相关属性
属性意义
innerHeight浏览器窗口的内容区域的高度,包含水平滚动条(如果有的话)
innerWidth浏览器窗口的内容区域的宽度,包含垂直滚动条(如果有的话)
outerHeight浏览器窗口的外部高度
outerWidth浏览器窗口的外部宽度

获得不包含滚动条的窗口宽度,要用 document.documentElement.clientWidth

  • resize事件 在窗口大小改变之后,就会触发resize事件,可以使用window.onresize或window.addEventListener('resize')来绑定事件处理函数
  • 已卷动高度
  1. window.scrollY属性表示在垂直方向已滚动的像素值
  2. document.documentElement.scrollTop属性也表示窗口卷动高度 var scrollTop = window. scrollY || document.documentElement. scrollTop; document.documentElement.scrollTop不是只读的
    而window.scrolIY是只读的
  • scroll事件 在窗口被卷动之后,就会触发scroll事件,可以使用 window.onscroll或者window.addEventListener('scroll')来绑定事件处理函数

Navigator对象

window.navigator属性可以检索navigator对象,它内部含有用户此次活动的浏览器的相关属性和标识

属性意义
appName浏览器官方名称
appVersion浏览器版本
userAgent浏览器的用户代理(含有内核信息和封装壳信息)
platform用户操作系统

识别用户浏览器品牌
navigator.userAgent

history对象

window.history对象提供了操作浏览器会话历史的接口

  • 常用操作就是模拟浏览器回退按钮 history.back(); //等同于点击浏览器的回退按钮
    history.go(-1); //等同于history.back();

Location对象

  • window.location 标识当前所在网址,可以通过给这个属性赋值命令浏览器进行页面跳转
  • 重新加载当前页面,location.reload()方法,true表示强制从服务器强制加载
  • GET请求查询参数 window.location.search属性即为浏览器的GET请求查询参数

BOM特效开发

  • 返回顶部按钮制作 返回顶部的原理:改变document.documentElement.scrollTop属性,通过定时器逐步改变此值,则将用动画形式返回顶部
  <style>
        body {
            height: 5000px;
            background-image: linear-gradient(to bottom, blue, green, yellow);
        }

        .backtotop {
            width: 60px;
            height: 60px;
            background-color: rgba(255, 255, 255, .6);
            position: fixed;
            bottom: 100px;
            right: 100px;
            /* 小手状 */
            cursor: pointer;
        }
    </style>
 <div class="backtotop" id="backtotopBtn">返回顶部</div>

    <script>
        var backtotopBtn = document.getElementById('backtotopBtn');

        var timer;
        backtotopBtn.onclick = function () {
            // 设表先关
            clearInterval(timer);

            // 设置定时器
            timer = setInterval(function () {
                // 不断让scrollTop减少
                document.documentElement.scrollTop -= 200;
                // 定时器肯定要停
                if (document.documentElement.scrollTop <= 0) {
                    clearInterval(timer);
                }
            }, 20);
        };
    </script>
  • 楼层导航小效果
  1. DOM元素都有offsetTop属性,表示此元素到定位祖先元素的垂直距离
  2. 定位祖先元素:在祖先中,离自己最近的且拥有定位属性的元素

面向对象

认识对象

对象是键值对的集合,表示属性和值的映射关系

  • 属性的访问 如果属性名以变量形式存储,则必须使用方括号形式
var obj={
  a:1,
  b:2
};
var key='b';
console.log(obj.key);//undefied
console.log(obj[key]);//2
  • 属性的更改 直接使用赋值运算符对某属性赋值即可更改属性
  • 属性的创建 如果对象本身没有某个属性值,则用点语法赋值时,这个属性会被创建出来
var obj = {
   a: 10
};
obj.b = 40;
  • 属性的删除:delete

对象的方法

如果某个属性值是函数,则它也被称为对象的方法
使用点语法可以调用对象的方法

对象的遍历

使用for...in...循环可以遍历对象的每个键
for(var k in obj){}
此时是浅克隆

对象的深浅克隆

  • 对象是引用类型值,这意味着
  1. 不能用var obj2 = obj1这样的语法克隆一个对象
  2. 使用==或者===进行对象的比较时,比较的是他们是否为内存中的同一个对象,而不是比较值是否相同
  • 对象的浅克隆
  1. 只克隆对象的表层,如果对象的某些属性值又是引用类型,则不进一步克隆他们,只是传递它们的引用
  2. 使用for...in...循环可实现对象的浅克隆
 var obj1 = {
            a: 1,
            b: 2,
            c: [44, 55, 66]
        };

        // 实现浅克隆
        var obj2 = {};
        for (var k in obj1) {
            // 每遍历一个k属性,就给obj2也添加一个同名的k属性
            // 值和obj1的k属性值相同
            obj2[k] = obj1[k];
        }

        // 为什么叫浅克隆呢?比如c属性的值是引用类型值,那么本质上obj1和obj2的c属性是内存中的同一个数组,并没有被克隆分开。
        obj1.c.push(77);
        console.log(obj2);                  // obj2的c属性这个数组也会被增加77数组
        console.log(obj1.c == obj2.c);      // true,true就证明了数组是同一个对象
  • 对象的深克隆
  1. 克隆对象的全貌,不论对象的属性值是否为引用类型值,都能将他们实现克隆
  2. 使用递归方法
  3. 面试经常考深克隆算法
        var obj1 = {
            a: 1,
            b: 2,
            c: [33, 44, {
                m: 55,
                n: 66,
                p: [77, 88]
            }]
        };

        // 深克隆
        function deepClone(o) {
            // 要判断o是对象还是数组
            if (Array.isArray(o)) {
                // 数组
                var result = [];
                for (var i = 0; i < o.length; i++) {
                    result.push(deepClone(o[i]));
                }
            } else if (typeof o == 'object') {
                // 对象
                var result = {};
                for (var k in o) {
                    result[k] = deepClone(o[k]);
                }
            } else {
                // 基本类型值
                var result = o;
            }
            return result;
        }
        
        var obj2 = deepClone(obj1);
        console.log(obj2);
        
        console.log(obj1.c == obj2.c);      // false  内存中的不同对象
        
        obj1.c.push(99);
        console.log(obj2);                  // obj2不变的,因为没有“藕断丝连”的现象

上下文规则(this)

函数的上下文由调用函数的方法决定

var obj={
  a:1,
  b:2,
  fn:function(){
    console.log(this.a+this.b); //3
    console.log(this===obj);  //true
  }
}
fn();

但是如果变成var fn=obj.fn;  fn();
则此时this===window,
this.a+this.b输出的值为NaN

规则1 对象.方法()

对象打点调用他的方法函数,则函数的上下文是这个打点的对象

  • 题目举例-1
       function fn(){
            console.log(this.a+this.b);  //20  fn放在obj中被调用了
        }
        var obj={
            a:10,
            b:10,
            fn:fn
        }
        obj.fn()
  • 题目举例-2
 var obj1={
            a:1,
            b:2,
            fn:function(){
                console.log(this.a+this.b);  //7
            }
        }
        var obj2={
            a:3,
            b:4,
            fn:obj1.fn
        }
        obj2.fn();
  • 题目举例-3
function outer(){
  var a=11;
  var b=22;
  return{
   var c=33;
   var d=44;
   fn:function(){
     console.log(this.a+this.b);//77
   }
 }
}
outer().fn();
  • 题目举例-4
function fun(){
  console.log(this.a+this.b);//7
}
var obj={
  a:1,
  b:2z,
  c:[{
    a:3,
    b:4,
    c:fun
  }]
};
var a=5;
obj.c[0].c();

规则2 函数()

圆括号直接调用函数,则函数的上下文是window对象

  • 题目举例-1
var obj1={
  a:1,
  b:2,
  fn:function(){
    console.log(this.a+this.b);//7
  }
};
var a=3;
var b=4;
var fn=obj1.fn;
fn();
  • 题目举例-2
 function fun(){
            return this.a+this.b
        }
        var a=1;
        var b=2;
        var obj={
            a:3,
            b:fun(),  //适用规则2
            fun:fun
        }
        var result=obj.fun(); //适用规则1
        console.log(result);  //6

规则3 数组[下标]()

数组(类数组对象)枚举出函数进行调用,上下文是这个数组(类数组对象)

  • 题目举例-1(数组对象)
var arr=['A','B','C',function(){
  console.log(this[0]);
}]
arr[3]();  //"A"
  • 题目举例-2(类数组对象)
   function fun(){
            arguments[3]();
        }
        fun('A','B','C',function(){
            console.log(this[1]);   //B
        })

规则4 IIFE(function(){})()

IIFE中的函数,上下文是window对象

  var a=1;
        var obj={
            a:2,
            fun:(function(){
                var a=this.a;  //1
                return function(){
                    console.log(a+this.a);  //1+2=3
                }
            })()  //适用规则4
        }
        obj.fun()  //适用规则1

规则5 定时器、延时器

定时器、延时器调用函数,上下文是window对象

  • setInterval(函数,时间)
  • setTimeout(函数,时间)
  • 题目举例-1
var obj={
  a: 1,
  b: 2,
  fun: function () {
    console.log (this.a + this.b);  //7
  }
}
var a = 3;
var b = 4;
setTimeout(obj.fun,2000);适用规则5
  • 题目举例-2
var obj={
  a: 1,
  b: 2,
  fun: function () {
    console .log (this.a + this.b);  //3
  }
}
var a = 3;
var b = 4;
setTimeout(function(){
    obj.fun();   //适用规则1
},2000);

规则6 DOM元素

事件处理函数的上下文是绑定事件的DOM元素
DOM元素.onclick=function(){};

点击哪个盒子,哪个盒子在2000ms就变红
       function setColorToRed() {
            // 备份上下文
            var self = this;
            setTimeout(function() {
                self.style.backgroundColor = 'red';
            }, 2000);
        }

        var box1 = document.getElementById('box1');
        var box2 = document.getElementById('box2');

        box1.onclick = setColorToRed;
        box2.onclick = setColorToRed;

规则7 call和apply

call和apply可以任意指定函数的上下文

 function sum(b1, b2) {
            alert(this.c + this.m + this.e + b1 + b2);//弹出两次278
        };

        var xiaoming = {
            c: 100,
            m: 90,
            e: 80
        };

        sum.call(xiaoming, 3, 5);
        sum.apply(xiaoming, [3, 5]);
  • call和apply的区别
  1. call要用逗号罗列参数
  2. apply要把参数写在数组中
  • 到底使用call还是apply
function fun1() {
  fun2.apply(this, arguments);//这里用到了类数组,只能使用apply
}
function fun2(a,b) {
  alert(a + b); //77,因为用了arguments,把a,b的值作为数组传入到了arguments中
}
fun1(3344);

以上规则的总结

对象函数
对象.函数()对象
函数()window
数组[下标]()数组
IIFEwindow
定时器window
DOM事件处理函数绑定DOM的元素
call和apply任意指定

构造函数和类

用new操作符调用函数

new 函数()

  • 四步走
  1. 函数体内自动创建一个空白对象
  2. 函数的上下文(this)会指向这个对象
  3. 函数体内的语句会执行
  4. 函数会自动返回上下文对象,即使函数没有return语句

image.png

构造函数

什么是构造函数

  • 用new调用一个函数,这个函数就被称为“构造函数”,任何函数都可以是构造函数,只需要用new调用它
  • 顾名思义,构造函数用来“构造新对象”,它内部的语句将为新对象添加若干属性和方法,完成对象的初始化
  • 构造函数必须用new关键字调用,否则不能正常工作,正因如此,开发者约定构造函数命名时首字母要大写 构造函数中的this不是这个函数本身,而是创建的新对象

类与实例

类好比是蓝图,实例是具体的对象

image.png

JavaScript是基于对象语言

原型和原型链

prototype

  • 任何函数都有prototype属性
  • prototype属性值是个对象,它默认拥有constructor属性指回函数

image.png

  • 构造函数的prototype属性是它的实例的原型 xiaoming.__proto__ === People.prototype //true
    ==和===都是true

image.png

原型链

实例可以打点访问它的原型的属性和方法,这被称为‘原型链查找’

function People(name,age,sex){
  this.name=name;
  this.age=age;
  this.sex=sex;
}
People.prototype.nationality = '中国'; 
var xiaoming = new People('小明',12,'男');
console.log(xiaoming.nationality);//实例可以打点访问原型的属性和方法

如下图所示 image.png

  • hasOwnProperty hasOwnProperty方法可以检查对象是否真正‘自己拥有’某属性或者方法
    xiaoming.hasOwnProperty('name'); //true
    xiaoming.hasOwnProperty('nationality'); //false

  • in in运算符只能检查某个属性或方法是否可以被对象访问,不能检查是否是自己的属性或方法
    'name' in xiaoming //true
    'nationality' in xiaoming //true

在prototype上添加方法

把方法直接添加到实例上的缺点:每个实例和每个实例方法函数都是内存中不同的函数,造成了内存的浪费
解决方法:将方法写在prototype上

image.png

原型链的终点

image.png

console.log(xiaoming.__proto__.__proto__ === Object.prototype);     // true
console.log(Object.prototype.__proto__);                            // null

console.log(Object.prototype.hasOwnProperty('hasOwnProperty'));     // true
console.log(Object.prototype.hasOwnProperty('toString'));           // true
  • 关于数组的原型链 image.png

继承

  • 如何实现继承
  1. 继承的关键:子类必须拥有父类全部的属性和方法,子类还需有属于自己特有的属性和方法
  2. 使用JavaScript特有的原型链特性来实现继承,是普遍的做法
  3. ES6中也有关于继承的方法
  • 通过原型链实现继承

image.png 关键语句,实现继承Student.prototype = new People

  function People(name, age, sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        }
        People.prototype.sayHello = function () {
            console.log('你好,我是' + this.name + '我今年' + this.age + '岁了');
        };
        People.prototype.sleep = function () {
            console.log(this.name + '开始睡觉,zzzzz');
        };

        // 子类,学生类
        function Student(name, age, sex, scholl, studentNumber) {
            this.name = name;
            this.age = age;
            this.sex = sex;
            this.scholl = scholl;
            this.studentNumber = studentNumber;
        }
        // 关键语句,实现继承
        Student.prototype = new People();

        Student.prototype.study = function () {
            console.log(this.name + '正在学习');
        }
        Student.prototype.exam = function () {
            console.log(this.name + '正在考试,加油!');
        }
        // 重写、复写(override)父类的sayHello
        Student.prototype.sayHello = function () {
            console.log('敬礼!我是' + this.name + '我今年' + this.age + '岁了');
        }

        // 实例化
        var hanmeimei = new Student('韩梅梅', 9, '女', '慕课小学', 100556);

        hanmeimei.study();
        hanmeimei.sayHello();
        hanmeimei.sleep();

        var laozhang = new People('老张', 66, '男');
        laozhang.sayHello();
  • 缺点
  1. 在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱
  2. 在创建子类型的时候不能向超类型传递参数

还有其他继承方法,在稍后的学习中进行补充。。。

上升到面向对象

  • 实例(红绿灯案例) 做100个红绿灯,点击红灯变黄,黄灯变绿,绿灯变红

组件化思维,最重要的是创建类

  • TrafficLight类 属性:自己的当前颜色color、自己的DOM元素dom
    方法:初始化init()、切换颜色changeColor()、绑定事件bindEvent()
<div id="box"></div>
    <script>
        // 定义红绿灯类
        function TrafficLight() {
            // 颜色属性,一开始都是红色
            // 红色1、黄色2、绿色3
            this.color = 1;
            // 调用自己的初始化方法
            this.init();
            // 绑定监听
            this.bindEvent();
        }
        // 初始化方法
        TrafficLight.prototype.init = function() {
            // 创建自己的DOM
            this.dom = document.createElement('img');
            // 设置src属性
            this.dom.src = 'images/' + this.color + '.jpg';
            //孤儿节点上树
            box.appendChild(this.dom);
        };
        // 绑定监听
        TrafficLight.prototype.bindEvent = function() {
            // 备份上下文,这里的this指的是JS的实例
            var self = this;
            // 当自己的dom被点击的时候
            this.dom.onclick = function () {
                // 当被点击的时候,调用自己的changeColor方法
                self.changeColor();
            };
        }
        // 改变颜色
        TrafficLight.prototype.changeColor = function () {
            // 改变自己的color属性,从而有一种“自治”的感觉,自己管理自己,不干扰别的红绿灯
            this.color ++;
            if (this.color == 4) {
                this.color = 1;
            }
            // 光color属性变化没有用,还要更改自己的dom的src属性
            this.dom.src = 'images/' + this.color + '.jpg';
        };
        // 得到盒子
        var box = document.getElementById('box');
        // 实例化100个
        var count = 100;
        while(count--){
            new TrafficLight();
        }

内置对象

包装类

  • 什么是包装类
  1. Number()、String()、Boolean()分别是数字、字符串、布尔值的包装类
  2. 包装类的目的就是为了让基本类型值可以从它们的构造函数的prototype上获得方法
var a = new Number(123);
var b = new String('慕课网');  
var c = new Boolean(true);
console.log(typeof a);      // object
console.log(5 + a);         // 128
console.log(b.slice(0, 2)); // '慕课'       
var d = 123;
console.log(d.__proto__ == Number.prototype);       // true
 var e = '慕课网';
console.log(e.__proto__ == String.prototype);       // true
console.log(String.prototype.hasOwnProperty('toLowerCase'));    // true
  1. Number()、String()和Boolean()的实例都是object类型,它们的PrimitiveValue属性存储它们的本身值
  2. new出来的基本类型值可以正常参与运算

Math对象

  • Math.pow(); 开方 Math.sqrt()
  • 向上取整 Math.ceil() 向下取整 Math.floor()
  • 四舍五入 Math.round() 四舍五入到小数点后某位 先乘再取整然后除
  • 取最大最小值 Math.max()和Math.min() 如何利用Math.max()求数组最大值
  1. Math.max()要求参数必须是“罗列出来”,而不能是数组
  2. 用apply方法:它可以指定函数的上下文,并且以数组的形式传入“零散值”当做函数的参数
var arr = [3692];
var max = Math.max.apply(nu11,arr);//第一个参数:设置上下文
console.log(max);
var min = Math.min(2,11,5,'a');
console.log(min);//NaN
  • 随机数Math.random()
  1. 得到0-1的小数
  2. 得到[a,b]区间的整数
    parseInt(Math.random()*(b-a+1)+a)

Date()日期对象

  1. 使用new Date()即可得到当前时间的日期对象,它是object类型值
  2. 使用new Date(2020,11,1)即可得到指定日期的日期对象,注意第二个参数表示月份,从e开始算,11表示12月
  3. 也可以是new Date( '2020-12-01')这样的写法
方法功能
getDate()得到日期1~31
getDay()得到星期0~6
getMonth()得到月份0~11
getFullYear()得到年份
getHours()得到小时数0~23
getMinutes()得到分钟数0~59
getSeconds()得到秒数0~59
  • 时间戳
  1. 时间戳表示1970年1月1日零点整距离某时刻的毫秒数
  2. 通过getTime()方法或者Date.parse()函数可以将日期对象变为时间戳
  3. 通过new Date(时间戳)的写法,可以将时间戳变为日期对象