1、同步和异步的区别
执行顺序不同。同步任务指在主线程上排队进行的任务,只有当前面的任务执行完才会执行下一个任务。异步任务不进入主线程,而是进入任务队列,只有等主线程任务执行完毕,任务队列开始通知主线程,请求执行任务,才会进入主线程。
2、预编译
预编译时候做了哪些事情?
1、创建ao对象
2、找形参和变量声明,作为ao对象的属性名,值为undefined
3、形参和实参相统一
4、找函数声明会替换变量声明
function fn(a,c){
console.log(a);//function a(){};
var a = 123;
console.log(a);//123
console.log(c);//function c(){};
function a(){};//函数声明
if(false){
var d = 678;
}
console.log(d);//undefined
console.log(b);//undefined
var b = function(){};//函数的表达式而不是函数声明
console.log(b);//function(){};
function c(){};//函数声明
console.log(c);//function c(){};
}
fn(1,2)
ao{
a: undefined 1 function a(){};
c: undefined 2 function c(){};
d: undefined
b: undefined
}
3、this指向问题
题目:
var name = 222;
var a = {
name:111,
say:function(){
console.log(this.name)
}
}
var fun =a.say;
fun();
a.say();
var b = {
name:333,
say:function(fun){
fun();
}
}
b.say(a.say);
b.say = a.say;
b.say();
1、*在函数中直接使用this
//在函数中直接使用this
function get(content){
console.log(content);
}
get("你好")
//等同于 get.call(window,"你好")
//函数即对象,有对象就有方法
2、*函数作为对象的方法被调用(谁调用我,我就指向谁)
var person = {
name:"张三",
run:function(time){
console.log(`${this.name}在跑步,最多${time}分就不行了`)}
}
person.run(30);
//等同于person.run.call(person,30)
题目:
var name = 222;
var a = {
name:111,
say:function(){
console.log(this.name);
}
}
var fun = a.say;
//var fun = function(){
//console.log(this.name);
//}
fun();//fun.call(window); 222
a.say();//a.say.call(a);111
var b= {
name:333,
say:function(fun){
fun();
}
}
b.say(a.say);//fun() fun.call(window) 222
b.say = a.say;
//var b= {
//name:333,
//say:function(){
// console.log(this.name);
//}
//}
b.say();//b.say.call(b) 333
箭头函数的this:
箭头函数没有自己的this,导致内部的this就是外层代码块的this,即继承自父执行上下文中的this。所以不能用作构造函数。
var name = 222;
var objj={
name:111,
say:()=>{console.log(this.name)
}
};
objj.say();//222
var name = 222;
var objj={
name:111,
say:function(){
var a = ()=>{
console.log(this.name);
}
return a();
}
};
objj.say();//111
4、深拷贝、浅拷贝、赋值的区别
浅拷贝:重新在堆中创建内存,如果属性是基本类型拷贝的是基本类型的值,如果属性是引用类型拷贝的是内存地址(改变新数据会影响老数据)。
var person={name:"张三",hobby:['学习',['唱歌','跳舞'],'shopping']}
function shallow(obj){
var target = {};//重新在堆中创建内存
for (var i in obj){
if(obj.hasOwnProperty(i)){
target[i] = obj[i]
}
}
return target;
}
var person1 = shallow(person);
console.log(person);
person1.name = "李四";//拷贝前后基本数据类型互不影响
person1.hobby[0] = "玩耍";//引用类型共用一块内存互相影响
console.log(person1);
//{hobby: ["玩耍", ['唱歌','跳舞'], "shopping"],
//name: "张三"}
//{hobby: (3) ["玩耍", ['唱歌','跳舞'], "shopping"],
//name: "李四"}
深拷贝:将一个对象从内存中完整的拷贝出来,从堆内存中开辟一个新区域存放新对象(改变新数据不会影响老数据)。
var person={name:"张三",hobby:['学习',['唱歌','跳舞'],'shopping']};
let isArray =(arr)=> {
return Object.prototype.toString.call(arr) === "[object Array]"
}
let isObject = (obj)=>{
return typeof obj === "object" && obj !==null
}
function deepClone(obj){
var target = isArray(obj)?[]:{};
for (var i in obj){
if(isObject(obj[i])){
target[i] = deepClone(obj[i])
}else{
target[i] = obj[i]
}
}
return target;
}
赋值:当把一个对象赋值给一个新变量的时候,赋的是该对象在栈中的地址。而不是堆中的数据,两个对象指向的是同一个存储空间。因此两个对象是联动的。
var person={name:"张三",hobby:['学习',['唱歌','跳舞'],'shopping']};
var person1 =person;
person1.name = "李四"
console.log(person);
//{name: "李四", hobby: Array(3)}
console.log(person1);
//{name: "李四", hobby: Array(3)}
5、防抖
是闭包的实际应用
防抖函数:当持续触发的事件,一定时间内没有触发该事件,事件函数才会执行一次。
<div>
<input id="input"/>
</div>
<script>
function keyUp(callback){
let timer;
return function(value,delay){//闭包
clearTimeout(timer)
//清除以前触发的定时器
timer = setTimeout(
function(){
callback(value)
}
,delay)
}
}
function fn(value){
console.log(value)
}
var input =document.getElementById('input')
var TimeOut = keyUp(fn);
input.addEventListener('keyup',function(e){
TimeOut(e.target.value,1000)
})
</script>
6、节流
当持续触发事件的时候,保证一段时间内,事件函数只执行一次。
案例:当鼠标持续点击按钮,在n秒内有效点击只有一次。
7、作用域链
会被保存到一个隐式的属性 scope 中,这个属性用户访问不到,但却真实存在,让js引擎来访问,里面存储的就是作用域链。 AO 和 GO的集合。
8、闭包
1、函数嵌套函数
2、函数外部能读取函数内部的值
3、参数和变量不会被垃圾回收机制回收
优点:变量会长期存在内存中
缺点:造成内存泄漏
闭包的实际应用:
setTimeout传递第一个函数是不能带参数,闭包可以解决这个问题。
function f1(a){
return function f2(){
console.log(a)
}
}
var f3 = f1(1);
setTimeout(f3,1000);
9、split、splice、slice区别
split:根据特定字符分割字符串,返回生成的数组。
splice:删除、插入、替换,改变原数组
slice:数组截取,不改变原数组。
10、js运行机制
单线程。同一时间做同一件事。
11、arguments的对象是什么
类数组对象,有length,但没有数组的方法。
类数组转化为数组? es6展开运算符 Array.prototype.slice.call(arguments)
箭头函数是没有arguments的
12、为什么b会变成全局变量
function get(){
let a=b=0
//let a=(b=0)
//赋值从右往左,但b在函数里又没有被定义,所以是个全局变量
}
get();
13、哪些操作会造成内存泄漏?
1、闭包
2、被遗忘的定时器
3、意外的全局变量
4、脱离dom的引用(虽然dom被清除但仍在内存当中,document.getElementById(’’))
14、什么是高阶函数?
将函数作为参数或者返回值的函数
15、手写Array.prototype.map
function map(arr,mapCallback){
if(!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function' ){
return []
}else{
var result = [];
for(var i=0;i<arr.length;i++){
result.push(mapCallback(arr[i],i,arr))
//mapCallback回调函数
}
}
return result;
}
console.log(map([1,2,3,4],(item)=>{return item+1}))
16.event-loop
事件循环由三部分组成
调用栈 微任务队列 消息队列
主任务>微任务>消息队列
js中的宏任务一般是:包括整体代码script,setTimeout,setInterval。
微任务是:Promise.then,process.nextTick。
async与await与Promise:
async-await 是Promise和generator的语法糖。为了让代码书写流畅,增加了可读性。
若async有返回值,相当于Promise.resolve(123)
async function demo01(){
return 123;
}
demo01.then(val=>{console.log(val)})
//123
若没有return相当于执行了Promise.resolve()
async function demo01(){
console.log(123);
}
demo01.then(val=>{console.log(val)})
//123
//undefined
await必须在async内部使用
await可以使用任何js表达式,但主要意图是等待Promise被resolved。
如果await的是Promise对象,则先执行async之外的代码(注意:Promise内的同步代码会先执行。)
如果await的是一个表达式,则会阻塞await后面的代码,先执行async外面的代码,主线程执行完毕后会再来执行await后面的代码。
17、js单例模式
解决的问题:一个全局使用的类,频繁的创建和销毁
优点:内存只有一个实例,减少了内存的开销
使用场景: 弹窗 全局缓存
var login = (function(){
var div
div = document.createElement("div")
div.innerHTML="我是登录弹窗";
div.style.display = "none";
document.body.appendChild(div);
return div;
})()
document.getElementById('btn').onclick = function(){
login.style.display="block"
}
缺点:资源的浪费,一进入页面就创建
var createLogin = (function(){
var div
return function(){
if(!div){
div = document.createElement("div")
div.innerHTML="我是登录弹窗";
div.style.display = "none";
document.body.appendChild(div);
}
return div;
}
return div;
})()
document.getElementById('btn').onclick = function(){
var login = createLogin();
login.style.display="block"
}
class:es5是没有类的,es6的class是构造函数的语法糖,静态方法static(关键字),静态方法不能被实例调用。用class和static结合实现单例模式。
18、js策略模式
定义一系列算法 并且封装起来 他们之间可以相互替换
核心:将算法的使用和算法的实现分离开
例:年终奖,S的人四倍工资,A的人三倍工资,B的人二倍工资
var strategies = {
"S":function(salary){
return salary*4
},
"A":function(salary){
return salary*3
},
"B":function(salary){
return salary*2
}
}
var getSalary = function(strategy,salary){
return strategies[strategy](salary)
}
console.log(getSalary("A",1000))
19、数组扁平化
将一个多维数组变成一维数组。
方法一、flat
arr.flat(Infinity)Infinity是正无穷,不管数组包裹多少层
方法二、递归
var res5 = [];
const fn = arr=>{
for (var i = 0; i<arr.length;i++){
if(Array.isArray(arr[i])){
fn(arr[i])
}else{
res5.push(arr[i])
}
}
}
fn(arr);
20、BFC
理解:块级格式化上下文,指一个独立的块级渲染区域
如何创建BFC
1、float的值不是none
2、position的值不是position和static
3、display的值是inline-block、flex、inline-flex
4、overflow:hidden
作用:
1、阻止塌陷
2、阻止元素被浮动元素覆盖
21、js继承的几种方法
1、原型链继承
原来:子类的原型对象指向父类的实例,当子类实例找不到对应的属性和方法时会沿着原型链往上找。
function Parent(){
this.name = ["aaa"]
}
Parent.prototype.getName = function(){
return this.name;
}
function Child(){
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child1 = new Child();
//const child2 = new Child();
//child1.name = ['bbb']
console.log(child1.name)
//console.log(child2.name)
缺点:多个实例指向同一原型,多个实例的值会互相影响。2、无法对父类进行传参。(无法实现super功能)
2、构造函数继承
在子类的构造函数中去执行父类的构造函数,并且为其绑定子类的this,也就是说把父类的属性方法挂到子类上去
function Parent(name){
this.name = [name];
}
Parent.prototype.getName=function(){
return this.name;
}
function Child(){
Parent.call(this,"aaa")
}
const child1 = new Child();
const child2 = new Child();
child1.name = ['bbb']
console.log(child1.name)
console.log(child2.name)
console.log(child2.getName())//报错
缺点:并不能够继承父类原型上的方法和属性
3、组合式继承
缺点:每次创建实例的时候都要new Parent()
4、寄生式继承
特点:子类的prototype指向父类的prototype
缺点:对子类原型的操作会影响到父类的原型
解决方法:拷贝,Object.create(Parent.prototype)(寄生组合式继承)
22、创建对象的几种方法
1、new Oject创建对象
2、字面式创建对象
缺点:这两种用的都是同一个接口,会产生大量重复的代码。
3、工厂模式
function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alter(this.name)
}
return o;
}
let person = createPerson('mm',19,'active')
缺点:返回的是一个对象,无法判断返回的对象是什么类型
4、构造函数创建对象
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person('Nike',29,'teacher');
var person2 = new Person('Arvin',20,'student');
对比工厂模式
1、没有显示的创建对象
2、直接将属性和方法赋值给this
3、没有return语句
4、可以识别对象的类型
缺点:每个方法都要在实例上重新创建一遍,占内存。
5、原型创建对象
function Person(){}
Person.prototype.name = 'Nike';
Person.prototype.age = 20;
Person.prototype.jbo = 'teacher';
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name ='Greg';
alert(person1.name); //'Greg' --来自实例
alert(person2.name); //'Nike' --来自原型
当为对象添加一个属性时,就会屏蔽原型中的同名属性。
6、组合模式(构造函数模式和原型模式)
23、+=与=+的区别
表达式 A=+B 会进行 B转化为数字 赋值给A
表达式 A+=B 会进行 A = A + B; 也就是js的加法运算
24、三次握手 四次挥手
握手:
1、客户端准备和服务器端进行连接
2、服务器端接收响应
3、客户端收到响应
挥手:
1、客户端已经接近结尾准备和服务器端断开
2、服务器端已经知道客户端没话说了
3、客户端等待服务器端反馈
4、客户端收到,双方确认关闭。