文章目录
- 常见JS特效
- 1、在页面的盒子中显示鼠标坐标
- 2、滚动条的距离
- 3、动画函数的封装
- 4、轮播图的实现
- 5、图片放大效果
- 6、点击Tab栏的切换
- 7、精灵图的使用
- 8、字体图标的使用
- <一>构造函数、原型对象、原型链、原型
- 1、如何理解原型和原型链
- 2、prototype的修改和重写
- 3、原型链的指向问题
- 4、原型链的终点
- 5、如何获取对象非原型链上的属性`hasOwnProperty`
- <二>、闭包、作用域链、执行上下文
- 1、闭包
- 2、函数执行顺序的问题
- <三>、JS基础
- 1、数组原生的方法
- 2、常见的位运算符有哪些
- 3、JS为什么进行变量提升,导致了什么问题
- 4、use strict 是什么意思?用了之后有什么区别
- 5、JS是单线程还是多线程,为什么?
- 6、JS如何实现多线程的
- 7、ajax和axios的区别和联系
- 7、JS常见的数据类型
- 8、数据类型的检测方式
- 9、判断数组的方式有哪些
- 10、commonjs模块和ES6 modules有哪些区别?
- <五>、函数的this/apply/call/bind
- 1、this的问题
- 2、call()和apply()的区别
- 3、call() apply()bind() 方法的实现
- <六>、如何在vue中引入自己的JS文件
- <七>、异步编程的问题
- 1、实现异步编程的方式
- 2、对promise的理解
- 3、promise解决的问题
- 4、settimeout setinterval requestAnimationFrame的区别
- 5、setTimeout、promise、async/await的区别
- 6、await等的啥
- 7、async和await的优势
- 8、async如何捕获异常
- <八>、JS的继承
- 1、实现继承的两种方式
- <九>、面向对象
- 1、创建对象的方式有哪些
- <十>、垃圾回收和内存泄露
- 1、垃圾回收
- 2、内存泄露
- 9、什么是 回调函数?
- <十一>、并发和并行
常见JS特效
1、在页面的盒子中显示鼠标坐标
思路:鼠标在盒子中的坐标等于鼠标在页面中的坐标(e.pageX
)减去盒子在页面中的坐标(div.offsetLeft
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 200px;
height: 200px;
background-color: pink;
margin: 100px auto;
}
</style>
</head>
<body>
<div class="div"></div>
<script>
var div = document.querySelector(".div")
div.addEventListener("mousemove", function(e) {
var x = e.pageX - this.offsetLeft
var y = e.pageY - this.offsetTop
div.innerHTML = "X的坐标是" + x + "Y的坐标是" + y
})
</script>
</body>
</html>
2、滚动条的距离
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 200px;
height: 200px;
background-color:pink;
overflow: auto;
}
</style>
</head>
<body>
<div>我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
</div>
<script>
var div = document.querySelector('div');
console.log(div.scrollHeight); //返回的是实际高度 包括盒子的高度加上隐藏内容的高度 不带单位 283
console.log(div.clientHeight); //返回的是 盒子高度 不带单位 200
div.addEventListener('scroll', function() {
console.log(div.scrollTop); //返回被卷上去的 高度 滚到最下边的时候就是83px,所以上边的scrollHeigh是200+83等于283px,不带单位。
})
</script>
</body>
</html>
3、动画函数的封装
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
position: relative;
width: 200px;
height: 200px;
background-color:pink;
}
span {
position: relative;
display: block;
width: 100px;
height: 100px;
background-color: red;
}
</style>
</head>
<body>
<div></div>
<span>夏雨荷</span>
<script>
//为啥刚开始不动呢 因为忘记加定位了
//原理:首先获得该盒子的当前位置 然后在此基础上不断加1(利用定时器完成这个操作) 再给定一个结束定时的条件(清除定时器)
var div = document.querySelector('div');//这个得是 全局变量 下边才可以调用
var span = document.querySelector('span');
function animate(obj, target) {
var timer = setInterval(function() {
if ( obj.offsetLeft >= target) {
//到了设定值之后,清除定时器,盒子最终就停在offsetLeft=400px的距离上。
clearInterval(timer);
}
//盒子的左侧距离页面的距离不断加 5px。
obj.style.left = obj.offsetLeft + 5 + 'px';
}, 30)
}
animate(div, 400);
animate(span,300);
</script>
</body>
</html>
4、轮播图的实现
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>品优购-综合网购首选-正品低价、品质保障、配送及时、轻松购物!</title>
<meta name="description" content="品优购" />
<meta name="Keywords" content="品优购" />
<style>
/*清除元素默认的内外边距 */
* {
margin: 0;
padding: 0
}
/*让所有斜体 不倾斜*/
em,
i {
font-style: normal;
}
/*去掉列表前面的小点*/
li {
list-style: none;
}
/*图片没有边框 去掉图片底侧的空白缝隙*/
img {
border: 0; /*ie6*/
vertical-align: middle;
}
.main {
width: 980px;
height: 455px;
margin-left: 219px;
margin-top: 10px;
}
.focus {
position: relative;
width: 721px;
height: 455px;
background-color: purple;
overflow: hidden;
}
.focus ul {
/* 为了使用动画函数 */
position: absolute;
top: 0;
left: 0;
/* 这是为了放有四张图片的ul能放得下 所以设置为父盒子宽度额600%即六倍 */
width: 600%;
}
.focus ul li {
/* 为了让li在一行显示 */
float: left;
}
.arrow-l,
.arrow-r {
display: none;
position: absolute;
top: 50%;
margin-top: -20px;
width: 24px;
height: 40px;
background: rgba(0, 0, 0, .3);
text-align: center;
line-height: 40px;
color: #fff;
font-family: 'icomoon';
font-size: 18px;
z-index: 2;
}
.arrow-r {
right: 0;
}
.circle {
position: absolute;
bottom: 10px;
left: 50px;
}
.circle li {
float: left;
width: 8px;
height: 8px;
/*background-color: #fff;*/
border: 2px solid rgba(255, 255, 255, 0.5);
margin: 0 3px;
border-radius: 50%;
/*鼠标经过显示小手*/
cursor: pointer;
}
.current {
background-color: #fff;
}
</style>
</head>
<body>
<!-- main 模块 -->
<div class="w">
<div class="main">
<div class="focus fl">
<!-- 左侧按钮 -->
<a href="javascript:;" class="arrow-l">
<
</a>
<!-- 右侧按钮 -->
<a href="javascript:;" class="arrow-r"> > </a>
<!-- 核心的滚动区域 -->
<ul>
<li>
<a href="#"><img src="upload/focus.jpg" alt=""></a>
</li>
<li>
<a href="#"><img src="upload/focus1.jpg" alt=""></a>
</li>
<li>
<a href="#"><img src="upload/focus2.jpg" alt=""></a>
</li>
<li>
<a href="#"><img src="upload/focus3.jpg" alt=""></a>
</li>
</ul>
<!-- 小圆圈 -->
<ol class="circle"></ol>
</div>
</div>
</div>
<script>
function animate(obj, target, callback) {
// console.log(callback); callback = function() {} 调用的时候 callback()
// 先清除以前的定时器,只保留当前的一个定时器执行
clearInterval(obj.timer);
obj.timer = setInterval(function() {
// 步长值写到定时器的里面
// 把我们步长值改为整数 不要出现小数的问题
// var step = Math.ceil((target - obj.offsetLeft) / 10);
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (obj.offsetLeft == target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
// 回调函数写到定时器结束里面
// if (callback) {
// // 调用函数
// callback();
// }
callback && callback();
}
// 把每次加1 这个步长值改为一个慢慢变小的值 步长公式:(目标值 - 现在的位置) / 10
obj.style.left = obj.offsetLeft + step + 'px';
}, 15);
}
window.addEventListener('load', function() {
// 1. 获取元素
var arrow_l = document.querySelector('.arrow-l');
var arrow_r = document.querySelector('.arrow-r');
var focus = document.querySelector('.focus');
var focusWidth = focus.offsetWidth;
// 2. 鼠标经过focus 就显示隐藏左右按钮 并且清除图片自动播放效果
focus.addEventListener('mouseenter', function() {
arrow_l.style.display = 'block';
arrow_r.style.display = 'block';
clearInterval(timer);
timer = null; // 清除定时器变量 释放空间
});
focus.addEventListener('mouseleave', function() {
arrow_l.style.display = 'none';
arrow_r.style.display = 'none';
timer = setInterval(function() { //鼠标离开时 开启自动播放轮播图功能
//手动调用点击事件
arrow_r.click();
}, 2000);
});
// 3. 动态生成小圆圈 有几张图片,我就生成几个小圆圈
var ul = focus.querySelector('ul');
var ol = focus.querySelector('.circle');
// console.log(ul.children.length);
for (var i = 0; i < ul.children.length; i++) {
// 创建一个小li
var li = document.createElement('li');
// 记录当前小圆圈的索引号 通过自定义属性来做
li.setAttribute('index', i);
// 把小li插入到ol 里面
ol.appendChild(li);
// 4. 小圆圈的排他思想 我们可以直接在生成小圆圈的同时直接绑定点击事件
li.addEventListener('click', function() {
// 干掉所有人 把所有的小li 清除 current 类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下我自己 当前的小li 设置current 类名
this.className = 'current';
// 5. 点击小圆圈,移动图片 当然移动的是 ul 因为li是浮动的 所以不能移动li
// ul 的移动距离 小圆圈的索引号 乘以 图片的宽度 注意是负值
// 当我们点击了某个小li 就拿到当前小li 的索引号
var index = this.getAttribute('index');
// 当我们点击了某个小li 就要把这个li 的索引号给 num 控制li
num = index;
// 当我们点击了某个小li 就要把这个li 的索引号给 circle 控制小圆圈
circle = index;
// num = circle = index;
//var focusWidth = focus.offsetWidth; 上边已经获取了
console.log(focusWidth);
console.log(index);
animate(ul, -index * focusWidth);
})
}
// 把ol里面的第一个小li设置类名为 current
ol.children[0].className = 'current';
// 6. 克隆第一张图片(li)放到ul 最后面 为了解决小圆圈会多一个的问题
var first = ul.children[0].cloneNode(true);
ul.appendChild(first);
// 7. 点击右侧按钮, 图片滚动一张
var num = 0;
// circle 控制小圆圈的播放
var circle = 0;
// flag 节流阀 防止 按钮点击过快图片切换不过来的问题
var flag = true; //先打开节流阀
arrow_r.addEventListener('click', function() {
if (flag) {
flag = false; // 然后在程序已执行的时候就紧接着关闭节流阀
// 如果走到了最后复制的一张图片ul.children.length - 1,此时 我们的ul 要快速复原 left 改为 0
if (num == ul.children.length - 1) {
ul.style.left = 0;
num = 0;
}
num++;
animate(ul, -num * focusWidth, function() {
flag = true; // 在动画调用完毕之后才在打开节流阀
});
// 8. 点击右侧按钮,小圆圈跟随一起变化 可以再声明一个变量控制小圆圈的播放
circle++;
// 如果circle == 4 说明走到最后我们克隆的这张图片了 我们就复原
if (circle == ol.children.length) {
circle = 0;
}
// 调用函数
circleChange();
}
});
// 9. 左侧按钮做法
arrow_l.addEventListener('click', function() {
if (flag) {
flag = false;
if (num == 0) {
num = ul.children.length - 1;
ul.style.left = -num * focusWidth + 'px';
}
num--;
animate(ul, -num * focusWidth, function() {
flag = true;
});
// 点击左侧按钮,小圆圈跟随一起变化 可以再声明一个变量控制小圆圈的播放
circle--;
// 如果circle < 0 说明第一张图片,则小圆圈要改为第4个小圆圈(3)
// if (circle < 0) {
// circle = ol.children.length - 1;
// }
circle = circle < 0 ? ol.children.length - 1 : circle;
// 调用函数
circleChange();
}
});
//因为左侧和右侧的功能是一样的 所以封装成了一个函数
function circleChange() {
// 先清除其余小圆圈的current类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 留下当前的小圆圈的current类名
ol.children[circle].className = 'current';
}
// 10. 自动播放轮播图
var timer = setInterval(function() {
//手动调用点击事件
arrow_r.click();
}, 2000);
})
</script>
</body>
</html>
5、图片放大效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>手机详情页!</title>
<meta name="description" content="为您提供愉悦的网上购物体验!" />
<meta name="Keywords" content="网上购物"/>
<style>
.de_container {
margin-top: 20px;
}
.preview_wrap {
width: 400px;
height: 590px;
}
.preview_img {
position: relative;
height: 398px;
border: 1px solid #ccc;
}
.mask {
display: none;
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 300px;
background: #FEDE4F;
opacity: .5;
border: 1px solid #ccc;
cursor: move;
}
.big {
display: none;
position: absolute;
left: 410px;
top: 0;
width: 500px;
height: 500px;
background-color: pink;
z-index: 999;
border: 1px solid #ccc;
overflow: hidden;
}
.big img {
position: absolute;
top: 0;
left: 0;
}
.remark {
position: absolute;
right: 10px;
top: 20px;
}
</style>
</head>
<body>
<div class="de_container w">
<!-- 产品介绍模块 -->
<div class="product_intro clearfix">
<!-- 预览区域 -->
<div class="preview_wrap fl">
<div class="preview_img">
<img src="upload/s3.png" alt="">
<div class="mask"></div>
<div class="big">
<img src="upload/big.jpg" alt="" class="bigImg">
</div>
</div>
</div>
</div>
</div>
<script>
window.addEventListener('load', function() {
var preview_img = document.querySelector('.preview_img');
var mask = document.querySelector('.mask');
var big = document.querySelector('.big');
// 1. 当我们鼠标经过 preview_img 就显示和隐藏 mask 遮挡层 和 big 大盒子
preview_img.addEventListener('mouseover', function() {
mask.style.display = 'block';
big.style.display = 'block';
})
preview_img.addEventListener('mouseout', function() {
mask.style.display = 'none';
big.style.display = 'none';
})
// 2. 鼠标移动的时候,让黄色的盒子跟着鼠标来走
preview_img.addEventListener('mousemove', function(e) {
// (1). 先计算出鼠标在盒子内的坐标
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
// console.log(x, y);
// (2) 减去盒子高度 300的一半 是 150 就是我们mask 的最终 left 和top值了
// (3) 我们mask 移动的距离
var maskX = x - mask.offsetWidth / 2; //保证鼠标在黄色遮罩层的 中间位置
var maskY = y - mask.offsetHeight / 2;
// (4) 限制黄色盒子的移动边框 不能超过preview_img.width 如果x 坐标小于了0 就让他停在0 的位置
// 遮挡层的最大移动距离
var maskMax = preview_img.offsetWidth - mask.offsetWidth; //照片的宽度 减去 黄色遮挡层的宽度
if (maskX <= 0) {
maskX = 0;
} else if (maskX >= maskMax) {
maskX = maskMax;
}
if (maskY <= 0) {
maskY = 0;
} else if (maskY >= maskMax) {
maskY = maskMax;
}
mask.style.left = maskX + 'px';
mask.style.top = maskY + 'px';
// 3. 大图片的移动距离 = 遮挡层移动距离 * 大图片最大移动距离 / 遮挡层的最大移动距离
// 大图
var bigIMg = document.querySelector('.bigImg');
// 大图片最大移动距离
var bigMax = bigIMg.offsetWidth - big.offsetWidth;
// 大图片的移动距离 X Y
var bigX = maskX * bigMax / maskMax;
var bigY = maskY * bigMax / maskMax;
bigIMg.style.left = -bigX + 'px'; //遮罩层与大图走的方向相反
bigIMg.style.top = -bigY + 'px';
})
})
</script>
</body>
</html>
6、点击Tab栏的切换
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.tab {
width: 580px;
}
li {
list-style: none;
}
.tab_list li {
float: left;
height: 39px;
line-height: 39px;
padding: 0 20px;
text-align: center;
cursor: pointer;
}
.tab_list .current {
background-color: red;
color: #fff;
}
/* 隐藏所有内容 */
.tab_con .item {
display: none;
}
/* 刚开始 让第一个li和内容显示出来 */
.tab_con .item:nth-child(1) {
display: block;
}
</style>
</head>
<body>
<div class="tab">
<div class="tab_list">
<ul>
<li class="current">商品介绍</li>
<li>规格与包装</li>
<li>售后保障</li>
<li>商品评价</li>
<li>手机社区</li>
</ul>
</div>
<div class="tab_con">
<div class="item">
规格介绍模块内容
</div>
<div class="item">
规格与包装模块内容
</div>
<div class="item">
售后保障模块内容
</div>
<div class="item">
商品评价模块内容
</div>
<div class="item">
手机社区模块内容
</div>
</div>
</div>
<script>
//先实现点击tab_list就会变红
var tab_list = document.querySelector('.tab_list');
var lis = tab_list.querySelectorAll('li');
var items = document.querySelectorAll('.item');
//console.log(tab_list);
//console.log(lis);
//console.log(items);
for (var i = 0; i < lis.length; i++) {
//要给每一个Li 添加一个索引号
lis[i].setAttribute('index',i); //注意此时i 是一个函数变量 不需要 加引号
lis[i].onclick = function() {
//利用排它思想 先干掉其他人 只留下自己
for(var i = 0; i < lis.length; i++) {
lis[i].className = '';
items[i].style.display = 'none';
}
//点击哪一个 哪一个就会变红
this.className = 'current';
// 下边的内容是跟着上边的 li 的变化而变化的 所以要取到当前li的索引号
var index = this.getAttribute('index');
//console.log(index);
//让content里边对应序号的内容显示出来 还是需要排他思想 但是把这段代码 放到了上边的排它代码里 简洁
/* for(var i = 0; i < items.length; i++) {
items[i].style.display = 'none';
} */
items[index].style.display = 'block';
}
}
</script>
</body>
</html>
7、精灵图的使用
目的:把好多小的图片整合到一张图片里,减少向服务器发送请求的次数,提高网页的加载速度。
使用步骤:
1、在Adobe Fireworks CS5这个软件中打开一张精灵图。并点击右侧的位图进行锁定。
2、点击左侧菜单栏的web的切片工具按钮到该精灵图的对应位置,这时候左下角会显示盒子的宽高以及需要的精灵图的移动距离。
3、精灵图位置的锁定采用background-position
。注意软件中显示的X Y 加负号才是真正的精灵图的位置。
代码如下
.box2 {
width: 28px;
height: 27px;
margin: 200px;
//images/sprites.png是整张精灵图,-154px -106px表示需要的精灵图距离整张图片左上角需要移动的距离。如果不写的话,默认插入的是精灵图的左上角位置的图片。
background: url(images/sprites.png) no-repeat -154px -106px;
}
优缺点:优点是可以减少HTTP的请求次数;缺点是不能用于需要经常替换的图片,放大或者是缩小会失真,并且一张图片可能会比较大。
8、字体图标的使用
目的:精灵图图片较大,并且不能更改颜色等,所以引入字体图标,本质是文字,可以随意更改文字颜色大小等,并且是轻量级的,一旦文字被加载出来了,字体图标也被渲染出来了,还有就是不存在兼容性的问题。常见的字体图标包括:定位图标 上下左右箭头等
使用:
1、登录阿里巴巴字体图标库 点击进入字体下载,之后选择自己想要的字体图标添加至购物车
2、将购物车中的字体图标添加至项目,然后点击font-class如果没有代码生成的话点击查看在线链接,就生成了代码,打开该代码,并且复制到响应HTML文档的style中
3、想要哪一个字体图标,就写哪一个类名,注意iconfont
类名是必须要写的。
<span class="iconfont icon-shouji1"></span>
<一>构造函数、原型对象、原型链、原型
1、如何理解原型和原型链
理解:ES5是通过构造函数来创建实例化对象的,每创建一个实例化对象就会开辟一个新的内存空间,里边包含构造函数的所有属性和方法,为了节省内存空间,把构造函数中共有的属性和方法放到一个新对象中,这个对象就叫做原型(对象)。构造函数通过__prototype__
这个属性来访问原型对象,实例化对象通过__proto__
来访问原型对象。当实例化对象有自己的同名的属性或者是方法时,先去调用自己的,没有的话,就去调用原型对象的属性或者是方法,若是原型对象还没有,则去查找原型对象的原型,一直查找下去,直至出现该属性或者是方法,这就构成了原型链。最高的原型对象是Object.prototype
,所以创建出来的实例化对象可以使用toString等方法。
2、prototype的修改和重写
//正常来讲的话 Star.prototype === p.__proto__即构造函数的原型对象等于实例化的对象原型
<script>
function Star(name, age) {
this.name = name
this.age = age
}
var p = new Star('shixue', 18)
console.log(Star.prototype === p.__proto__); //true
console.log(p.constructor.prototype === p.__proto__); //true 实例化对象的构造函数也指向Star
</script>
<script>
//修改原型对象的话,p.constructor不在指向Star了,而是指向Object了,
function Star(name, age) {
this.name = name
this.age = age
}
Star.prototype = {
sing: function() {}
}
var p = new Star('shixue', 18)
console.log(Star.prototype === p.__proto__); //true
console.log(p.constructor.prototype === p.__proto__); //false
</script>
<script>
//修改原型对象的话,shixue.constructor不在指向Star了,而是指向Object了,
function Star(name, age) {
this.name = name
this.age = age
}
Star.prototype = {
//在这里如果需要修改原型对象的话,就必须让构造函数重新指向Star
constructor:Star
sing: function() {}
}
var p = new Star('shixue', 18)
console.log(Star.prototype === p.__proto__); //true
console.log(p.constructor.prototype === p.__proto__); //true
</script>
3、原型链的指向问题
<script>
function Star(name, age) {
this.name = name
this.age = age
}
var p = new Star('shixue', 18)
console.log(p.__proto__); //Star.prototype
console.log(p.__proto__.constructor); //Star
console.log(Star.prototype.constructor); //Star
console.log(Star.prototype.__proto__); //Object.prototype
console.log(p.__proto__.__proto__); //Object.prototype
console.log(p.__proto__.constructor.prototype.__proto__); //Object.prototype
console.log(Star.prototype.constructor.prototype.__proto__); //Object.prototype
</script>
4、原型链的终点
Object
是构造函数,原型对象是Object.prototype
,下一级是原型对象的对象原型Object.prototype.__proto__
<script>
console.log(Object.prototype.__proto__); //null
</script>
5、如何获取对象非原型链上的属性hasOwnProperty
<script>
function Star(name, age) {
this.name = name
this.age = age
}
var shixue = new Star('shixue', 18)
function no(obj) {
var res = []
for(var key in obj) {
if(obj.hasOwnProperty(key)) {
res.push(key + ':' + obj[key])
}
}
return res
}
console.log(no(shixue));
</script>
结果:name: shixue, age:18
<二>、闭包、作用域链、执行上下文
1、闭包
定义:闭包是一个函数,是一个有权访问其他函数作用域的变量的函数,通常的创建方式就是在函数内部创建一个函数,这个函数有权访问当前函数。
作用:创建私有变量,实现块级作用域。
实例:
<script>
function A() {
var a = 1
function B() {
console.log(a);
}
B()
}
A() // 1
</script>
解析:B()就是一个闭包。
面试题
<script>
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
</script>
结果:输出 5 个 6,因为定时器是异步函数,var 是全局变量,先执行循环,这时候 i 变成了 6,所以得到 5 个 6。
现在用闭包来解决这个问题
<script>
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
</script>
结果:1 2 3 4 5 6 间隔一秒输出
解析:i = 1,执行立即执行函数,把 i 赋值给 j,这时候立即执行函数执行,里边的定时器函数执行,间隔一秒输出1;i = 1执行完毕之后,再去执行下一个循环。
2、函数执行顺序的问题
<script>
let a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
</script>
结果:
Inside first function
Inside second function
Again inside first function
解析:碰到函数定义跳过,当碰到函数调用的时候再去调用,执行该函数。
<三>、JS基础
1、数组原生的方法
1>、增删改 push pop unshift shift splice
2>、遍历 some every forEach map fliter reduce
3>、搜索 indexOf lastIndexOf find findIndex includes
4>、转换成字符串 toString join
5>、拼接数组 concat
2、常见的位运算符有哪些
位运算定义:计算机对二进制数进行的加减乘数运算叫做位运算。
1>、与运算 & 只有当两个数都为1的时候在为1
2>、或运算 | 只要有一个数 为1 结果就是1
3>、取反运算 ~ 0变1 1变0
4>、异或运算 ^ 相同为0,不同为1
5>、左移 <<
6>、右移 >>
7>、原码 反码 和补码
最高位是符号位,0是整数 1是负数
正数的原码 反码 补码是一样的;负数的原码是符号位加正常的二进制表示数,反码是除符号位按位取反,补码是在反码的基础上加1。
3、JS为什么进行变量提升,导致了什么问题
变量提升是什么:JS在拿到一个函数或者是变量的时候,会有两步操作,解析和执行。解析过程就是检查代码语法和预编译。首先创建一个全局上下文环境,声明变量和函数,变量设置为undefined,函数提前声明好可以使用。在执行函数之前也会创建一个函数执行上下文环境,和全局执行上下文环境一样,只不过多了 this arguments 和函数的参数。
为什么进行变量提升:一是为了提高性能,JS在执行之前进行代码检查和预编译,并且这个过程只要一次,这样做提升了性能,要不然的话,JS在每次执行之前都要进行函数的编译,这是没有必要的,因为变量和函数的代码不会改变。还有预编译的过程中声明了函数和变量,去除了函数注释和没有必要的空格,在函数执行的时候,预先为变量分配了栈空间,提高了代码的运行效率。二是提高代码的容错率、
变量提升的问题:
var tmp = new Date();
function fn(){
console.log(tmp);
if(false){
var tmp = 'hello world';
}
}
fn(); // undefined
等价于下边的代码’
var temp
temp = new Date()
function fn(){
var temp
console.log(temp) //undefined
if(false) {
temp = 'hello world'
}
}
fn()
<script>
var tmp = 'hello world';
for (var i = 0; i < tmp.length; i++) {
console.log(tmp[i]);
}
console.log(i); // 11
</script>
解析:因为有变量提升,所以 i 被当成全局变量。
4、use strict 是什么意思?用了之后有什么区别
use strict是在js文件开头写的,
js按照严格模式运行的作用:
减小了代码的不规范编写,及早查出错误;
提高了代码的安全性能;
提高了编译效率;
为之后的JS版本打下基础。
区别如下:
1>、变量
必须提前声明
<script>
'use strict'
x = 3
console.log(x); //x 未被定义
</script>
2>、对象
不能有重复的属性
<script>
'use strict'
var obj = {
name: 'shixue',
name: 'zhangjie'
}
console.log(obj.name); //Uncaught SyntaxError: Unexpected identifier
</script>
3>、函数
不能有重复的参数
<script>
'use strict'
function sum(num,num) {
return num + num
}
console.log(sum(10,20)); //Uncaught SyntaxError: Duplicate parameter name not allowed in this context
</script>
4>、if for语句中声明函数会报错
<script>
'use strict'
if(true) {
function sum(num,num) {
return num + num
}
console.log(sum(10,20)); //Uncaught SyntaxError: Duplicate parameter name not allowed in this context
}
</script>
5>、this不能指向全局
非严格模式
<script>
var a = (function(){ return this})();
console.log(a); //window
</script>
严格模式
<script>
'use strict'
var a = (function(){return this})();
console.log(a); //undefined
</script>
5、JS是单线程还是多线程,为什么?
JS是单线程的,这是由他的作用决定的,JS的主要作用就是实现和用户交互以及操作DOM元素,多线程的话会造成很多的问题。比如:一个线程要求改变该DOM节点的值,另一个线程要求删除该节点,那应该是执行哪一个呢,这就带来了很多的问题。
6、JS如何实现多线程的
通过创建一个new Worker(js文件)来实现为该js文件创建一个新的子线程,这样的话,子线程的执行就不会影响主线程的JS代码的执行了。
具体实现大概就是在子线程中创建一个Worker对象来执行js文件并且通过postMessage(data)来实现主线程与子线程之间的数据传输,通过worker.onmessage()方法来处理由主线程或者是子线程返回的数据data。
实例:实现计数的同时也会输出一个对话框
HTML代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text" name="ipt" id="ipt" value="" />
<button id="start">start</button>
<button id="stop">stop</button>
<button id="ale">alert</button>
<script type="text/javascript">
var ipt = document.getElementById("ipt");
var stop = document.getElementById("stop");
var start = document.getElementById("start");
var ale = document.getElementById("ale");
var worker = new Worker("./test1.js");
worker.onmessage = function(){
ipt.value = event.data;
};
stop.addEventListener("click",function(){
//用于关闭worker线程
worker.terminate();
});
start.addEventListener("click",function(){
//开起worker线程
worker = new Worker("./test1.js");
});
ale.addEventListener("click",function(){
alert("i'm a dialog");
});
</script>
</body>
</html>
test1.js的代码 如下
var i = 0;
function mainFunc(){
i++;
//把i发送到浏览器的js引擎线程里
postMessage(i);
}
var id = setInterval(mainFunc,1000);
7、ajax和axios的区别和联系
作用:均用于网络请求
ajax:不刷新网页的情况下更新部分网页内容的技术,它是利用XMLHttpRequest来实现的,当有多个网络请求的时候会出现回调地狱的问题,并且更多情况下是和Jquery配合使用,在一个项目中如果再要引入Jquery的话,增加代码量,所以放弃使用这种方法。
axios:感觉像是用Promise封装的ajax,解决了回调地狱的问题,并且在常见跨域请求中,只需要在vue项目的根目录下配置vue.config.js文件即可,ajax的话,还得在使用JSONP来解决跨域问题。并且它有以下特点,
1.从浏览器中创建 XMLHttpRequests
2.从 node.js 创建 http 请求
3.支持 Promise API
4.拦截请求和响应
5.转换请求数据和响应数据
6.取消请求
7.自动转换 JSON 数据
8.客户端支持防御 XSRF
他们的代码格式差不多
**axios**({
url: 'http://jsonplaceholder.typicode.com/users',
method: 'get',
responseType: 'json', // 默认的
data: {
//'a': 1,
//'b': 2,
}
}).then(function (response) {
console.log(response);
console.log(response.data);
}).catch(function (error) {
console.log(error);
})
$.**ajax**({
url: 'http://jsonplaceholder.typicode.com/users',
type: 'get',
dataType: 'json',
data: {
//'a': 1,
//'b': 2,
},
success: function (response) {
console.log(response);
}
})
axios的拦截器
请求拦截成功后做的事情:比如要求用户登录
响应拦截成功后做的事情:比如对数据进行过滤
响应拦截失败做的事情:比如当响应出错的时候跳入另一个页面。
7、JS常见的数据类型
原始数据类型:undefined null boolean number string,存放在栈中,占据内存空间小。
引用数据类型:对象 数组 和 函数。
存储位置不同:原始数据类型存储在栈中,占据空间小,大小固定,属于被频繁使用的数据。引用数据类型存放在堆中,占据空间大,大小不固定,引用数据类型在栈中存放了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值的时候,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
在操作系统中,内存被分为栈区和堆区,栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆内存一般由开发者分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。
8、数据类型的检测方式
1、typeof
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof 'str'); // string
console.log(typeof []); // object
console.log(typeof function(){}); // function
console.log(typeof {}); // object
console.log(typeof undefined); // undefined
console.log(typeof null); // object
2、instanceOf
作用:用于判断一个对象在其原型链中有没有构造函数的prototype属性,只能判断引用数据类型。
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
3、constructor
两个作用:一是判断数据类型,二是访问该实例化对象的构造函数。
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
4、Object.prototype.toString
var a = Object.prototype.toString;
console.log(a.call(2));
console.log(a.call(true));
console.log(a.call('str'));
console.log(a.call([]));
console.log(a.call(function(){}));
console.log(a.call({}));
console.log(a.call(undefined));
console.log(a.call(null));
//得到以下结果
[object Number]
[object Boolean]
[object String]
[object Array]
[object Function]
[object Object]
[object Undefined]
[object Null]
9、判断数组的方式有哪些
1、通过原型链去判断
obj.__proto__ === Array.prototype;
2、isArrray方法
Array.isArrray(obj);
3、instanceof方法
obj instanceof Array
4、isPrototypeOf方法
Array.prototype.isPrototypeOf(obj)
5、Object.prototype.toString方法
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
10、commonjs模块和ES6 modules有哪些区别?
ES6模块是 动态只读引入 通过import导入模块,动态的意思是模块的原始值发生变化,引入的该模块的变量也会发生改变,只读的意思是不能修改引入变量的指针指向,对于基本数据类型不能更改变量值,对于引用数据类型可以给对象添加属性和方法,因为指针指向的是栈中存放的实体的起始地址,实体存放在堆中,所以可以更改对象的属性和方法。
commonjs模块是通过require引入的,属于浅拷贝可以更改变量的的指针指向,当同一个模块重复引用的时候只会执行第一个模块,剩余的用缓存中的。
<五>、函数的this/apply/call/bind
1、this的问题
1、普通函数 计时器函数 立即执行函数的this指向 window
2、构造函数的this 指向 实例化对象
3、事件绑定函数指向 绑定事件的对象
改变函数内部this指向
1、call
Father.call(this指向,参数1,参数2,参数3等等)
作用:一是调用Father函数,一是改变this指向
应用场景:继承
<script>
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
function Son(uname, age) {
//call的作用 一是调用Father函数 二是改变this指向
Father.call(this, uname, age);
}
var son = new Son('刘德华', 18);
console.log(son);
</script>
2、apply
Father.apply(this指向,[参数构成的数组])
作用:一是改变this指向,一是调用Father函数
应用场景:求数组元素的操作
<script>
function a() {
console.log('hello');
}
function o(arr) {
console.log(this);
console.log(arr);
}
//apply 一是调用o函数 二是改变this指向
o.apply(a, ['pink']); //function a() {log('hello')} pink
//应用场景 操作数组元素
var arr = [1,22,44,21,44,23,55];
var max = Math.max.apply(Math, arr);
var min = Math.min.apply(Math, arr)
console.log(max, min); //55 1
```
**3、bind**
Father.bind(this指向,参数1,参数2,参数3等等)
作用:不会立即调用Father,但会立即改变this指向
应用场景:用于调用特定的函数值的时候
```javascript
var a = 1
var module = {
a: 2,
get: function() {
console.log(this.a);
}
}
module.get() //方法函数的this指向调用该方法的对象
var get1 = module.get //这时候 get1 是一个函数
get1() // 1 在外部调用函数 函数的this指向 window
//现在要想在外部调用的时候也把this绑定到 module中就 需要用bind
var get2 = module.get.bind(module)
get2() // 2
2、call()和apply()的区别
call()方法传入的参数是一个一个的参数
apply()方法传入的参数是一个数组
3、call() apply()bind() 方法的实现
1>、call的实现
<script>
Function.prototype.myCall = function(context) {
//1、先判断调用该方法的是不是一个函数
if(typeof this !== 'function') {
console.error('type error');
}
//2、判断上下文对象是否存在
context = context || window
//3、处理传入的参数
let args = [...arguments].sloce(1)
result = null
//4、将函数作为该对象的方法
context.fn = this
//5、调用函数
result = context.fn(...args)
//6、删除定义的方法
delete context.fn
//返回结果
return result
}
</script>
2>、apply的实现
<script>
Function.prototype.myApply = function(context) {
//1、先判断调用该方法的是不是一个函数
if(typeof this !== 'function') {
console.error('type error');
}
//2、判断上下文对象是否存在
context = context || window
//3、处理传入的参数
let result = null
//4、将函数作为该对象的方法
context.fn = this
//5、调用函数
if(arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
//6、删除定义的方法
delete context.fn
//返回结果
return result
}
</script>
3>、bind()的实现
<script>
Function.prototype.myBind = function(context) {
//1、判断调用该方法的是一个函数
if(typeof this !== 'function') {
console.error('type error');
}
//2、判断是不是传入上下文对象
context = context || window
//3、处理参数,并把当前调用者保存下来
let args = [...arguments].slice(1)
fn = this
//4、用一个函数返回结果
return function Fn() {
//5、调用apply把绑定函数的调用,需要判断函数为构造函数的情况,这时候需要把当前函数的this传给apply调用,不是的话就用上下文指定的对象
return fn.apply(
this.instanceOF = Fn ? this : context,
args.concat(...arguments)
)
}
}
</script>
<六>、如何在vue中引入自己的JS文件
1、在对应的vue文件的template中写入对应的div
2、在对应的vue文件的style中写入相应地样式
3、创建对应的js文件,并且以函数形式封装,最后导出这个模块
function fangda() {}
export {fangda}
4、在对应的vue文件的script中导入该模块
import {fangda} from 'js文件的正确路径'
<七>、异步编程的问题
1、实现异步编程的方式
1、回调函数,多个回调函数的使用会造成huidiao地狱的问题
2、promise函数,解决了回调地狱的问题,但是存在的多个then的调用可能导致调用不明确的存在。
3、async函数,内部自带执行器,当执行到await语句的时候,如果返回的是一个Promise对象,则等到promise对象的状态变成resolved的时候在继续往下执行。
console.log('script start')
//promise对象一旦构建就立即执行里边的代码
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () { //then是微任务,得等到当前轮宏任务结束之后再去调用
console.log('promise2')
})
//这属于 宏任务得等到当前轮的微任务结束之后才会调用
setTimeout(function(){
console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout
async function async1(){
console.log('async1 start');
await async2();
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start');
async1(); //这里是因为函数不调用就不执行
console.log('script end')
// 输出顺序:script start->async1 start->async2->script end->async1 end
function testAsy(x){
return new Promise(resolve=>{setTimeout(() => {
resolve(x);
}, 3000)
}
)
}
async function testAwt(){
let result = await testAsy('hello world'); //awit会阻塞该函数后边的执行,如果有定时器函数的话,先去执行下边的函数,等到定时器结束在执行await函数
console.log(result); // 3秒钟之后出现hello world
console.log('cuger') // 3秒钟之后出现cuger
}
testAwt();
console.log('cug') //立即输出cug
2、对promise的理解
promise是异步编程的解决方案,接受一个函数作为参数,该函数有resolve和reject两个形参,返回一个promise实例。一个promise有三种状态,分别是pendding resolved rejected,并且状态一旦改变就无法更改。
状态的改变是通过resolve和reject函数来实现的,可以在异步操作结束后调用这两个函数改变promise实例的状态,他的原型上有一个then方法,使用这个then方法可以为两个状态的改变注册回调函数,这个回调函数属于微任务,会在本轮事件循环的末尾执行。
在构造promise的时候,构造函数内部的代码是立即执行的。
常见方法
1、then当成功时调用的方法,指向上边的儿resolve。
2、catch,当失败的时候调用的方法,指向上边的reject。
3、all(),当所有的promise都执行完之后再去执行的操作。适用于发生对个请求并且按照请求顺序去执行的时候用all()方法。
4、finally(),没有形参,不管最后的状态如何都会去调用的方法,适合放一些成功还是失败都要放的东西。
5、race()不管里边有几个promise,只要有一个完成,就改变整个promise的状态。用于需要做一件事情,但是超过一定时间之后就不去做了的情况。
3、promise解决的问题
在实际应用中,比如发送一个ajax请求A得到数据,并且想把该数据再给请求B,这样的话,就得在请求A成功之后在进行下一步,造成了回调地狱的问题,
-let fs = require('fs')
fs.readFile('./a.txt','utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
fs.readFile(data,'utf8',function(err,data){
console.log(data)
})
})
})
采用promise可以避免这种问题。
let fs = require('fs')
function read(url){
return new Promise((resolve,reject)=>{
fs.readFile(url,'utf8',function(error,data){
error && reject(error)
resolve(data)
})
})
}
read('./a.txt').then(data=>{
return read(data)
}).then(data=>{
return read(data)
}).then(data=>{
console.log(data)
})
4、settimeout setinterval requestAnimationFrame的区别
settimeout()是多久之后再去调用该函数
setinterval()是间隔多久就调用一次该函数
requestAnimationFrame()自带节流功能,基本可以保证在16.6ms之内只执行一次。
5、setTimeout、promise、async/await的区别
1、setTimeout是过多久之后 执行这个函数,即使是立刻执行的也会先加入宏任务队列,等当前任务执行完毕之后再去执行。
console.log('script start') //1. 打印 script start
setTimeout(function(){
console.log('settimeout') // 4. 打印 settimeout
}) // 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数
console.log('script end') //3. 打印 script start
// 输出顺序:script start->script end->settimeout
2、promise
console.log('script start')
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () {
console.log('promise2')
})
setTimeout(function(){
console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout
执行顺序:当执行到promise对象的时候,里边的代码是同步立即执行的。then方法中是一个任务,当promise的状态变成resolved/rejected的时候,放入当前循环事件的微任务队列。setTimeout也是一个任务,放入当前循环事件的宏任务队列。
3、async/await
async function async1(){
console.log('async1 start');
await async2();
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
// 输出顺序:script start->async1 start->async2->script end->async1 end
async是被放入当前循环的宏任务队列的,当调用的时候再去执行,碰到await的时候让出主线程,等当前循环执行完毕之后再去执行剩下的代码。
6、await等的啥
function getSomething() {
return "something";
}
async function testAsync() {
return Promise.resolve("hello async");
}
async function test() {
const v1 = await getSomething();
const v2 = await testAsync();
console.log(v1, v2);
}
test();
//something hello async
await表达式的运算结果取决于他等的啥,如果后边是一个非promise对象,则直接返回表达式的值,如果后边是一个promise对象,则他会阻塞后边的代码,等着promise对象resolve的值作为await表达式的运算结果。
function testAsy(x){
return new Promise(resolve=>{setTimeout(() => {
resolve(x);
}, 3000)
}
)
}
async function testAwt(){
let result = await testAsy('hello world');
console.log(result); // 3秒钟之后出现hello world 他得等到await执行完毕才会接着往下执行
console.log('cuger') // 3秒钟之后出现cug
}
testAwt();
console.log('cug') //立即输出cug
这就是 await 必须用在 async中的原因,async并不会造成阻塞,他的阻塞都是被放在await中异步执行的,等待返回一个promise对象。
7、async和await的优势
单一的promise链并不能体现async、await的优势,但是,如果需要处理多个promise组成的then链的时候,优势就可以体现出来了,很有意思的是promise用then来解决多层回调地狱的问题,现在又用async、await来优化。
现在假设有一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。仍然用setTimeout来完成。
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
现在用promise来实现
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
现在用async来实现,虽然结果和promise一样,但是代码简洁了很多。
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
综上:async的优势在于让代码简洁了很多,读起来更加同步。
8、async如何捕获异常
async function fn(){
try{
let a = await Promise.reject('error')
}catch(error){
console.log(error)
}
}
fn()
<八>、JS的继承
1、实现继承的两种方式
1、原型链继承
核心:将父亲的实例作为孩子的原型。
缺点:子类无法向父类传参数,并且子类实例的对象原型的属性是共享的,一旦修改了一个,别的子类实例也会改变。
实现:这时候创建的子类实例的对象原型就是Father,在调用方法 和属性的时候先调用子类的,在子类中找不到的属性和方法会沿着原型链一直往上找。
//首先给一个父类
//首先给一个父类
function Father(name){//给构造函数添加了参数
this.name=name;
this.sex="male";
this.say=function(){
console.log(this.name);
}
}
Father.prototype = {
age: 18,
sing() {
console.log('sing');
}
};//给构造函数添加了原型属性
//然后创建一个子类
function Son(name) {
this.name = name
}
//子类继承父类的属性和方法:通过原型对象继承,核心就是子类的原型对象指向父类的实例化对象
Son.prototype = new Father('shixue')
var son1 = new Son('shikai')
console.log(son1);
console.log(son1.sex);
console.log(son1.say);
son1.say()
console.log(son1.sing);
son1.sing()
2、借用构造函数继承
核心:在子类中通过call来调用父类,并向父类传递参数。
缺点:父类原型对象中的属性和方法没办法使用
//首先给一个父类
function Father(name){
this.name=name;
this.sex="male";
this.say=function(){
console.log(this.name);
}
}
Father.prototype = {
age: 18,
sing() {
console.log('sing');
}
};
//然后创建一个子类
function Son() {
//通过 构造函数来继承父类,可以实现子类给父类传参
Father.call(this, 'shixue')
}
var son1 = new Son()
console.log(son1);
console.log(son1.sex);
console.log(son1.age); //undefined 因为这个属性是从父类的原型对象中的
console.log(son1.say);
son1.say()
console.log(son1.sing); //error 原理同上
son1.sing()
输出结果如下:
3、组合继承:使用原型继承和构造函数继承
核心:在子类的构造函数内部使用构造函来继承父类(实现参数的传递),子类的原型对象还指向父类的实例化对象,实现了可以继承父类的原型对象的属性和方法。执行顺序是先执行子类的构造函数,在执行原型对象,这个执行顺序主要用于查找子类的构造函数和原型中有共有的属性和方法的时候使用。
优缺点:既可以往父类中传参数,也可以实现父类原型对象中属性和方法的继承。
输出结果:
<九>、面向对象
1、创建对象的方式有哪些
1、字面量创建对象
优缺点:通俗易懂,但是如果每个对象中有相同的属性和方法的时候就会导致代码繁琐和重复。
使用场景:起始就确定对象的属性和方法的
var obj = {
name: 'shixue',
age: 18,
sing: function() {
console.log('sing');
}
}
console.log(obj);
console.log(obj.name); //得到属性值有两种方式,一个是这个 一个是下边这个
console.log(obj['name']);
obj.sing();
创建的 obj 对象如下:
2、利用 new Object创建对象
优缺点:本质上是和用字面量创建是一样的,都是代码简单易懂但是如果创建的多个对象有重复的属性和方法的时候会造成大段的代码重复。
使用场景:起始不确定对象的属性和方法的。
var obj = new Object()
obj.name = 'shixue'
obj.age = 18
obj.sing = function() {
console.log('sing');
}
console.log(obj);
console.log(obj.name);
console.log(obj['name']);
obj.sing();
输出的代码如下:
3、利用构造函数创建对象
优缺点:把对象中共有的属性和方法继承到一个构造函数中,创建新对象的时候new一下即可,节省了存储属性和方法的内存空间。
使用场景:适用于创建多个有共同属性名和方法的对象
function Person(name,age) {
this.name = name
this.age = age
this.sing = function() {
console.log('sing');
}
}
var shixue = new Person('shixue', 18)
console.log(shixue);
console.log(shixue.name);
console.log(shixue.age);
shixue.sing();
4、使用构造函数和原型创建对象
优缺点:把共有属性放在构造函数里,把方法放在原型对象中,缺点就是代码封装性不够好。
应用场景:适用于创建有共同属性和方法的多个对象
function Person(name,age) {
this.name = name
this.age = age
}
Person.prototype = {
sing: function() {
console.log('sing');
}
}
var shixue = new Person('shixue', 18)
console.log(shixue);
console.log(shixue.name);
console.log(shixue.age);
shixue.sing();
得到的输出如下:
5、ES6通过类来创建对象
优缺点:就是ES5中构造函数和原型对象创建的更新版。
使用场景:用于创建有多个共有属性和方法的对象。
class Person {
constructor(name, age) {
this.name = name //this指向实例化对象
this.age = age
}
sing() {
console.log('sing'); //方法中的this指向方法的调用者
}
}
var shixue = new Person('shixue', 18)
console.log(shixue);
console.log(shixue.name);
console.log(shixue.age);
shixue.sing();
输出代码如下:
<十>、垃圾回收和内存泄露
1、垃圾回收
1、垃圾回收的概念:当JS代码在运行的时候,操作系统为变量和值分配内存空间,当变量和值不在参与运行的时候操作系统就会回收分配的内存空间,这就是垃圾回收。
2、垃圾回收的机制:JS具有自动的垃圾回收机制,间隔一段时间就会对不使用的变量和对象进行定期的内存释放,原理就是找到不用的变量,然后回收它所占用的内存。JS变量有全局变量和局部变量两种,全局变量的周期是直至浏览器关闭页面,局部变量在该函数执行完毕就进行内存回收。值得注意的局部变量被外部函数使用,即闭包函数,当该函数执行完毕的时候,外部函数还会指向该局部变量,所以该变量不会被回收。
function assignHandler(){
var element = $('id');
var id = elment.id;
element.onclick = function(){
alert(id); //这个元素不会被回收,在往上找就是element不会被回收
};
element = null;
}
3、垃圾回收的方式
1、标记清理
当变量进入环境的时候标记 “进入环境”,进入环境的变量不能被回收,因为它正在使用,当变量离开环境的时候被标记为 “离开环境”,被标记为离开环境的变量就进行内存的回收。
2、引入计数
用计数器追踪每个值被引用的次数。当这个值被引用数据类型赋值,引用次数就加1,当引用该值的数据类型,相反,如果包含对这个值引用的变量又取得了另外一个值,引用次数减1。当引用次数变成0的时候,就在垃圾回收器下次运行的时候对该值进行回收。如下:
var a = {} //次数为0
var b = a //次数为0
//这时候在垃圾回收器运行的时候就把 a 进行回收
但是这种情况下可能会造成引用循环
function fun() {
let obj1 = {};
let obj2 = {};
obj1.a = obj2; // obj1 引用 obj2
obj2.a = obj1; // obj2 引用 obj1
}
这种情况下就要手动释放内存
obj1.a = null
obj2.a = null
如何减少垃圾回收:
1、数组:清空数组的时候,先给一个 [] 然后将其长度设置为 0
2、对象:对象最好是能够复用,清空的时候 {} = null
3、函数:对于循环内部可以复用的代码,尽量放在函数外部
2、内存泄露
1、意外的全局变量
当使用未声明的变量,导致引入了一个全局变量,无法被回收
2、忘记取消计时器setInterval
3、脱离DOM元素
创建了一个对DOM元素的引用,后来DOM元素删除了,但是引用一直无法被释放
4、不合理的闭包
不合理的闭包使用,导致变量无法被回收。因为闭包的作用域链中引用的变量不会被回收,即element不会被回收,所以当不用他的时候,要给他设置成 null
function assignHandler(){
var element = $('id');
var id = elment.id;
element.onclick = function(){
alert(id); //这个元素不会被回收,在往上找就是element不会被回收
};
element = null;
}
9、什么是 回调函数?
回调函数常用在数据请求中,用ajax请求完一段数据之后,再在这段数据之下进行接下来的数据请求。
ajax(url, () => {
// 处理逻辑
ajax(url1, () => {
// 处理逻辑
ajax(url2, () => {
// 处理逻辑
})
})
})
但是 函数的嵌套会存在耦合性问题,牵一发而动全身,不利于代码的维护。
<十一>、并发和并行
并发:宏观概念,现在有两个任务,在一段时间之内通过任务之间的切换完成了这两个任务,这种情况称之为并发。
并行:微观任务,假设CPU有两个核心,那么我就可以同时完成任务A和B,同时完成多个任务的情况就可以称之为并行。