【42】mouseover和mouseenter的区别
-
mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件是mouseout
-
mouseenter:当鼠标移入元素本身(不包含子元素)会触发事件,也就是不会冒泡,对应的移除事件是mouseleave
只会在鼠标刚进入的时候触发,当进入其子元素的时候,是不会再触发的了
- 最重要的区别就是mouseover会冒泡而mouseenter不会
【43】懒加载和预加载
1、懒加载
-
什么是懒加载?
- 懒加载也就是延迟加载,指的是在长网页中延迟加载图像,是一种很好优化网页性能的方式 ,当访问一个页面的时候,先把img元素或是其他元素的背景图片路径替换成一张大小为1*1px图片的路径(这样就只需请求一次,俗称占位图),只有当图片出现在浏览器的可视区域内时,才设置图片正真的路径,让图片显示出来。这就是图片懒加载。
-
为什么要懒加载?
- 当很多页面,内容很丰富,页面很长,图片较多的时候。比如说各种商城页面。这些页面图片数量多,而且比较大,少说百来K,多则上兆。要是页面载入就一次性加载所有图片,等待时间很长,用户难免会心生抱怨,这就严重影响用户体验。
-
懒加载的原理
- 中的img元素,如果没有src属性,浏览器就不会发出请求去下载图片,只有通过javascript设置了图片路径,浏览器才会发送请求
- 载的原理就是先在页面中把所有的图片统一使用一张占位图进行占位,把真正的路径存在元素的“data-src”(这个名字起个自己认识好记的就行)属性里,要用的时候就取出来,再设置;
-
懒加载的优点
- 加载速度快、可以减轻服务器的压力、节约了流量,用户体验好
-
懒加载实现方式
-
法一:纯粹的延迟加载,使用setTimeOut或setInterval进行加载延迟.
-
法二:条件加载,符合某些条件,或触发了某些事件才开始异步下载。
-
法三:可视区加载,即仅加载用户可以看到的区域,这个主要由监控滚动条来实现,一般会在距用户看到某图片前一定距离遍开始加载,这样能保证用户拉下时正好能看到图片。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script> <title>Document</title> <style> .image-item { display: block; margin-bottom: 50px; /* 一定记得设置图片高度 */ height: 200px; } </style> </head> <body> <div class="wrap"> <img class="image-item" src="" data-src="images/1.jpg" alt="" /> <img class="image-item" src="" data-src="images/2.jpg" alt="" /> <img class="image-item" src="" data-src="images/3.jpg" alt="" /> <img class="image-item" src="" data-src="images/4.jpg" alt="" /> <img class="image-item" src="" data-src="images/5.jpg" alt="" /> <img class="image-item" src="" data-src="images/6.jpg" alt="" /> <img class="image-item" src="" data-src="images/7.jpg" alt="" /> <img class="image-item" src="" data-src="images/8.jpg" alt="" /> <img class="image-item" src="" data-src="images/9.jpg" alt="" /> <img class="image-item" src="" data-src="images/10.jpg" alt="" /> </div> <script> // 首先获得页面当中所有的图片 let img = document.getElementsByTagName("img"); //存储图片加裁到的位置,避免每次都从第一 张图片开始遍历 let n = 0; //页面载入完毕加载可视区域内的图片 lazyload(); //注册滚动条滚动事件 window.onscroll = lazyload; //监听页面滚动事件 function lazyload() { //可见区域高度 let seeHeight = document.documentElement.clientHeight; /*滚动条距离顶部高度 * Chrome: document.body.scrollTop * FF: document.documentElement.scrollTop **/ let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; for (let i = n; i < img.length; i++) { // 当前图片相对于页面顶部的距离 <= 可视区域+滚动条距离距离顶部的距离 if (img[i].offsetTop < seeHeight + scrollTop - 100) { //将img[i]的src设置 成img[i]的data-src if (img[i].getAttribute("src") == "") { img[i].src = img[i].getAttribute("data-src"); n = i + 1; } } } } </script> </body> </html> -
2、预加载
-
什么是预加载
- 提前加载图片,当用户需要查看时可直接从本地缓存中渲染
-
为什么需要预加载?
- 网页全部加载之前,对一些主要内容进行加载,以提供给用户更好的体验,减少等待的时间。否则,如果一个页面的内容过于庞大,没有使用预加载技术的页面就会长时间的展现为一片空白,直到所有内容加载完毕。图片预先加载到浏览器中,访问者便可顺利地在你的网站上冲浪,并享受到极快的加载速度。这对图片画廊及图片占据很大比例的网站来说十分有利,它保证了图片快速、无缝地发布,也可帮助用户在浏览你网站内容时获得更好的用户体验
-
预加载的优点
- 可以说是牺牲服务器前端性能,换取更好的用户体验,这样可以使用户的操作得到最快的反映。
-
预加载实现方式
- 法一:CSS方式实现预加载,隐藏在css的background的url属性里面
#preload-01 { background: url(http://domain.tld/image-01.png) no-repeat -9999px -9999px; } #preload-02 { background: url(http://domain.tld/image-02.png) no-repeat -9999px -9999px; } #preload-03 { background: url(http://domain.tld/image-03.png) no-repeat -9999px -9999px; }- 法二:JavaScript实现预加载,通过javascript的Image对象设置实例对象的src属性实现图片的预加载
function preloadImg(url) { var img = new Image(); img.src = url;//有了url就会去加载图片 if(img.complete) { //接下来可以使用图片了 //do something here } else { img.onload = function() { //接下来可以使用图片了 //do something here }; } }
3、懒加载和预加载的对比
两者都是提高页面性能有效的办法,两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力
理解:懒加载提高首次页面载入的速度,在实现方法三中,仍然使用预加载的思想,提前准备好数据;
【44】js的new做了什么
1、过程
- 创建了一个空的js对象(即{})
- 将空对象的原型prototype指向构造函数的原型
- 将空对象作为构造函数的上下文(改变this指向)
- 对构造函数有返回值的判断
2、怎么实现?
/*
create函数要接受不定量的参数,第一个参数是构造函数(也就是new操作符的目标函数),其余参数被构造函数使用。
new Create() 是一种js语法糖。我们可以用函数调用的方式模拟实现
*/
function create(Con,...args){
//1、创建一个空的对象
let obj = {}; // let obj = Object.create({});
//2、将空对象的原型prototype指向构造函数的原型
Object.setPrototypeOf(obj,Con.prototype); // obj.__proto__ = Con.prototype
//3、改变构造函数的上下文(this),并将剩余的参数传入,让obj执行该函数
let result = Con.apply(obj,args);
//4、在构造函数有返回值的情况进行判断
return result instanceof Object?result:obj;
}
3、构造函数返回值的判断
-
一般情况下构造函数没有返回值,但是作为函数,是可以有返回值的。那么在构造函数有返回值的情况下,new操作符做了什么?先看两个例子:
-
注意一下上面两个返回值的差异
function Person(name){ this.name = name; return 1; // return undefined/NaN/'string'/null } let me = new Person('快乐每一天'); console.log(me); // { name:'快乐每一天' }function Person(name){ this.name = name; return { age:12 }; } let me = new Person('快乐每一天'); console.log(me); // { age:12 }
4、结论:
-
在new的时候,会对构造函数的返回值做一些判断:
-
如果返回值是基础数据类型,则忽略返回值; 直接返回已经初始化好的obj
-
如果返回值是引用数据类型,则使用return 的返回,也就是new操作符无效; 相当于普通函数
-
【45】window.DOMContentLoaded和window.onload这两个事件的区别
- window.DOMContentLoaded前者是在DOM解析完毕之后触发,这时候DOM解析完毕,JavaScript可以获取到DOM引用,但是页面中的一些资源比如图片、视频等还没有加载完,作用同jQuery中的ready事件
- window.onload则是页面完全加载完毕,包括各种资源
【46】网页性能优化-异步加载js文件
1、背景
-
浏览器加载 JavaScript 脚本,主要通过
<script>元素完成。正常的网页加载流程是这样的。- 浏览器一边下载 HTML 网页,一边开始解析。也就是说,不等到下载完,就开始解析。
- 解析过程中,浏览器发现
<script>元素,就暂停解析,把网页渲染的控制权转交给 JavaScript 引擎。 - 如果
<script>元素引用了外部脚本,就下载该脚本再执行,否则就直接执行代码。 - JavaScript 引擎执行完毕,控制权交还渲染引擎,恢复解析 HTML 网页。
-
加载外部脚本时,浏览器会暂停页面渲染,等待脚本下载并执行完成后,再继续渲染。原因是 JavaScript 代码可以修改 DOM,所以必须把控制权让给它,否则会导致复杂的线程竞赛的问题
2、优化方案一
- 严格来说,这并不算是异步加载,但是这也是常见的通过改变js加载方式来提升页面性能的一种方式,所以也就放到这里来说。
- 将
<script>放到<body>底部,解决上上面说到的几个问题,一是不会造成页面解析的阻塞,就算加载时间过长用户也可以看到页面而不是一片空白,而且这时候可以在脚本中操作DOM。
3、 defer属性
- 通过给
<script>标签设置defer属性,将脚本文件设置为延迟加载,当浏览器遇到带有defer属性的<script>标签时,会再开启一个线程去下载js文件,同时继续解析HTML文档,等等HTML全部解析完毕DOM加载完成之后,再去执行加载好的js文件。 - 这种方式只适用于引用外部js文件的
<script>标签,可以保证多个js文件的执行顺序就是它们在页面中出现的顺序,但是要注意,添加defer属性的js文件不应该使用document.write方法
因为document已经加载解析完毕,文档流已经关闭了
3、 async属性**
async属性和defer属性类似,也是会开启一个线程去下载js文件,但和defer不同的时,它会在下载完成后立刻执行,而不是会等到DOM加载完成之后再执行,所以还是有可能会造成阻塞。- 同样的,
async也是只适用于外部js文件,也不能在js中使用document.write方法,但是对多个带有async的js文件,它不能像defer那样保证按顺序执行,它是哪个js文件先下载完就先执行哪个
4、动态创建<script>标签
- 方式一
(function(){
var scriptEle = document.createElement("script");
scriptEle.type = "text/javasctipt";
scriptEle.async = true;
scriptEle.src = "http://cdn.bootcss.com/jquery/3.0.0- beta1/jquery.min.js";
var x = document.getElementsByTagName("head")[0];
x.insertBefore(scriptEle, x.firstChild);
})();
- 方式二
(function(){
if(window.attachEvent){ //浏览器兼容
window.attachEvent("load", asyncLoad);
}else{
window.addEventListener("load", asyncLoad);
}
var asyncLoad = function(){
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
}
})();
- 上面两种方法中,第一种方式执行完之前会阻止onload事件的触发,而现在很多页面的代码都在onload时还执行额外的渲染工作,所以还是会阻塞部分页面的初始化处理(在head里面有script,还没有解析到body,哪来的onload)
- 第二种则不会阻止onload事件的触发,onload结束之后才执行
5、什么时候用defer,什么时候用async呢?
- 一般来说,两者之间的选择则是看脚本之间是否有依赖关系,有依赖的话应当要保证执行顺序,应当使用
defer没有依赖的话使用async,同时使用的话defer失效。要注意的是两者都不应该使用document.write,这个导致整个页面被清除 - 下面一幅图表明了同步加载以及
defer、async加载时的区别,其中绿色线代表 HTML 解析,蓝色线代表网络读取js脚本,红色线代表js脚本执行时间:
【47】null与undefined区别
1、null表示"没有对象",即该处不应该有值。典型用法是:
-
作为函数的参数,表示该函数的参数不是对象。
-
作为对象原型链的终点。
//Object.getPrototypeOf() 方法返回指定对象的原型(内部[[Prototype]]属性的值) Object.getPrototypeOf(Object.prototype) // null
2、undefined表示有东西但"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:
- 变量被声明了,但没有赋值时,就等于undefined。
- 调用函数时,应该提供的参数没有提供,该参数等于undefined。
- 对象没有赋值的属性,该属性的值为undefined。
- 函数没有返回值时,默认返回undefined。
var i;
i // undefined
function f(x){console.log(x)}
f() // undefined
var o = new Object();
o.p // undefined
var x = f();
x // undefined
【48】js垃圾回收机制
1、垃圾回收的必要性
- 每当创建一个实体时,都要动态分配内存,如果不释放,就会消耗完系统中所有可用的内存,造成系统崩溃
2、垃圾回收方法:
- 方式一标记清除法
- 当变量进入执行环境,就标记这个变量为“进入环境”,永远不能释放进入环境的变量所占的内存,因为只要执行流进入相应的环境,就可能用到他们。当变量离开环境的时候,就将其标记为“离开环境”
- 垃圾收集器在运行时会给存储在内存中的所有变量都加上标记,然后,它会去掉环境中的变量以及被环境中标量引用的标记 (即正在使用的变量),在此以后,再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾回收器完成内存清除工作,销毁那些带标记的值,并回收他们所占的内存空间。
- 方式二引用计数
-
根据被引用的次数,当声明一个变量并将一个引用类型赋值给该变量时,这个值得引用次数就是1,相反,如果包含对这个值引用的变量又取得了另外一个值,这个值得引用次数就-1,当这个值得引用次数变为0的时候,就说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,就会释放哪些引用次数为0所占的内存。
-
但是这种方法存在一个问题:
- 在这个例子中,objA和objB通过各自的属性相互引用;也就是说这两个对象的引用次数都是2 (即new Object这个值被引用了2次)。在采用引用计数的策略中,由于函数执行之后,这两个对象都离开了作用域,函数执行完成之后,objA和objB还将会继续存在,因为他们的引用次数永远不会是0。这样的相互引用如果说很大量的存在就会导致大量的内存泄露。可以手动切断他们的循环引用
-
3、减少js中垃圾回收
-
方式一对象优化(实时系统)
- 利用cr.wipe(obj)方法清理对象,再为obj添加新的属性,就可以达到重复利用对象的目的。虽然通过清空一个对象来获取“新对象”的做法,比简单的通过{}来创建对象要耗时一些,但是在实时性要求很高的代码中,这一点短暂的时间消耗,将会有效的减少垃圾堆积,并且最终避免垃圾回收暂停,这是非常值得的!
-
方式二数组array优化
- 将[]赋值给一个数组对象,是清空数组的捷径(例如: arr = [];),但是需要注意的是,这种方式又创建了一个新的空对象,并且将原来的数组对象变成了一小片内存垃圾!实际上,将数组长度赋值为0(arr.length = 0)也能达到清空数组的目的,并且同时能实现数组重用,减少内存垃圾的产生
-
方式三function优化
- 函数一般都是在初始化的时候创建,并且此后很少在运行时进行动态内存分配,这就使得导致内存垃圾产生的方法,找起来就不是那么容易了。
- 但是从另一角度来说,这更便于我们寻找了,因为只要是动态创建方法的地方,就有可能产生内存垃圾。例如:将方法作为返回值,就是一个动态创建方法的实例。在游戏的主循环中,setTimeout或requestAnimationFrame来调用一个成员方法是很常见的,例如:
setTimeout( (function(self) { return function () { self.tick(); }; })(this), 16)- 每过16毫秒调用一次this.tick(),嗯,乍一看似乎没什么问题,但是仔细一琢磨,每一次调用都返回了一个新的方法对象,这就导致了大量的方法对象垃圾!
- 为了解决这个问题,可以将作为返回值的方法保存起来,例如:
// at startup this.tickFunc = ( function(self) { return function() { self.tick(); }; } )(this); // in the tick() function setTimeout(this.tickFunc,16)
【49】 session与jwt
1、session+cookie
-
用户向服务器发送用户名和密码。
-
服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
-
服务器向用户返回一个 session_id,写入用户的 Cookie。
-
用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
-
服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
-
缺点:
- 这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
- 举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
- 一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。
2、jwt
2-1原理
- JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}
-
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。
-
服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
2-2、数据结构
-
实际的 JWT 大概就像下面这样。
-
它是一个很长的字符串,中间用点(
.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。 -
JWT 的三个部分依次如下。
Header(头部)
Payload(负载)
Signature(签名)
- 写成一行,就是下面的样子。
Header.Payload.Signature
-
Header
- Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
{ "alg": "HS256", "typ": "JWT" }-
上面代码中,
alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。 -
最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。
-
Payload
- Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
iss (issuer):签发人 exp (expiration time):过期时间 sub (subject):主题 aud (audience):受众 nbf (Not Before):生效时间 iat (Issued At):签发时间 jti (JWT ID):编号- 除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。
{ "sub": "1234567890", "name": "John Doe", "admin": true }-
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
-
这个 JSON 对象也要使用 Base64URL 算法转成字符串。
-
Signature
-
Signature 部分是对前两部分的签名,防止数据篡改(数据存储在客户端,防止被修改)
-
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)- 算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(
.)分隔,就可以返回给用户。
-
-
Base64URL
-
前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。
-
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符
+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_。这就是 Base64URL 算法。
-
3、jwt的使用方式
-
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage
-
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域
- 所以更好的做法是放在 HTTP 请求的头信
Authorization字段里面。
Authorization: Bearer <token>- 另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。
- 所以更好的做法是放在 HTTP 请求的头信
4、jwt的几个特点
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
- JWT 不加密的情况下,不能将秘密数据写入 JWT
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
- JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
【50】定时器中的requestAnimationFrame
1、 setTimeout setInterval
- setTimeout和setInterval的问题是它们都不精确。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行*
2、requestAnimationFrame
-
由系统决定回调函数的执行时机。60Hz的刷新频率,那么每次刷新的间隔中会执行一次回调函数,不会引起丢帧,不会卡顿
-
采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果
-
优势
- requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
- 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
- requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
-
代码实现
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
<style>
#e{
width: 100px;
height: 100px;
background: red;
position: absolute;
left: 0;
top: 0;
zoom: 1;
}
</style>
</head>
<body>
<div id="e"></div>
<script>
var e = document.getElementById("e");
var flag = true;
var left = 0;
var rafId = null
function render() { //绘画函数,自定义
if(flag == true){
if(left>=100){
flag = false
}
e.style.left = ` ${left++}px`
}else{
if(left<=0){
flag = true
}
e.style.left = ` ${left--}px`
}
}
//requestAnimationFrame效果
(function animloop(time) {
console.log(time,Date.now())
render();
rafId = requestAnimationFrame(animloop);//屏幕每次刷新都会调用animloop
//如果left等于50 停止动画
if(left == 50){
cancelAnimationFrame(rafId) //用于关闭循环
}
})();
//setInterval效果
// setInterval(function(){
// render()
// },1000/60)
</script>
</body>
</html>
-
requestAnimationFrame(animloop)通知浏览器它希望在需要绘制图框时立即执行回调函数。-
这里没有递归,因为我们要通过一个事件循环来断开执行。 该函数不是在调用自身,而是在被调用, 每次执行完毕,就可以回收该RAM(浏览器的优化)
-
值得记住的是,如果step简单地调用自身,那将是无限的递归。 在这种情况下,堆栈会炸毁。 如果我们想象一个无限的堆栈不会崩溃(或尾调用递归),它将阻塞事件循环并阻止其他任何代码运行
-
【51】flex及其属性
【52】实现一个两列等高布局,讲讲思路
1、方式一使用flex布局
<style>
.parent {
display: flex;
}
.left {
width: 100px;
margin-left: 20px;
}
.right {
flex: 1;
}
</style>
- 注意这里实际上使用了 align-items: stretch,flex 默认的 align-items 的值为 stretch,
2、方式二
- 为了实现两列等高,可以给每列加上如下代码
padding-bottom:9999px;
margin-bottom:-9999px;
- 同时父元素设置overflow:hidden;但是只是为伪等高,只有背景等高
<style>
.parent {
overflow: hidden;
}
.left,.right {
padding-bottom: 9999px;
margin-bottom: -9999px;
}
.left {
float: left;
width: 100px;
margin-right: 20px;
}
.right {
overflow: hidden;
}
</style>