1、请用 js 去除字符串空格?
答案:replace 正则匹配方法、str. trim()方法、JQ 方法:$. trim(str)方法
解析:
方法一:replace 正则匹配方法
去除字符串内所有的空格:str = str. replace(/\s*/g, “”);
去除字符串内两头的空格:str = str. replace(/^\s|\s$/g, “”);
去除字符串内左侧的空格:str = str. replace(/^\s*/, “”);
去除字符串内右侧的空格:str = str. replace(/(\s*$)/g, “”);
方法二:str. trim()方法
trim()方法是用来删除字符串两端的空白字符并返回,trim 方法并不影响原来的字符串本身,它返回的是一个新的字符串。
缺陷:只能去除字符串两端的空格,不能去除中间的空格
方法三:JQ 方法:$. trim(str)方法
$. trim() 函数用于去除字符串两端的空白字符。
注意:$. trim()函数会移除字符串开始和末尾处的所有换行符,空格(包括连续的空格)和制表符。如果这些空白字符在字符串中间时,它们将被保留,不会被移除。
1.1JS数组常用的方法
1、获取数组字符串
toString();将数组转换成字符串
语法格式:数组名.toString();
数组元素间用逗号相隔,拼接成一个字符串
join(分隔符);是将数组转换成字符串,元素之间用指定的分隔符隔开,注意分隔符是字符串形式
语法:数组名.join(分隔符);
2、连接数组
将两个或更多个数组拼接为一个大的数组
语法:数组名.concat(需要拼接数组列表);数组之间用逗号隔开
注意:该方法不会改变原有数组,而是会拼接后产生一个新的数组
3、获取子数组(数组的切割)
就是用原有数组的部分元素组成一个新的数组
语法:数组名.slice(start,end);
-start指定从元素的哪个下标开始剪切,如果为负值则表示从尾部开始算,比如设置-1,指从最后一个元素,-2表示倒数第二个元素
-end指结束处的下标(不包含),可以省略
注意:裁剪后不会影响原有数组,而是会生成一个新的数组
4、修改数组
对数组就行修改:对数组进行元素的添加或删除
语法: 数组名.splice(start,count,e1,e2,e3…);
-start表示添加/删除的起始位置
-count表示要删除的元素个数,0表示不删除
-e1,e2,…en;表示在start出添加的元素
注意:修改后会改变原有数组
var arr = [1, 2, 3, 4, 5];
arr.splice(1,1,'a')
console.log(arr) //[ 1, 'a', 3, 4, 5 ]
5、倒转数组
将数组元素的顺序颠倒过来,会改变原有数组
语法:数组名.reverse();
6、数组排序
通过sort()方法对数组元素由小到大进行排序
语法:数组名.sort();
注意:汉字是通过其Unicode编码进行排序
7、进出栈操作
栈和队列的区别:
栈是先进后出(比如把书放进箱子里,最先放进去的在最下边,最后一本在最顶部,如果要把书拿出来,需要从最后一本开始)
队列是先进先出(比如火车过隧道,第一节车厢先进入隧道,那么第一节车厢也将先出隧道)
数组的操作可以按照标准的栈式访问(先进后出)
入栈:将元素存入数组,放在栈顶(将元素放在数组的尾部)
出栈:将元素弹出数组,(将最后一个元素从数组移除)
push(): 入栈,在数组的尾部添加元素,并且会返回数组的新长度
语法:数组名.push(元素列表);元素间用逗号隔开
pop(): 出栈,删除数组尾部的元素,并返回新长度,括号中不需要参数,一次删除一个元素
除了以上两个操作外,还提供了 shift()和unshift()方法,表示从数组的头部增删元素
数组名.shift():从头部删除一个元素
数组名.unshift():从头部添加元素
2、js 是一门怎样的语言,它有什么特点
-
脚本语言。JavaScript 是一种解释型的脚本语言, C、C++等语言先编译后执行, 而 JavaScript 是在程序的运行过程中逐行进行解释。
-
基于对象。JavaScript 是一种基于对象的脚本语言, 它不仅可以创建对象, 也能使用现有的对象。
-
简单。JavaScript 语言中采用的是弱类型的变量类型, 对使用的数据类型未做出严格的要求, 是基于 Java 基本语句和控制的脚本语言, 其设计简单紧凑。
-
动态性。JavaScript 是一种采用事件驱动的脚本语言, 它不需要经过 Web 服务器就可以对用户的输入做出响应。
-
跨平台性。JavaScript 脚本语言不依赖于操作系统, 仅需要浏览器的支持。
2.1 什么是语法糖
是一种为避免编码出错和提高效率编码而生的语法层面的优雅解决方案,简单说就是,一种便携写法。
3、== 和 === 的不同
== 是抽象相等运算符,而 === 是严格相等运算符。 == 运算符是在进行必要的类型转换后,再比较。 === 运算符不会进行类型转换,所以如果两个值不是相同的类型,会直接返回 false 。
相等运算符可以做类型转换,全等运算符是在相等运算之上再加上类型的对比。
1.数字字符串可转换为数字,布尔值也可以转为数字,例如:'1’true是true
2.Null和Undefined不能进行转换,NullUndefined是true,它们和0比较都是false
3.NaN表示一个不确切的数值,所以无论NaN和NaN怎么比较都是false
4.比较对象的时候,要考虑引用对象的地址。
4、事件冒泡和事件委托
事件冒泡:当一个子元素的事件被触发的时候(例如onclick事件),该事件会从事件(被电击的元素)开始逐个向上传播,触发父级元素的点击事件。
事件委托(事件代理) :将子元素的事件通过冒泡的形式交由父元素来执行。比如我们平时在给ul中的li添加事件的时候,我们都是通过for循环一个个添加,如果li很多个的话,其实就有点占内存了,这个时候可以用 事件代理来优化性能。
<style>
*{
margin: 0;
padding: 0;
}
ul{
list-style: none;
width: 400px;
border: 1px solid #000;
margin: 100px auto;
}
li{
width: 100%;
height: 50px;
border-bottom: 1px solid #000;
box-sizing: border-box;
}
.selected{
background-color: red;
}
</style>
<ul>
<li class="selected">1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
</ul>
<script>
let oUl = document.querySelector('ul');
let oLi = document.querySelector('.selected');
oUl.onclick = function (event) {
event = event || window.event;//兼容所有浏览器
oLi.className = '';
let item = event.target;//event.target为当前点击的li
item.className = 'selected';
oLi = item;
}
</script>
5、click()和onclick()
onclick()是绑定事件:作用是执行函数代码;
click()本身是方法:作用是触发onclick()事件
<script type="text/javascript">
$(function(){
$("#b2").click(function(){
$("#b1").click();
});});
function change(){
alert("调用了方法change");
}
</script>
<button id = "b1" onclick="change()">按钮1</button>
<button id = "b2">按钮2</button>
5.1 reduce()用法:
reduce 为数组中的每一个元素依次执行回调函数。reduce(cur, next, index, arr):cur代表当前返回的值或初始值,next代表当前正在处理的元素,index标识当前处理元素的索引,arr表示原数组。
1,累加器
let arr = [3,5,7,8,10];
let sum = arr.reduce((cur, next) => {
return cur + next;
}, 0) // 返回数组总和
2,去重(去重数据)
let arr = [3,3,5,2,10];
let arr1 = arr.reduce((cur,next) => {
cur.indexOf(next) === -1 && cur.push(next);
return cur;
}, [])
6、require 与 import 的区别
答案:两者的加载方式不同、规范不同
第一、两者的加载方式不同,require 是在运行时加载,而 import 是在编译时加载
require(’. /a’)(); // a 模块是一个函数,立即执行 a 模块函数
var data = require(’. /a’). data; // a 模块导出的是一个对象
var a = require(’. /a’)[0]; // a 模块导出的是一个数组==>哪都行
import $ from ‘jquery’;
import * as _ from ‘_’;
import {a, b, c} from ‘. /a’;
import {default as alias, a as a_a, b, c} from ‘. /a’; ==>用在开头
第二、规范不同,require 是 CommonJS/AMD 规范,import 是 ESMAScript6+规范
第三、require 特点:社区方案,提供了服务器/浏览器的模块加载方案。非语言层面的标准。只能在运行时确定模块的依赖关系及输入/输出的变量,无法进行静态优化。
import 特点:语言规格层面支持模块功能。支持编译时静态分析,便于 JS 引入宏和类型检验。动态绑定。
7、javascript 对象的几种创建方式
第一种:Object 构造函数创建
//new 关键字
var persons = new Object();
persons.firstname = 'john';
persons.lastname = 'Doe';
persons.age = 50 ;
//create来创建,通常该方法用来创建继承对象
var child = Object.create(persons); //继承父类的属性
child.name = 'jack';
child.sex = 'nan';
console.log(child);
第二种:使用对象字面量表示法
var Cat = {};//JSON
Cat.name="kity";//添加属性并赋值
Cat.age=2;
Cat.sayHello=function(){
alert("hello "+Cat.name+",今年"+Cat["age"]+"岁了");//可以使用“.”的方式访问属性,也可以使用HashMap的方式访问
}
Cat.sayHello();//调用对象的(方法)函数
第三种:使用工厂模式创建对象。工厂模式创建的对象1.在一个函数内部生成、2.并不能解决资源占用的问题 3、方法名小写
function createPerson(name,age) {
return{
name:name,
age:age,
sayHei:function () {
return name + age;
}
}
}
let p1 = createPerson('jack','18');
第四种: 使用构造函数创建对象。
1.通过new来调用这个函数
2.this指的是对象的原型实例
3.构造模式的方法命名采用pascal命名法,每个单词首字母大写
4.也不能解决资源占用的问题
function CreatePerson(name,age) {
this.name = name;
this.age = age;
this.sayHei = function () {
return this.name + this.age;
}
}
let p1 = new createPerson('jack',18);
第五种:原型创建对象模式。每个对象下面都有一个Prototype原型对象,解决了资源占用问题,但当成员为引用类型时会有问题。
function Dog(){}
Dog.prototype.name="旺财";
Dog.prototype.eat=function(){
alert(this.name+"是个吃货");
}
var wangcai =new Dog();
wangcai.eat();
第六种:组合使用构造函数模式和原型模式
function Car(name,price){
this.name=name;
this.price=price;
}
Car.prototype.sell=function(){
alert("我是"+this.name+",我现在卖"+this.price+"万元");
}
var camry =new Car("凯美瑞",27);
camry.sell();
8、原型和原型链的理解
原型:在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象,使用原型对象的好处是所有对象实例共享它所包含的属性和方法
隐式原型(proto):上面说的这个原型是JavaScript中的内置属性[[prototype]],此属性继承自object对象,在脚本中没有标准的方式访问[[prototype]],但Firefox、Safari和Chrome在每个对象上都支持一个属性proto,隐式原型的作用是用来构成原型链,实现基于原型的继承。
显示原型(prototype):每一个函数在创建之后,便会拥有一个prototype属性,这个属性指向函数的原型对象,显示原型的作用是用来实现基于原型的继承与属性的共享。
prototype 和proto的关系是什么:
所有的对象都拥有__proto__属性,它指向对象构造函数的 prototype 属性
所有的函数都同时拥有proto和 protytpe 属性
函数的proto指向自己的函数,实现 函数的 prototype 是一个对象 ,所以函数的 prototype 也有proto属性 指向 Object. prototype,Object. prototype. proto指向 null
prototype 和 proto 区别是什么?
1)prototype是构造函数的属性
2)__proto__是每个实例都有的属性,可以访问 [[prototype]] 属性
3)实例的__proto__与其构造函数的prototype指向的是同一个对象
原型链:原型链是原型对象创建过程的历史记录,当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的proto隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的proto中查找,这样一层一层向上查找就会形成一个链式结构.
8.1作用域和作用域链的理解
作用域:变量和函数的可访问范围,变量的作用域有两种:全局变量和局部变量。
答案:作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到 window 对象即被终止,作用域链向下访问变量是不被允许的。
9、 javascript 的 typeof 返回哪些数据类型
7 种分别为 string、boolean、number、Object(对象,数组,null)、Function、undefined、symbol(ES6)
JavaScript 的数据类型
答案:JS 数据类型共有六种,分别是 String、Number、Boolean、Null、Undefined 和 Object 等, 另外,ES6 新增了 Symbol 类型。其中,Object 是引用数据类型,其他的都是基本数据类型(Primitive Type)。
9.1 基本数据类型与引用类型的区别
1、声明变量时不同的内存分配:基本类型存储在栈中,可以直接访问;引用类型存储在堆中,存储在变量处的值是一个指针,指向存储对象的内存地址。
2、不同的访问机制:访问对象时,先得到这个对象在堆内存中的地址,再根据地址获取这个对象的值,即按引用访问;而基本类型可以直接访问。
3、复制变量时不同:基本类型是赋值后两个变量完全独立;引用类型是将内存地址赋值给新变量,一个做出改变另一个也会跟着变化。
10、如何判断 JS 变量的一个类型(至少三种方式)
答案:typeof、instanceof、 constructor、 object.prototype.toString.call()
constuctor:基本数据类型中只有boolean,string和symbol可以通过constructor属性查询到构造函数。用法:true.constructor === Boolean //true
10.1 js判断空对象的几种方法
1、将对象转为字符串比较,JSON.stringify
var a={}; var b=new Object(); console.log(JSON.stringify(a)=="{}") *//true* console.log(JSON.stringify(b)=="{}") *//true*
2、for ···in循环,使用for in循环可以遍历所有属性以次判断对象是否为空对象:
var a={};
function isEmptyObject(obj){
for(var key in obj){
return false
};
return true
};
console.log(isEmptyObject(a));//true
3、Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。用此方法判断空对象只需要判断返回的数组长度是否为零,为零的话就是空对象。
var obj = { };
console.log(Object.getOwnPropertyNames(obj).length == 0); // true
4、Object.keys()
该方法属于 ES5 标准,IE9 以上和其它现代浏览器均支持。Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致。用此方法判断空对象只需要判断返回的数组长度是否为零,为零的话就是空对象。
var data = {};
var arr = Object.keys(data);
console.log(arr.length == 0);//true
11、 列举 3 种强制类型,转换和 2 种隐式类型转换
答案:强制: parseInt(), parseFloat(), Number(), Boolean(), String()
隐式: +, -
12、你对闭包的理解?优缺点?
概念:闭包就是能够读取其他函数内部变量的函数。
三大特性:
- 函数嵌套函数。
- 函数内部可以引用外部的参数和变量。
- 参数和变量不会被垃圾回收机制回收。
优点:
- 希望一个变量长期存储在内存中。
- 避免全局变量的污染。
- 私有成员的存在。
缺点:
- 常驻内存,增加内存使用量。
- 使用不当会很容易造成内存泄露。
解决内存泄露:1、解除闭包;2、将引用的变量设置为null。
14、如何判断一个对象是否为数组
第一种方法:使用 instanceof 操作符。
第二种方法:使用 ECMAScript 5 新增的 Array. isArray()方法。
第三种方法:使用使用 Object. prototype.toString.call() 上的 方法判断。
Array.prototype.slice.call(arguments)能将具有length属性的对象转成数组
15、Object. prototype. toString. call() 和 instanceOf 和 Array. isArray() 区别好坏
答案:
- Object. prototype. toString. call()
- 优点:这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。
- 缺点:不能精准判断自定义对象,对于自定义对象只会返回[object Object]
- instanceOf
- 优点:instanceof 可以弥补 Object. prototype. toString. call()不能判断自定义实例化对象的缺点。
- 缺点: instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true,且不同于其他两种方法的是它不能检测出 iframes。
- Array. isArray()
- 优点:当检测 Array 实例时,Array. isArray 优于 instanceof ,因为 Array. isArray 可以检测出 iframes
- 缺点:只能判别数组
16、面向对象和面向过程的异同
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
- 面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
面向对象的三个基本特征是:封装、继承、多态。
封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承 是指这样一种能力:在一个类的基础上创建一个新的类,它拥有之前类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
17、 JS 块级作用域、变量提升
-
块级作用域
JS 中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称 ES6)中新增了块级作用域。块作用域由 { } 包括,if 语句和 for 语句里面的{ }也属于块作用域。
-
变量提升
- 如果变量声明在函数里面,则将变量声明提升到函数的开头
- 如果变量声明是一个全局变量,则将变量声明提升到全局作用域的开头
解释 JavaScript 中的作用域与变量声明提升?
- 作用域只会对某个范围产生作用,而不会对外产生影响的封闭空间。在这样的一些空间里,外 部不能访问内部变量,但内部可以访问外部变量。
- 所有申明都会被提升到作用域的最顶上
- 同一个变量申明只进行一次,并且因此其他申明都会被忽略
- 函数声明的优先级优于变量申明,且函数声明会连带定义一起被提升
18、var、let、const 的区别
- var 定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问,存在变量提升。
- let 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问,不存在变量提升,在同一个作用域下不可以重复定义同一个变量值。
- const 用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。
- 同一个变量只能使用一种方式声明,不然会报错
- var 存在变量提升,let 不存在;let在同一个作用域下不可以重复定义同一个变量值,而var可以
19、 null/undefined 的区别
null: Null 类型,代表“空值",代表一个空对象指针,使用 typeof 运算得到 “object",所以你可以认为它是一个特殊的对象值。
undefined: Undefined 类型,当一个声明了一个变量未初始化时,得到的就是 undefined。
20、JS 哪些操作会造成内存泄露
1)意外的全局变量引起的内存泄露
2)闭包引起的内存泄露
3)没有清理的 DOM 元素引用
4)被遗忘的定时器或者回调
5)子元素存在引起的内存泄露
6)IE7/8 引用计数使用循环引用产生的问题
21、 jsonp 优缺点
- 优点
- 1 它不像 XMLHttpRequest 对象实现的 Ajax 请求那样受到同源策略的限制,JSONP 可以跨越同源策略;
-
2 它的兼容性更好,在更加古老的浏览器中都可以运行,不需要 XMLHttpRequest 或 ActiveX 的支持
-
3 在请求完毕后可以通过调用 callback 的方式回传结果。将回调方法的权限给了调用方。这个就相当于将 controller 层和 view 层终于分 开了。我提供的 jsonp 服务只提供纯服务的数据,至于提供服务以 后的页面渲染和后续 view 操作都由调用者来自己定义就好了。如果有两个页面需要渲染同一份数据,你们只需要有不同的渲染逻辑就可以了,逻辑都可以使用同 一个 jsonp 服务。
- 缺点
- 1 它只支持 GET 请求而不支持 POST 等其它类型的 HTTP 请求
-
2 它只支持跨域 HTTP 请求这种情况,不能解决不同域的两个页面之间如何进行 JavaScript 调用的问题。
-
3 jsonp 在调用失败的时候不会返回各种 HTTP 状态码。
-
4 缺点是安全性。万一假如提供 jsonp 的服务存在页面注入漏洞,即它返回的 javascript 的内容被人控制的。那么结果是什么?所有调用这个 jsonp 的网站都会存在漏洞。于是无法把危险控制在一个域名下…所以在使用 jsonp 的时候必须要保证使用的 jsonp 服务必须是安全可信的
22、this指向问题
- 解析器在调用函数时,每次都会向函数内部传递进一个隐含的参数,这个参数就是this
this指向的是一个对象,对象称为函数执行的上下文对象 - 根据函数调用的方式不同,this指向不同的对象
1、以函数形式调用时,this永远都是window
2、以方法形式调用时,this就是调用方法的对象 - this的情况:
1、当以函数的形式调用时,this就是window,
2、当以方法的形式调用时,谁调用方法this就是谁,
3、当以构造函数的形式调用时,this就是新创建的那个对象
4、使用call和apply调用时,this是指定的那个对象
1.当this直接在函数体中时,this指向window
2.当函数作为一个对象的属性时,函数中的this指向这个对象
3.在事件中,this指向调用者
4.构造函数中的this指向当前实例化对象
5.原型上的this指向调用者
6.this在定时器中指向window
7.this在箭头函数中的指向是继承而来的; 默认指向在定义它时所处的对象(宿主对象),也就是外层代码块的this
最后总结:一般情况this指向它的调用者,在定时器中this一般指向window,在箭头函数中继承它外层this的指向.然后需要注意的是,使用call, apply, bind(ES5新增)绑定的this指的是 绑定的对象。
22.1普通函数和箭头函数的区别
箭头函数:
①没有this,super,arguments 和new.target 绑定,箭头函数中的这些值由外围最近一层非箭头函数决定。
②不能通过new 关键字调用。它没有[[Construct]], 所以不能用作构造函数。如用会报错
③没有原型( prototype )
④不可以改变this 的绑定,函数内部的this 值不可被改变,在函数的生命周期内始终保持一致。
⑤不支持arguments 对象。所以只能通过命名参数和不定参数两种形式访问函数参数。
⑥不支持重复的命名函数。
普通函数中的this:
1.this 总是代表它的直接调用者, 例如obj.func , 那么func 中的this 就是obj
2.在默认情况(非严格模式下,未使用’use strict’), 没找到直接调用者,则this 指的是window
3.在严格模式下,没有直接调用者的函数中的this 是undefined
4.使用call,apply,bind(ES5 新增)绑定的,this 指的是绑定的对象
箭头函数中的this:默认指向在定义它时,它所处的对象,而不是执行时的对象, 定义它的时候,可能环境是window (即继承父级的this )
23、 call() 和 apply() 的含义和区别?
bind,apply,call的区别
apply、call和bind的共同点,作用:
- 他们都可以去调用别人的方法
- 他们都可以改变this的指向
- 他们的第一个参数都是this对象的指向
- 后续都可以进行参数的传递
首先说明两个方法的含义:
- call:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B. call(A, args1, args2); 即 A 对象调用 B 对象的方法。
- apply:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B. apply(A, arguments); 即 A 对象应用 B 对象的方法。
call 与 apply 的相同点:
- 方法的含义是一样的,即方法功能是一样的;
- 第一个参数的作用是一样的;
call 与 apply 的不同点:两者传入的列表形式不一样
- call 可以传入多个参数;
- apply 只能传入两个参数,所以其第二个参数往往是作为数组形式传入
call 和 apply 其实是一样的,区别就在于传参时参数是一个一个传或者是以一个数组的方式来传。
call 和 apply 都是在调用时生效,改变调用者的 this 指向
bind 也是改变 this 指向,不过不是在调用时生效,而是返回一个新函数。bind可以直接在新构建的函数里面传递参数,也可以在传递的第二个参数里面写参数。
let name = 'Jack'
const obj = {name: 'Tom'}
function sayHi() {console.log('Hi! ' + this.name)}
sayHi() // Hi! Jack
sayHi.call(obj) // Hi! Tom
const newFunc = sayHi.bind(obj)
24、new 操作符具体干了什么呢?
new 共经过了 4 个阶段
- 1、创建一个空对象 var obj=new Object();
- 2、设置原型链,将空对象的原型链连接到另一个对象。 obj.proto= Func.prototype;
- 3、让 Func 中的 this 指向 obj,并执行 Func 的函数体
var result =Func.call(obj); - 4、判断 Func 的返回值类型:如果是值类型,返回obj。如果是引用类型,就返回这个引用类型的对象。
if (typeof(result) == “object”){
func=result;
}
else{
func=obj;
}
25、事件绑定与普通事件有什么区别
- 用普通事件添加相同事件,下面会覆盖上面的,而事件绑定不会
- 普通事件是针对非 dom 元素,事件绑定是针对 dom 元素的事件
26、 javascript 的同源策略
同源策略是客户端脚本(尤其是Javascript)的重要的安全度量标准。它最早出自Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。
这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议。
指一段脚本只能读取来自同一来源的窗口和文档的属性。
27、事件冒泡与事件捕获
事件冒泡:由最具体的元素(目标元素)向外传播到最不具体的元素(子元素向父元素传递)
事件捕获:由最不确定的元素到目标元素(父元素向子元素传递)
28、 js 中 callee 与 caller 的作用
- caller 返回一个调用当前函数的引用。 如果是由顶层调用的话 则返回 null
2. callee 返回一个正在被执行函数的引用 (这里常用来递归匿名函数本身 但是在严格模式下不可行)
30、 Js 动画与 CSS 动画区别及相应实现
CSS3的动画的优点:在性能上会稍微好一些,浏览器会对CSS3的动画做一些优化,代码相对简单(animation:all 0.3s; @keyframe)
缺点:在动画控制上不够灵活,兼容性不好
JavaScript的动画正好弥补了这两个缺点,控制能力很强,可以单帧的控制、变换,同时写得好完全可以兼容IE6,并且功能强大。对于一些复杂控制的动画,使用javascript会比较靠谱。
31、定时器 setInterval 有一个有名函数 fn1,setInterval(fn1, 500)与 setInterval(fn1(), 500)有什么区别?
答案:第一个是重复执行每 500 毫秒执行一次,后面一个只执行一次。
32、你用过 require. js 吗?它有什么特性?
(1)实现 js 文件的异步加载,避免网页失去响应;
(2)管理模块之间的依赖性,便于代码的编写和维护。
requireJS 的核心原理是什么?(如何动态加载的?如何避免多次加载的?如何缓存的?)
答案:核心是 js 的加载模块,通过正则匹配模块以及模块的依赖关系,保证文件加载的先后顺序,根据文件的路径对加载过的文件做了缓存
33、对象浅拷贝和深拷贝有什么区别
浅拷贝可以使用列表自带的copy()函数(如list.copy()),或者使用copy模块的copy()函数。深拷贝只能使用copy模块的deepcopy(),所以使用前要导入:from copy import deepcopy
如果拷贝的对象里的元素只有值,没有引用,那浅拷贝和深拷贝没有差别,都会将原有对象复制一份,产生一个新对象,对新对象里的值进行修改不会影响原有对象,新对象和原对象完全分离开。
如果拷贝的对象里的元素包含引用(像一个列表里储存着另一个列表,存的就是另一个列表的引用),那浅拷贝和深拷贝是不同的,浅拷贝虽然将原有对象复制一份,但是依然保存的是引用,所以对新对象里的引用里的值进行修改,依然会改变原对象里的列表的值,新对象和原对象完全分离开并没有完全分离开。而深拷贝则不同,它会将原对象里的引用也新创建一个,即新建一个列表,然后放的是新列表的引用,这样就可以将新对象和原对象完全分离开。
JS 中深拷贝的几种实现方法
1、使用递归的方式实现深拷贝
//使用递归的方式实现数组、对象的深拷贝
function deepClone1(obj) {
//判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝
var objClone = Array.isArray(obj) ? [] : {};
//进行深拷贝的不能为空,并且是对象或者是数组
if (obj && typeof obj === "object") {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = deepClone1(obj[key]);
} else {
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
2、通过 JSON 对象实现深拷贝
//通过js的内置对象JSON来进行数组对象的深拷贝
function deepClone2(obj) {
var _obj = JSON.stringify(obj),
objClone = JSON.parse(_obj);
return objClone;
}
JSON对象实现深拷贝的一些问题:无法实现对对象中方法的深拷贝
3、通过jQuery的extend方法实现深拷贝
var array = [1,2,3,4];
var newArray = $.extend(true,[],array);
4、Object.assign()拷贝
当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。
5、lodash函数库实现深拷贝
lodash很热门的函数库,提供了 lodash.cloneDeep()实现深拷贝
34、如何编写高性能的 Javascript?
- 使用 DocumentFragment 优化多次 append
- 通过模板元素 clone ,替代 createElement
- 使用一次 innerHTML 赋值代替构建 dom 元素
- 使用 firstChild 和 nextSibling 代替 childNodes 遍历 dom 元素
- 使用 Array 做为 StringBuffer ,代替字符串拼接的操作
- 将循环控制量保存到局部变量
- 顺序无关的遍历时,用 while 替代 for
- 将条件分支,按可能性顺序从高到低排列
- 在同一条件子的多( >2 )条件分支时,使用 switch 优于 if
- 使用三目运算符替代条件分支
- 需要不断执行的时候,优先考虑使用 setInterval
35、documen. write 和 innerHTML 的区别?
- document. write 是重写整个 document, 写入内容是字符串的 html
- innerHTML 是 HTMLElement 的属性,是一个元素的内部 html 内容
37、事件绑定的方式
-
嵌入 dom
按钮
-
直接绑定
btn.onclick = function() {};
-
事件监听
btn.addEventListener(“click”, function() {});
38、target 和 currentTarget 区别
- event. target:返回触发事件的元素
- event. currentTarget:返回绑定事件的元素
40、forEach,map和filter的区别
- filter函数,顾名思义,它是一个用来过滤的函数。他可以通过指定的过滤条件,筛选出数组中符合条件的元素,并返回。不会对空数组进行检测。filter() 不会改变原始数组。
//返回数组 ages 中所有元素都大于 18 的元素:
var ages = [32, 33, 16, 40];
function checkAdult(age) {
return age >= 18;
}
function myFunction(arr) {
var a=arr.filter(checkAdult);
console.log(a)
}
myFunction(ages) - map函数,这个函数与filter函数不同之处在于,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。而map则会返回传入函数return的值。map函数之后,数组元素个数不变,但是按照一定的条件转换,数组元素发生了变化。filter函数之后,数组元素个数可能发生了改变,但是数组元素不会发生改变。
var arr=[‘aa’,‘bb’,‘cc’];
function Upper(arr){
arr=arr.map(function(item){
return String.prototype.toUpperCase.call(item);
});
console.log(arr);
}
Upper(arr); - forEach函数,可以实现对数组的遍历,和map函数与filter函数不同的是它没有返回值。
41、for和forEach的区别
1、foreach
定义:foreach又叫做增强for循环,相当于for循环的简化版,因此在一些较复杂的循环中不适用。
forEach方法中的function回调有三个参数:
第一个参数是遍历的数组内容,
第二个参数是对应的数组索引,
第三个参数是数组本身
结构:foreach(元素类型 元素名称:循环对象(数组、集合)){
循环语句
}
特点:foreach在循环次数未知或者计算起来较复杂的情况下效率比for循环高。但是更为复杂的一些循环还是需要用到for循环效率更高。
var arr = [1,2,3,4];
var sum =0;
arr.forEach(function(value,index,array){
array[index] == value; //结果为true
sum+=value;
});
console.log(sum); //结果为 10
2、foreach与for循环的明显差别在于foreach循环时循环对象(数组、集合)被锁定,不能对循环对象中的内容进行增删改操作。
// for循环:可以修改循环语句
var array = ["a","b","c","d"];
for (var i = 0; i < array.length; i ++) {
array[i] = "A";
};
console.log(array); // 结果:"A" "A" "A" "A"
// foreach循环:不能修改循环语句
var array2 = ["a","b","c","d"];
array2.forEach(function(item) {
item = "A";
});
console.log(array2); // 结果:["a","b","c","d"]
42、数组中的forEach和map的区别
相同点:
- 都是循环遍历数组中的每一项
- forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项),index(索引值),arr(原数组)
- 匿名函数中的this都是指向window
- 只能遍历数组
- 都不会改变原数组
区别:
map方法:
1.map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。
2.map方法不会对空数组进行检测,map方法不会改变原始数组。
3.浏览器支持:chrome、Safari1.5+、opera都支持,IE9+,
array.map(function(item, index, arr) {}, thisValue)
var arr = [0, 2, 4, 6, 8];
var str = arr.map(function(item, index, arr) {
console.log(this); //window
console.log("原数组arr:", arr); //注意这里执行5次
return item / 2;
}, this);
console.log(str); //[0,1,2,3,4]
若arr为空数组,则map方法返回的也是一个空数组。
forEach方法:
-
forEach方法用来调用数组的每个元素,将元素传给回调函数
-
forEach对于空数组是不会调用回调函数的。
Array.forEach(function(item, index, arr) {}, this)
var arr = [0, 2, 4, 6, 8];
var sum = 0;
var str = arr.forEach(function(item, index, arr) {
sum += item;
console.log(“sum的值为:”, sum); //0 2 6 12 20
console.log(this); //window
}, this)
console.log(sum); //20
console.log(str); //undefined
无论arr是不是空数组,forEach返回的都是undefined。这个方法只是将数组中的每一项作为callback的参数执行一次。
43、for in和for of的区别
简单总结就是,for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。
for-in总是得到对象的key或数组、字符串的下标。
for-of总是得到对象的value或数组、字符串的值,另外还可以用于遍历Map和Set。
for in
-
一般用于遍历对象的可枚举属性。以及对象从构造函数原型中继承的属性。对于每个不同的属性,语句都会被执行。
-
不建议使用 for in 遍历数组,因为输出的顺序是不固定的。
-
如果迭代的对象的变量值是 null 或者 undefined, for in 不执行循环体,建议在使用 for in 循环之前,先检查该对象的值是不是 null 或者 undefined。
for of
- for…of 语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
44、JavaScript 中 undefined 和 not defined 的区别
答案:undefined是没有初始化,not defined是没有声明
47、typeof 与 instanceof 区别
1、typeof是一元运算符,返回结果是该类型的字符串形式表示【6】(number、string、undefined、boolean、function、object)
2、instanceof用于判断一个变量是否某个对象的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 在这里需要特别注意的是:instanceof 检测的是原型。
typeof会返回一个变量的基本类型,instanceof返回的是一个布尔值
instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型
而typeof 也存在弊端,它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了function 类型以外,其他的也无法判断。
48、微任务和宏任务
/*
* 宏任务
* 分类: setTimeout setInterval requrestAnimationFrame
* 1. 宏任务所处的队列就是宏任务队列
* 2. 第一个宏任务队列中只有一个任务: 执行主线程的js代码
* 3. 宏任务队列可以有多个
* 4. 当宏任务队列的中的任务全部执行完以后会查看是否有微任务队列如果有先执行微任务队列中的所有任务,如果没有就查看是否有宏任务队列
*
* 微任务
* 分类: new Promise().then(回调) process.nextTick
* 1. 微任务所处的队列就是微任务队列
* 2. 只有一个微任务队列
* 3. 在上一个宏任务队列执行完毕后如果有微任务队列就会执行微任务队列中的所有任务
* */
整个代码的执行流程是: setTimeout推到宏任务队列 然后遇到promise立刻执行,输出2。遇到promise.then推到微任务队列。继续往下,输出4。 至此,当前主线程中的同步任务执行完毕,读取微任务队列中的promise.then,输出3, 浏览器渲染之后继续执行宏任务setTimeout,输出1
console.log('----------------- start -----------------');
setTimeout(() => {
console.log('setTimeout');
}, 0)
new Promise((resolve, reject) => {
for (var i = 0; i < 5; i++) {
console.log(i);
}
resolve(); // 修改promise实例对象的状态为成功的状态
}).then(() => {
console.log('promise实例成功回调执行');
})
console.log('----------------- end -----------------');
----------------- start -----------------
0
1
2
3
4
----------------- end -----------------
promise实例成功回调执行
setTimeout
=======================================
console.log(' start ')
setTimeout(() => {
console.log('timeout ')
})
new Promise((resolve, reject) => {
console.log(' promise start ')
resolve()
console.log('promise after resolve ')
}).then(() => {
console.log(' promise finish ')
}).catch(() => {
console.log(' promise catch ')
})
console.log('finish ')
==========================================
start
promise start
promise after resolve
finish
promise finish
timeout
49、setTimeout和setInterval的区别
setTimeout和setInterval都属于JS中的定时器,可以规定延迟时间再执行某个操作,不同的是setTimeout在规定时间后执行完某个操作就停止了,而setInterval则可以一直循环下去。
50、DOM中节点的类型
51、js垃圾回收机制和引起内存泄漏的操作
Js具有自动垃圾回收机制。垃圾收集器会按照固定的时间间隔周期性的执行。
JS中最常见的垃圾回收方式是标记清除。
工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
工作流程:
- 垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。
- 去掉环境中的变量以及被环境中的变量引用的变量的标记。
- 再被加上标记的会被视为准备删除的变量。
- 垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。
引用计数 方式
工作原理:跟踪记录每个值被引用的次数。
工作流程:
- 声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值的引用次数就是1。
- 同一个值又被赋值给另一个变量,这个引用类型值的引用次数加1.
- 当包含这个引用类型值的变量又被赋值成另一个值了,那么这个引用类型值的引用次数减1.
- 当引用次数变成0时,说明没办法访问这个值了。
- 当垃圾收集器下一次运行时,它就会释放引用次数是0的值所占的内存。
但是循环引用的时候就会释放不掉内存。循环引用就是对象A中包含另一个指向对象B的指针,B中也包含一个指向A的引用。
因为IE中的BOM、DOM的实现使用了COM,而COM对象使用的垃圾收集机制是引用计数策略。所以会存在循环引用的问题。
解决:手工断开js对象和DOM之间的链接。赋值为null。IE9把DOM和BOM转换成真正的JS对象了,所以避免了这个问题。
什么情况会引起内存泄漏?
虽然有垃圾回收机制但是我们编写代码操作不当还是会造成内存泄漏。
- 意外的全局变量引起的内存泄漏。
原因:全局变量,不会被回收。
解决:使用严格模式避免。
- 闭包引起的内存泄漏
原因:闭包可以维持函数内局部变量,使其得不到释放。
解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。
- 没有清理的DOM元素引用
原因:虽然别的地方删除了,但是对象中还存在对dom的引用
解决:手动删除。
- 被遗忘的定时器或者回调
原因:定时器中有dom的引用,即使dom删除了,但是定时器还在,所以内存中还是有这个dom。
解决:手动删除定时器和dom。
- 子元素存在引用引起的内存泄漏
原因:div中的ul li 得到这个div,会间接引用某个得到的li,那么此时因为div间接引用li,即使li被清空,也还是在内存中,并且只要li不被删除,他的父元素都不会被删除。
解决:手动删除清空。
什么放在内存中?什么不放在内存中?
基本类型是:Undefined/Null/Boolean/Number/String
基本类型的值存在内存中,被保存在栈内存中。从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本。
引用类型:object
引用类型的值是对象,保存在堆内存中。
- 包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象。
- js不允许直接访问内存中的位置,也就是不能直接访问操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。
52、栈和堆的区别
一、堆栈空间分配区别:
1、栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
2、堆(操作系统): 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
二、堆栈缓存方式区别:
1、栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放;
2、堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
三、堆栈数据结构区别:
堆(数据结构):堆可以被看成是一棵树,如:堆排序;
栈(数据结构):一种先进后出的数据结构。
53、高阶函数
高阶函数 是函数操作其它函数,它接收函数作为参数或将函数作为返回值输出。(满足这两个条件之一就是高阶函数)(也就是如果一个函数A,它在接收参数的时候,传递过来的参数是一个B函数,那么A函数就是高阶函数)。接收函数作为参数(参数是函数),返回函数作为输出(返回值是函数)
数组中的map/filter/reduce/sort方法是js的原生高阶函数,也是我们常用函数。
54、函数柯里化
柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
指的是将原来接收两个参数的函数,变成新的接收一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数
z=f(x,y)转换成z=f(x)(y)
// 普通的add函数
function add(x, y) {
return x + y
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
add(1, 2) // 3
curryingAdd(1)(2) // 3
实现多个相加
function add(){
//let args=arguments;//argements是对象而不是数组
//所以需要把它转成数组
let args=Array.prototype.slice.call(arguments);
let inner=function(){
args.push(...arguments);//...展开运算
return inner; //调用递归,实现多个参数相加
};
inner.toString=function(){
return args.reduce((pre,cur)=>pre+cur)
}
return inner;
}
console.log(add(1,3)(2)(2).toString())
55、querySelectorAll()和getelementsByClassName()的区别
querySelectorAll是css3中新增的选择器,兼容IE8及以上,得到的是一个静态列表,它不会对dom结构进行动态查询,也就是说不是实时的。
在js里面getelementsByClassName()选择的标签是动态的,所以是相互映射,在调用时,我们一般使用这种标签,因为可以及时的反映在页面上。document.getElementsByClassName兼容IE9及以上,是动态查询的过程,会随着dom结构的变化,得到的结点列表也会发生变化
56、事件循环
当程序启动时, 一个进程被创建,同时也运行一个线程, 即为主线程,js的运行机制为单线程。js主线程是有一个执行栈的,所有的js代码都会在执行栈里运行。在执行代码过程中:
同步代码放在主进程中直接执行;
异步代码先放到异步队列:如果遇到一些异步代码(比如setTimeout,setInterval,ajax,promise.then以及用户点击等操作等),那么浏览器就会将这些代码放到一个异步进程中去等待,不阻塞主线程的执行,主线程继续执行栈中剩余的代码,当异步进程处理完毕后(比如setTimeout时间到了,ajax请求得到响应),将相应的异步任务(回调函数)放到异步队列中等待执行。
待同步代码执行完步,轮询执行异步队列里的任务:当主线程执行完栈中的所有代码后,它就会检查异步队列是否有任务要执行,如果有任务要执行的话,从队列取出第一个任务队列推到主线程的执行栈中执行。如果当前任务队列为空的话,它就会一直循环查询任务队列等待任务到来。因此,叫做事件循环。
57、定义函数的三种方式
函数声明
函数声明可以先调用,在声明
fn();
function fn(){
console.log(“函数体”)
}
函数表达式就是将一个匿名函数整体赋值给一个变量,就是函数表达式的形式。
函数表达式必须先声明,再调用
var fn = function() {
console.log(“函数体”);
}
fn();
构造函数Function
函数也是对象,可以使用Function构造函数new出来
语法:new Function(arg1,arg2,arg3…,body);
arg是任意参数,字符串类型的。body是函数体。
相当于var fn = function(){}
var fn = new Function();
var fn = new Function(“alert(1111)”);
fn();
var fn1 = new Function(“a”, “b”, “console.log(a*b)”);
fn1(1,2);
58、es6的新用法有哪些
(1)变量声明:由var变为let和const;
(2)模板字符串:使用反引号``;在模板字符串里面支持换行,并可以在里面使用${}来包裹一个变量或表达式;
(3)解构:有数组解构和对象解构;可以快速获取数组和对象的值;
- 展开运算符:在ES6中用…来表示展开运算符,它可以将数组或者对象进行展开;
(5) 箭头函数:函数的快捷写法,不需要通过function关键字创建函数,并且可以省略return关键字.但函数体内的this对象指的是定义时所在的对象,而不是使用时所在的对象;
化简规则:
Function 变成 =>;
1.只有1个参数可以省略小括号;
2.没有参数或者有多个参数不能省略小括号;
3.函数体内只有一行可以省略大括号,如果有返回值return,则return也要省略;
4.函数体内有多行,不能省略大括号;
(6) 对象的简化赋值:对象赋值时如果属性名和变量名一致可以简写
(7) .类的支持:ES6中添加了对类的支持,引入了class关键字。JS本身就是面向对象,ES6中提供的类实际上只是JS原型模式包装。现在有了class,对象的创建,继承更直观,父类方法的调用,实例化,静态方法和构造函数更加形象化
59、JS的几种简单设计模式
一、单例模式
使用构造函数实例化的时候,不管实例化多少回,都实例化出同一个对象
- 一个构造函数一生只能 new 出一个对象
- 当我们使用构造函数,每一次 new 出来的对象 属性/功能/方法 完全一样 的时候,我们把他设计成单例模式
二、组合模式
举一个简单的例子,就像家里每个电器都有单独的开关,而组合模式就是设置一个总开关,这个开关可以控制家中所有电器的开关,这就是组合模式。
实现思想:
先定义控制不同电器的开关,也就是一个个构造函数,每一个构造函数都有一个启动方法
每一个构造函数的实例就是一个个电器开关
再定义一个总开关,一个构造函数,有一个方法可以启动所有构造函数的,这时,需要一个承载所有构造函数实例的数组
总开关构造函数需要一个方法, 向数组里面添加内容
总开关构造函数需要一个方法, 能把数组里面的所有内容启动了
三、观察者模式
观察者观察着被观察者只要被观察者数据发生变化,观察者就要做一些事情。
举个生动的例子,学生就是被观察者,老师就是观察者,只要学生上课状态不好,老师就会请家长。
四、发布/订阅模式
有三种状态:订阅、取消订阅、发布
要实现订阅/取消订阅功能需要一个消息盒子{ }
订阅就是往消息盒子里添加内容
取消订阅就是删除消息盒子里的内容
发布就是执行消息盒子里的内容
60、节流和防抖的区别,以及如何实现
防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。
1、防抖:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。防抖的应用场景,最常见的就是页面滚动条监听的例子。
实现方式:每次触发事件时设置一个延迟调用方法,并且取消之前的延时调用方法
缺点:如果事件在规定的时间间隔内被不断的触发,则调用方法会被不断的延迟
//防抖debounce代码:
function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
// 每当用户输入的时候把前一个 setTimeout clear 掉
clearTimeout(timeout);
// 然后又创建一个新的 setTimeout, 这样就能保证interval 间隔内如果时间持续触发,就不会执行 fn 函数
timeout = setTimeout(() => {
fn.apply(this, arguments);
}, 500);
};
}
// 处理函数
function handle() {
console.log(Math.random());
}
// 滚动事件
window.addEventListener('scroll', debounce(handle));
2节流:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。函数节流应用的实际场景,根据文本框中输入的内容自动请求后台数据
实现方式:每次触发事件时,如果当前有等待执行的延时函数,则直接return
//节流throttle代码:
function throttle(fn) {
let canRun = true; // 通过闭包保存一个标记
return function () {
// 在函数开头判断标记是否为true,不为true则return
if (!canRun) return;
// 立即设置为false
canRun = false;
// 将外部传入的函数的执行放在setTimeout中
setTimeout(() => {
// 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。
// 当定时器没有执行的时候标记永远是false,在开头被return掉
fn.apply(this, arguments);
canRun = true;
}, 500);
};
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));
61、虚拟DOM是如何提升性能?
虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。
具体实现步骤如下:
1、用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中。
2、当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异。
3、把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。