DOM
- DOM是操控JS和HTML、CSS的桥梁
- 使用节点思维 DOM节点树
DOM简介
DOM(文档对象模型) 是js操作HTML文档的接口。
DOM最大的特点是将文档表示为节点树
nodeType常用属性值
| nodeType值 | 节点类型 |
|---|---|
| 1 | 元素节点,例如<p>和<div> |
| 3 | 文字节点 |
| 8 | 注释节点 |
| 9 | document节点 |
| 10 | DTD节点 |
认识document对象
- document对象是DOM中最重要的东西,几乎所有DOM的功能都封装在了document对象中
- document对象也表示整个文档,它是DOM节点数的根
- 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完全兼容|
节点的关系
| 父节点 | 考虑所有节点 | 只考虑元素节点 |
|---|---|---|
| 子节点 | childNodes | chilidren |
| 父节点 | parentNode | 同 |
| 第/最后一个子节点 | firstChild/lastChild | firstElementChild/lastElementChild |
| 前/后一个兄弟节点 | previousSibling/nextSilibing | previousElementSibling/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 | 鼠标指针相对于事件源元素的垂直坐标 |
事件对象(2)
e.charCode属性和e.keyCode属性
- e.charCode属性通常用于onkeypress事件中,表示用户输入的字符的“字符码”
- e.keyCode属性通常用于onkeydown事件和onkeyup中,表示用户按下的按键的“键码”
| charCode字符 | charCode字符码 |
|---|---|
| 数字0-9 | 48-57 |
| 大写字母A-Z | 65-90 |
| 小写字母a-z | 97-122 |
keyCode键码
事件对象(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';
}
}
- 批量事件监听的性能问题
- 每一个事件监听注册都会消耗一定的系统内存,而批量添加事件会导致监听数量太多,内存消耗会非常大
- 实际上,每个
<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>
- 动态绑定事件的问题
- 新增元素必须分别添加事件监听,不能自动获得事件监听
- 大量事件监听、大量事件处理函数都会产生大量消耗内存
故:出现了事件委托
利用事件冒泡机制,将后代元素委托给祖先元素
| 属性 | 属性描述 |
|---|---|
| target | 触发此事件的最早元素,即‘事件源元素’ |
| currentTarget | 事件处理程序附加到的元素 |
var oList = document.getElementById('list');
oList.onclick = function (e) {
// e.target表示用户真正点击的那个元素
e.target.style.color = 'red';
};
- 事件委托的使用场景
- 当有大量类似元素需要批量添加事件监听时,使用事件委托可以减少内存开销
- 当有动态元素节点上树时,使用事件委托可以让新上树的元素具有事件监听
- 使用事件委托注意事项
-
不能委托不冒泡的事件给祖先元素(onmouseenter不冒泡,onmouseover冒泡)
onmouseenter这个属性天生就是“不冒泡”的,相当于你事件处理函数附加给了哪个DOM节点,就是哪个DOM节点自己触发的事件,没有冒泡过程 -
最内层元素不能再有内层元素了
面试-写一个通用的事件侦听器函数
定时器和延时器
定时器
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继续执行其他语句,当异步完成后,会执行回调函数
使用定时器实现动画
利用的是视觉暂留的原理
- 使用定时器实现动画较为不便
- 不方便根据动画董事兼计算步长
- 运动方向要设置正负
- 多种运动进行叠加较为困难
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
- 无限滚动特效
- 轮播图特效
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')来绑定事件处理函数
- 已卷动高度
- window.scrollY属性表示在垂直方向已滚动的像素值
- 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>
- 楼层导航小效果
- DOM元素都有offsetTop属性,表示此元素到定位祖先元素的垂直距离
- 定位祖先元素:在祖先中,离自己最近的且拥有定位属性的元素
面向对象
认识对象
对象是键值对的集合,表示属性和值的映射关系
- 属性的访问 如果属性名以变量形式存储,则必须使用方括号形式
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){}
此时是浅克隆
对象的深浅克隆
- 对象是引用类型值,这意味着
- 不能用var obj2 = obj1这样的语法克隆一个对象
- 使用==或者===进行对象的比较时,比较的是他们是否为内存中的同一个对象,而不是比较值是否相同
- 对象的浅克隆
- 只克隆对象的表层,如果对象的某些属性值又是引用类型,则不进一步克隆他们,只是传递它们的引用
- 使用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就证明了数组是同一个对象
- 对象的深克隆
- 克隆对象的全貌,不论对象的属性值是否为引用类型值,都能将他们实现克隆
- 使用递归方法
- 面试经常考深克隆算法
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的区别
- call要用逗号罗列参数
- apply要把参数写在数组中
- 到底使用call还是apply
function fun1() {
fun2.apply(this, arguments);//这里用到了类数组,只能使用apply
}
function fun2(a,b) {
alert(a + b); //77,因为用了arguments,把a,b的值作为数组传入到了arguments中
}
fun1(33,44);
以上规则的总结
| 对象 | 函数 |
|---|---|
| 对象.函数() | 对象 |
| 函数() | window |
数组[下标]() | 数组 |
| IIFE | window |
| 定时器 | window |
| DOM事件处理函数 | 绑定DOM的元素 |
| call和apply | 任意指定 |
构造函数和类
用new操作符调用函数
new 函数()
- 四步走
- 函数体内自动创建一个空白对象
- 函数的上下文(this)会指向这个对象
- 函数体内的语句会执行
- 函数会自动返回上下文对象,即使函数没有return语句
构造函数
什么是构造函数
- 用new调用一个函数,这个函数就被称为“构造函数”,任何函数都可以是构造函数,只需要用new调用它
- 顾名思义,构造函数用来“构造新对象”,它内部的语句将为新对象添加若干属性和方法,完成对象的初始化
- 构造函数必须用new关键字调用,否则不能正常工作,正因如此,开发者约定构造函数命名时首字母要大写 构造函数中的this不是这个函数本身,而是创建的新对象
类与实例
类好比是蓝图,实例是具体的对象
JavaScript是基于对象语言
原型和原型链
prototype
- 任何函数都有prototype属性
- prototype属性值是个对象,它默认拥有constructor属性指回函数
- 构造函数的prototype属性是它的实例的原型
xiaoming.__proto__ === People.prototype //true
==和===都是true
原型链
实例可以打点访问它的原型的属性和方法,这被称为‘原型链查找’
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);//实例可以打点访问原型的属性和方法
如下图所示
-
hasOwnProperty hasOwnProperty方法可以检查对象是否真正‘自己拥有’某属性或者方法
xiaoming.hasOwnProperty('name'); //true
xiaoming.hasOwnProperty('nationality'); //false -
in in运算符只能检查某个属性或方法是否可以被对象访问,不能检查是否是自己的属性或方法
'name' in xiaoming //true
'nationality' in xiaoming //true
在prototype上添加方法
把方法直接添加到实例上的缺点:每个实例和每个实例方法函数都是内存中不同的函数,造成了内存的浪费
解决方法:将方法写在prototype上
原型链的终点
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
- 关于数组的原型链
继承
- 如何实现继承
- 继承的关键:子类必须拥有父类全部的属性和方法,子类还需有属于自己特有的属性和方法
- 使用JavaScript特有的原型链特性来实现继承,是普遍的做法
- ES6中也有关于继承的方法
- 通过原型链实现继承
关键语句,实现继承
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();
- 缺点
- 在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱
- 在创建子类型的时候不能向超类型传递参数
还有其他继承方法,在稍后的学习中进行补充。。。
上升到面向对象
- 实例(红绿灯案例) 做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();
}
内置对象
包装类
- 什么是包装类
- Number()、String()、Boolean()分别是数字、字符串、布尔值的包装类
- 包装类的目的就是为了让基本类型值可以从它们的构造函数的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
- Number()、String()和Boolean()的实例都是object类型,它们的PrimitiveValue属性存储它们的本身值
- new出来的基本类型值可以正常参与运算
Math对象
- 幂 Math.pow(); 开方 Math.sqrt()
- 向上取整 Math.ceil() 向下取整 Math.floor()
- 四舍五入 Math.round() 四舍五入到小数点后某位 先乘再取整然后除
- 取最大最小值 Math.max()和Math.min() 如何利用Math.max()求数组最大值
- Math.max()要求参数必须是“罗列出来”,而不能是数组
- 用apply方法:它可以指定函数的上下文,并且以数组的形式传入“零散值”当做函数的参数
var arr = [3,6,9,2];
var max = Math.max.apply(nu11,arr);//第一个参数:设置上下文
console.log(max);
var min = Math.min(2,11,5,'a');
console.log(min);//NaN
- 随机数Math.random()
- 得到0-1的小数
- 得到
[a,b]区间的整数
parseInt(Math.random()*(b-a+1)+a)
Date()日期对象
- 使用new Date()即可得到当前时间的日期对象,它是object类型值
- 使用new Date(2020,11,1)即可得到指定日期的日期对象,注意第二个参数表示月份,从e开始算,11表示12月
- 也可以是new Date( '2020-12-01')这样的写法
| 方法 | 功能 |
|---|---|
| getDate() | 得到日期1~31 |
| getDay() | 得到星期0~6 |
| getMonth() | 得到月份0~11 |
| getFullYear() | 得到年份 |
| getHours() | 得到小时数0~23 |
| getMinutes() | 得到分钟数0~59 |
| getSeconds() | 得到秒数0~59 |
- 时间戳
- 时间戳表示1970年1月1日零点整距离某时刻的毫秒数
- 通过getTime()方法或者Date.parse()函数可以将日期对象变为时间戳
- 通过new Date(时间戳)的写法,可以将时间戳变为日期对象