前端面试重点问题

前端面试重点问题

Vue中APP项目

浏览器渲染机制

HTML相关知识

CSS相关知识

Vue相关知识

计算机网络相关知识

数据库设计

数据结构代码

排序算法代码

定时器、防抖、节流的练习

Promise相关知识

正则

高阶函数reduce和forEach

webpack

setTimeout、async、promise 混用的执行顺序

跨域

Generator

git工具

一、前端的优化方式

  • 合并资源,减少HTTP请求
  • 非核心代码异步加载——异步加载的方式——异步加载的区别
  • 利用浏览器的缓存——缓存的分类——缓存的区别
  • 使用DNS预解析
  • 减少DOM操作:事件委托、节流、防抖
  • 使用CDN

二、BOM和DOM
BOM是浏览器对象模型,描述了与浏览器进行交互的方法和接口;

  • window:对应整个浏览器的窗口,同时window也是网页的全局对象
  • location:代表当前浏览器的地址栏信息
  • history:代表浏览器的历史记录,只能操作浏览器向前或者向后翻页;但不能获取具体的历史记录;
  • navigator:代表当前浏览器的信息。通过该对象可以识别不同的浏览器;
  • screen:代表用户屏幕的信息,通过该对象可以获取用户显示器相关的信息。

DOM(document object model):将整个页面规划成由节点层级构成的文档;

三、事件代理(也叫事件委托):

事件代理利用冒泡:指定一个事件处理程序,可以管理某一类型的所有事件;

JS中,添加到页面的事件处理程序数量直接关系到页面的整体运行性能,因为这需要不断的和DOM节点进行交互;访问DOM的次数越多,引起浏览器重绘重排的次数也越多,就会延长整个页面的交互就绪时间;性能优化的主要思想之一就是减少DOM操作;

每个函数都是一个对象,是对象的话,就会占用内存,所以可以使用事件代理,减少操作;

事件委托的优点:
 - 管理的函数数量变少了,节约内存
 - 方便动态添加和修改子元素,不需要因为元素的改动而修改事件绑定;
想要知道具体是哪个DOM元素触发了事件,可以通过event.target
event对象有一个target属性,可以返回事件的目标节点

四、数据类型以及Symbol

Symbol可以创建一个独一无二的值;

var a1 = Symbol("a")
var a2 = Symbol("a")
a1 !== a2   // true

判断数据类型:

typeof()  Array.isArray()  
Object.prototype.toString.call()

typeof()的值有number、Boolean、string、object、undefinedfunction
console.log(typeof NaN);   //'number'
console.log(typeof null);  //'object'
console.log(typeof(class c{}));  //'function'

undefined和null的区别:

  • null代表空值,类型转化时转化为0;表示程序级的、意料之中的值的空缺;
  • undefined代表未初始化的变量,类型转化时转化为NaN,表示系统级的、出乎意料的值的空缺;没有返回值的函数返回undefined,对象中没有赋值的属性,该属性的值为undefined;

五、深拷贝与浅拷贝、赋值三者的区别

深拷贝与浅拷贝只针对Object和Array这样的引用数据类型

  • 深拷贝:另外创造一个一模一样的对象;新旧对象不共享内存
JSON.parse(JSON.stringify(a))
const deepCopy = obj => {
  if(typeof obj !== "object") return
  let dst = obj instanceof Array ? [] : {}
  for(let i in obj) {
    if(obj.hasOwnProperty(i)) {
      dst[i] = typeof obj[i] === "object" ? deepCopy(obj[i]) : obj[i]
    }
  }
  return dst
}
let obj1 = {
  'name' : 'zhangsan', 'age' :  '18',
  'language' : [1,[2,3],[4,5]],
};
let obj2 = deepCopy(obj1)
  • 浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存;当第一层数据为基本数据类型,没有共享一块内存;当第一层数据为引用数据类型,则共享一块内存;
const shallowCopy = obj => {
  if(typeof obj !== "object") return
  const dst = obj instanceof Array ? [] : {}
  for(let i in obj) {
    if(obj.hasOwnProperty(i)) {
      dst[i] = obj[i]
    }
  }
  return dst
}
let obj1 = {
  'name' : 'zhangsan', 'age' :  '18',
  'language' : [1,[2,3],[4,5]],
};
let obj2 = shallowCopy(obj1)
  • 赋值:将对象赋值给一个新的变量;旧变量和新变量指向同一个对象;改变其中一个变量,另外一个变量的值也会发生改变;

六、同步与异步JS

  • JS是一门单线程语言,这样设计的目的:JS是用来操作DOM的,如果是多个线程的设计,一个线程要求该DOM节点增加内容,一个线程要求删除该DOM,就会发生冲突;

  • JS作为单线程语言,同一时间只能执行一个任务,如果遇到读取文件或者ajax请求,就会需要等待较长时间,所以JS划分了同步和异步操作

    • 主线程可以不动等待当前文件的读取或者ajax的加载成功,可以先挂起处于等待中的任务,先运行同步代码,等到文件读取完毕或者ajax加载成功,再回头执行挂起的任务;
  • 同步任务与异步任务

    • 同步任务 :在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行下一个任务;
    • 异步任务:不进入主线程,进入任务队列,只有任务队列通知主线程,某个异步任务才能进入主线程;
  • 任务队列:是一个先进先出的栈结构,当任务队列中的异步任务执行完成了,就会在任务队列中添加一个事件,表示异步任务完成了,该任务可以进入执行栈;

    但这个时候,主线程不一定有空,当主线程处理完其他任务时,就会从任务队列中读取里面有哪些事件,排在前面的事件优先处理;单线程从任务队列中读取事件是不断循环的,每次执行栈被清空后,都会在任务队列中读取新的事件,如果没有,就等到新的事件,这就叫 事件循环

    • 宏任务 :定时器、事件绑定、ajax、IO;
    • 微任务:promise.then/catch//finally、process.nextTick、async await;

七、类的创建

  • 利用function关键字
function Animal(name, age) {
  this.name = name
  this.age = age
  this.setName = function(name) {
    this.name = name
  }
}
const ani = new Animal("大毛", 1)
console.log(ani.name) // 大毛
  • 原型方法
function Animal(name, age) {
  this.name = name
  this.age = age
}
Animal.prototype = {
  getName: function() {  // 注意getName后面一定要用:,不要用=
    return this.name 
  },
  setName: function(name) {
    this.name = name
  }
}
const ani = new Animal("大毛", 1)
ani.setName("小六")
console.log(ani.name) // 小六
  • Object.create()
const Animal = {
  name: "大毛",
  getName: function() { return this.name }
}
const ani = Object.create(Animal)
console.log(ani.getName()) // 大毛

八、类的继承

  • 原型链继承:将父类实例作为子类的原型
    A instanceof B:判断A是否是B的实例对象或者B的子类的实例对象;

原型链继承的优点:可以继承父类实例的属性和方法,也可以继承父类原型属性和方法;
原型链继承的缺点:原型对象的所有属性被所有实例共享;无法实现多继承;无法向父类构造函数传参。

// 父类Animal
function Animal(name) {
  this.name = name
}
Animal.prototype.eat = function(food) {
  console.log(this.name + "正在吃:" + food)
}
// 子类Cat
function Cat() {}
Cat.prototype = new Animal()
// 此时Cat.prototype.constructor此刻还指向Animal
Cat.prototype.constructor = Cat
Cat.prototype.name = "cat"
const cat = new Cat()
console.log(cat.name) // cat
console.log(cat instanceof Animal) // true
console.log(cat instanceof Cat) // true
  • 构造继承:通过调用父类构造函数,继承父类的属性

优点:可以实现多继承,可以向父类传参;
缺点:只能继承父类实例的属性和方法,不能继承父类原型上的属性和方法;
注意:实例对象只是子类的实例,不是父类的实例。
function Animal(name) {
this.name = name
}

function Cat(name) {
  Animal.call(this, name)
  // Cat.prototype.__proto__ = Animal.prototype
}
const cat = new Cat("cat")
console.log(cat.name) // cat
console.log(cat instanceof Animal) // false
console.log(cat instanceof Cat) // true
console.log(Cat.prototype.constructor)
// [Function: Cat]

九、call、apply区别

call传参是一个一个传参;apply传参传入的是一个数组;

Person.call(this, name, age)
Person.apply(this, [name, age])
Person.apply(this, arguments) // 和上句一样的效果

十、class类的继承

class People {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  sayHi() {
    console.log(`hello,我是${this.name}`+
      `今年${this.age}岁了。`)
  }
}

let p = new People("柴勇", 19)
p.sayHi() // hello,我是柴勇,今年19岁了。
class Teacher extends People {
  constructor(name, age, subject) {
    super(name, age)
    this.subject = subject
  }
  attendClass() {
    console.log(`${this.name}给学生们上:`+
    `${this.subject}`)
  }
  sayHi() {
    console.log(`${this.name}说她累了,`+
      `不想上${this.subject}课了`)
  }
}
let teacher = new Teacher("王芳", 14, "数学")
teacher.attendClass() // 王芳给学生们上:数学
teacher.sayHi() // 王芳说她累了,不想上数学课了

十一、数组原生的方法

pop()删除末尾元素
push()在末尾处添加元素
shift()删除开头元素
unshift()在开头处添加元素
slice
includes()判断字符串中是否出现某个子字符串 / 数组中是否出现某个元素
str.includes(substr),返回true或者false
arr.includes(item),返回true或者false

slice、splice、concat
slice(a, b)不改变原数组,返回新数组
splice改变原数组
concat不改变原数组

sort()排序
reverse()换序

filter() 不改变原数组,返回数组中满足条件的元素,返回一个数组
find() 不改变原数组返回数组中满足条件的第一个元素
some() 只要有一个元素满足条件,则返回true
every() 所有元素满足条件,返回true
let res = obj.filter(ele => !ele["num"])
// forEach没有返回值,改变原数组
const numbers=[1, 2, 3, 4, 5]
numbers.forEach((num, index) => {
  return numbers[index] = num + 2
});
console.log(numbers)
// [ 3, 4, 5, 6, 7 ]
// map有返回值,可以不改变原数组
const numbers=[1, 2, 3, 4, 5]
const newNumbers = numbers.map(num => {
  return num = num + 2
});
console.log(numbers)
// [ 1, 2, 3, 4, 5 ]
console.log(newNumbers)
// [ 3, 4, 5, 6, 7 ]
reduce去重、查看每个元素出现的次数、累加、累乘

十二、字符串的方法

toString()将其他类型的数据转化为字符串类型
const number = 1337
let a = number.toString()
console.log(typeof a)  // string
console.log(typeof number)   // number
str.indexOf(substr, index)判断str中从index位置开始,是否包括substr?
如果包括,判断substr首次出现的位置;不包括,返回-1
// subString:从字符串中提取子字符串
str.subString(3)	从第3个到最后
str.subString(3, 7)	从第3个到第7个,不包括第7
includes:判断字符串中是否出现某个子字符串
也可判断数组中是否出现某个元素
str.includes(substr),返回true或者false
arr.includes(item),返回true或者false

十三、重绘、重排

当发生DOM节点的删除,修改,引起页面布局的变化,会发生重排;DOM非几何属性,比如颜色,字体大小变化,不引起布局的变化,会发生重绘;

减少重绘和重排的次数,需要用到虚拟DOM树;

不是每次DOM发生变化,都要进行重绘重排,可以将变化存储下来,比如累积到10次,可以和虚拟DOM进行比较,发现有什么不同之处,再去修改不同之处;这里与虚拟DOM进行比较会用到diff算法

Diff算法:

若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象一次性attch到DOM树上,再进行后续操作,避免大量无谓的计算量。所以,用JS对象模拟DOM节点的好处是,页面的更新可以先全部反映在JS对象(虚拟DOM)上,操作内存中的JS对象的速度显然要更快,等更新完成后,再将最终的JS对象映射成真实的DOM,交由浏览器去绘制。

修改某个数据,如果直接渲染到真实DOM上会引起整个DOM树的重绘和重排,有没有可能只更新修改的一小块DOM,而不更新整个DOM;diff算法可以帮助我们;

虚拟DOM对应的是真实DOM,使用document.CreateElement和document.CreateTextNode创建的就是真实节点;

// body下的 <div id="v" class="classA"><div> 
// 对应的 oldVnode 
{
  el: div, //对真实的节点的引用,本例中就是document.querySelector('#id.classA')
  tagName: 'DIV',  //节点的标签
  sel: 'div#v.classA', //节点的选择器
  data: null, // 一个存储节点属性的对象,对应节点的el[prop]属性,例如onclick , style
  children: [], //存储子节点的数组,每个子节点也是vnode结构
  text: null, //如果是文本节点,对应文本节点的textContent,否则为null
}

一定要注意: 比较新旧节点时,只在同层级之间进行比较,不会跨层级比较;

先根据真实DOM生成一颗虚拟DOM,当虚拟DOM某个节点的数据改变后:

  • 发生改变的DOM节点会生成一个新的虚拟节点Vnode,将其与原来的虚拟节点OldVnode进行比较,比较时会用到patch函数;
  • 如果两者不值得比较,也就是sel值和key值不一样,则用Vnode代替OldVnode;具体做法: 在OldVnode前面插入Vnode,之后将oldVnode删掉,然后将Vnode对应到真实节点上;

如果值得比较,则执行VnodePatch函数:

  • 判断两者是否指向同一个对象,如果是;则return
  • 如果不是指向同一个对象,则判断Vnode是否有text属性,也就是是否有文本节点;如果有,则将el的文本节点(innerText)设置为Vnode的text值;
  • 如果没有text属性值,则判断是否有子节点:
    • oldVnode没有子节点,Vnode有子节点,则增加子节点到真实DOM上;
    • oldVnode有子节点,Vnode没有子节点,则删除真实DOM上的子节点;
    • 如果两者都有子节点,则进行下一步的比较
  • 给oldVnode和Vnode分别加两根指针,oldVnode的oldStart指针指向其第一个子节点;oldEnd指针指向其最后一个子节点;Vnode的Start指针指向其第一个子节点;End指针指向其最后一个子节点;
  • 有四种匹配方式:oldEnd和Start是其中一种,如果两者匹配成功(也就是说两个子节点内容一样),则将oldEnd指向的子节点移动到第一个位置,之后将指针向中间移动;继续操作
  • 如果没有匹配成功,再匹配其他方式;如果四种匹配方式都没有匹配成功,则遍历oldVnode的子节点,将其与start指针指向的子节点进行对比,匹配成功的话,则把对应的子节点移动到第一个位置;

十四、eval的用法

// eval可以求解
let res = eval("3+2+4/2")
console.log(res) // 7

let b = eval("var a = 3")
console.log("b:"+ b) // b:undefined
console.log("a:"+ a) // a:3

十五、执行上下文栈

执行上下文:当前JS代码被解析和执行所在环境的抽象的概念;JS的任何代码都是在执行上下文中运行的(执行环境);

执行栈: 存储在代码执行期间创建的所有的执行上下文;

JS执行上下文分为三种:

  • 全局执行上下文
  • 函数执行上下文
  • Eval函数执行上下文:eval(alert(1))
    • ’use strict’;var x = 1;eval(“var x = 2;”); console.log(x);
    • 严格模式为1,非严格模式为2;
    • 严格模式执行eval函数,不会作用于它的外层作用域,所以修改x不会生效
      在这里插入图片描述JS引擎执行这段代码:
  • 首先创建一个新的全局上下文并将这个全局上下文推入当前的执行栈中;
  • 这个时候调用了one()函数,JS引擎为one()函数创建一个新的函数执行上下文,并将其推入执行栈的栈顶;
  • one()函数调用了two()函数,JS引擎为two()函数创建一个新的函数执行上下文,并将其推入执行栈的栈顶;
  • two()函数执行完,它的执行上下文从当前执行栈弹出;执行上下文的控制权交给当前执行栈中的下一个执行上下文(也就是one()函数所在的执行上下文);
  • one()函数执行完,将one函数所在的执行上下文移出栈;
  • 所有代码执行完,将全局执行上下文移出栈。

十六、this指向

this作为一个特殊的对象,是一个关键字,作为一个指针,在不同的情况下,指向不同的位置。

// 函数直接调用,this指向window,比如myfunc()
console.log(this) // {}   指向全局
function myfunc() {
  console.log(this)  // Object [global] this是window
}
myfunc();
// 函数被其他对象调用,this指向对象,比如obj.e()
var a = 1;
var obj = {
  i: 10,
  b: () => console.log(this.i, this), // undefined {}
  c: function() {
    console.log(a); // undefined  先在函数作用域内找
    var a = 2;
  },
  e: function() {
    console.log(this.i, this)
    // 10 { i: 10, b: [Function: b], c: [Function: c], e: [Function: e] }
  }
}

obj.b()   //  undefined {}
obj.c()  // undefined
obj.e()
// 10 { i: 10, b: [Function: b], c: [Function: c], e: [Function: e] }
// new一个实例,this指向实例
function Person(name) {
  this.name = name;
  console.log(this); // this是指实例p
  // Person { name: 'zhaowa' }
}
var p = new Person('zhaowa');
var obj = {
  d: function() {
    console.log(0);
    setTimeout(_ => console.log(1), 0);
    new Promise (resolve => {
      console.log(2);
      // 这里并没有调用resolve函数,所以不会执行.then函数
      // 因此不打印3
    }).then(_ => {
      console.log(3)
    })
    console.log(4);
  }
}
obj.d()
// 0 2 4 1
function getColor(color, hand) {
  this.color = color;
  this.hand = hand
  console.log(this)
}
function Car(name, color, hand){
  this.name = name; 
  // this指的是实例car
  getColor.apply(this, [color, hand]); 
  getColor.call(this, color, hand); 
  // 这里的this从原本的getColor,变成了car
}
const car = new Car("跑车", "紫色", "小手")
// Car { name: '跑车', color: '紫色', hand: '小手' }
// 队列中
var a = {
  myfunc: function () {
    setTimeout(function () {
      console.log(this); // this是 window
    }, 0)
  }
};
a.myfunc()

// 箭头函数
var a = {
  myfunc: function () {
    setTimeout(() => {
      console.log(this); // this是对象a
    }, 0)
  }
};
a.myfunc()

// 队列中
var a = {
  myfunc: function() {
      var that = this;
      setTimeout(function(){
        console.log(that); // this是a
        // { myfunc: [Function: myfunc] }
      }, 0)
      }
  };
a.myfunc()

这里要注意:

var canvas = {
  render: function() {
    this.update();
    this.draw();
  },
  update: function() {
    // ...
  },
  draw: function() {
    // ...
  }
};
window.setInterval(canvas.render, 1000 );
// 这样的写法是不对的,render方法中的this其实被指向了window;
// 可以使用bind,显示地把this绑定到回调函数,以便继续使用该对象;
window.setInterval(canvas.render.bind(canvas), 1000);

十七、bind函数

bind函数:只有函数对象才有bind、apply、call
bind函数会返回一个新的函数,新函数的this指向bind()第一个参数

var _fn = fn.bind(null, 10);
var ans = _fn(20, 30); // 60
console.log(ans)  // 10 + 20 + 30 
// 解释:fn函数需要三个参数,_fn函数将10作为默认的第一个参数,
// 所以只需传入两个参数即可;如果传入三个参数,也只会取前两个
function Person(num, num2, name, age) {
  this.num = num
  this.num2 = num2
  this.name = name;
  this.age = age;
  console.log(num + num2)
}
// new 操作符调用绑定函数时,bind 的第一个参数无效。
var _Person = Person.bind(null, 10);
var p = new _Person(20, 40, 'hanzichi', 30); 
console.log(p)
// 30
// Person { num: 10, num2: 20, name: 40, age: 'hanzichi' }

十八、ajax底层

  • 创建XMLHttpRequest异步对象
  • 设置回调函数
  • 使用open方法与服务器建立连接
  • 向服务器发送请求
  • 在回调函数中针对不同的响应状态进行处理
// 第一步
var obj = new XMLHttpRequest()
// 第二步
obj.onreadystatechange = callback
// 第三步
obj.open("get", "http://www.baidu.com", true)
// true表示异步
obj.open("post", "http://www.baidu.com", true)
obj.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
// 第四步
get不需要传递参数:obj.send(null)
post需要传递参数:obj.send("name=jay&age=18")
// 第五步
function callback() { 
    // 判断异步对象的状态 
    if(obj.readyState == 4) { 
        // 判断交互是否成功 
        if(obj.status == 200) { 
            // 获取服务器响应的数据 
            var res = obj.responseText
             // 解析数据 
             res = JSON.parse(res) 
         } 
     } 
 }
 var str = JSON.stringify(obj)  // 将JSON对象转化为JSON字符串
 var obj = JSON.parse(str);  // 由JSON字符串转换为JSON对象

readyState存有XMLHttpRequest的状态;从0到4的变化

readyState代表含义
0请求未初始化
1服务器连接已建立
2请求已接收
3请求处理中
4请求已完成,响应已就绪
  • 每当readyState发生变化的时候,就会执行onreadystatechange函数

  • content-type:定义网络文件的类型和网页的编码,决定浏览器将以什么方式或者什么编码读取这个文件,content-type表头告诉客户端实际返回的内容的类型;
    在这里插入图片描述
    编码类型:

  • UTF-8:使用1-4个字节表示一个符号(使用最广

  • GBK:表示汉字,支持一万多个汉字编码

  • ASCII:一共规定了128个字符的编码,8个二进制位可以组合256种状态,ASCII码只占用前7位就可以,最后一位统一规定为0;

  • Unicode字符集:包括了世界上所有的字符,是一个字符集

Vue的ajax请求:

// main.js文件:
import axios from 'axios'
Vue.prototype.$http = axios
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'

// 其他组件:
async getParentCategory() {
  const { data: res } = await this.$http.get('categories', {
    params: { type: 2 }
  })
  if (res.meta.status !== 200) {
    return this.$message.error('获取父类失败')
  }
  this.parentCateList = res.data
}

十九、JS加载过程阻塞的解决办法

默认情况下,浏览器同步加载JS脚本,渲染引擎遇到

如果脚本体积很大,下载和执行的时间很长,就会造成浏览器堵塞,用户会感觉浏览器“卡死”,没有响应;所以可以采取脚本异步加载的方法。

<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

在上面的代码中,

defer和async的区别:

  • defer要等到整个页面在内存中正常渲染结束(DOM结构完成生成,其他脚本执行完成)才会执行;
  • async:一旦下载完,渲染引擎就会中断渲染,执行这个脚本后,再继续渲染;
  • 总结:defer渲染完再执行,async下载完就执行;有多个defer脚本的话,会按照它们在页面中出现的顺序加载,而多个async脚本是不能保证加载顺序的;
  • 没有defer或者async的话,浏览器会立即下载并执行相应的脚本,并且在下载和执行时页面的处理会停止。

二十、原型链

  • 所有函数都有一个prototype属性,属性值是一个普通的对象,这个prototype对象自带一个constructor属性,该属性指向构造函数;
  • 构造函数实例化一个对象,该对象的__proto__指向构造函数的prototype
  • 非构造函数实例化的对象或者对象的prototype的__proto__指向Object.prototype
  • 所有函数的__proto__指向Function.prototype
  • Object.prototype的__proto__指向null

二十一、click在IOS上有300ms延迟?

原因:用户点击一次屏幕后,浏览器不能立刻判断是单击操作还是双击缩放;

解决办法:

  • 粗暴型:禁止缩放;
  • fastCLick:检测到touch事件后,立刻模拟click事件;

二十二、Doctype

声明于文档最前面,告诉浏览器以何种方式来渲染页面:严格模式 or 混杂模式?

  • 严格模式:以浏览器支持的最高标准运行
  • 混杂模式:向下兼容,可以模拟老式浏览器

二十一、HTML渲染,字节流,字符流
二十二、轮播图

二十三、箭头函数与普通函数的区别

  • 箭头函数没有具名函数
// 普通函数的具名函数
function func() {
  // code
}
// 普通函数的匿名函数
let func = function() {
  // code
}
// 箭头函数全是匿名函数
let func = () => {
  // code
}
  • 箭头函数不能用于构造函数,不能使用new
function Person(name, age) {
  this.name = name;
  this.age = age;
}

let person = new Person('柴勇', 22)
console.log(person.name, person.age) 
// 柴勇 22
  • 箭头函数中this指向不同:

普通函数中,this指向调用它的对象;如果用作构造函数,this指向创建的对象实例;

箭头函数本身不创建this,但它在声明时可以捕获其所在上下文的this供自己使用;this一旦被捕获,就不再发生变化;

此案例中,箭头函数在全局作用域声明,所以它捕获全局作用域中的this,this指向window对象。

var webName = "捕获成功";
let func = () => {
  console.log(this.webName);
}
func(); // 捕获成功

wrap()用作构造函数;使用new调用wrap()后,此函数作用域中的this指向创建的实例化对象;
箭头函数此时被声明,捕获这个this;

var name = "恩诺1";
function wrap(){
  this.name="恩诺2";
  let func = () => {
    console.log(this.name);
  }
  func();
}
let en = new wrap(); // 恩诺2
  • 箭头函数不绑定arguments,取而代之用rest参数…解决

每一个普通函数调用后都具有一个arguments对象,用来存储实际传递的参数。但是箭头函数并没有此对象。

function A(a) {
  console.log(arguments);
}
A(1,2,3,4,5,8);  
// [Arguments] { '0': 1, '1': 2, '2': 3, 
// '3': 4, '4': 5, '5': 8 }

let B = (b) => {
  console.log(arguments);
}
B(2,92,32,32);   
// Uncaught ReferenceError: arguments is not defined

let C = (...c) => {
  console.log(c);
}
C(3,82,32,11323);
// [ 3, 82, 32, 11323 ]
  • 箭头函数没有prototype原型对象

二十四、闭包函数

闭包指有权访问另外一个函数作用域中的变量的函数,在JS中,只有函数内部的子函数才能够读取局部变量,所以闭包可以理解为“定义在一个函数内部的函数”。本质上,闭包可以将函数内部和函数外部连接起来。

  • 为什么要用闭包?

1)有的函数只需要执行一次,其内部变量无需维护,可以使用闭包。 2)闭包不会释放外部的引用,从而函数内部的值可以得以保留。

闭包作用:

  • 可以访问到另一个函数的作用域,读取函数内部变量;闭包可以访问到父级函数的变量,且该变量不会被销毁。
  • 保护全局变量,避免全局污染;
  • 封装私有变量;
  • 模仿块级作用域(立即执行)(闭包可以访问外层函数作用域的变量)
    块级作用域:每次迭代都会创建这样一个作用域;

闭包缺点:

  • 导致变量不会被垃圾回收机制回收,造成内存泄漏;
var a = 10;
function Add() {
  var a = 10;
  return function() {
    a++
    return a
  }
}
var c = Add()
console.log(c()) // 11
console.log(c()) // 12
console.log(c()) // 13
console.log(a)  // 10

var b = Add()
console.log(b()) // 11

二十五、let和const的区别

const a = 3
a = 4
console.log(a) // TypeError: Assignment to constant variable.

const b = [1, 2, 3]
b = 4
console.log(b) // // TypeError: Assignment to constant variable.

const b = [1, 2, 3]
b[2] = 4
console.log(b) // [ 1, 2, 4 ]

let a = 3
a = 4
console.log(a) // 4

let b = [1, 2, 3]
b = 4
console.log(b) // 4

二十六、垃圾回收机制

  • 标记清理: 变量进入上下文,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文的标记;变量离开上下文时,会被加上离开上下文的标记;
  • 引用计数: 对每个值都记录它被引用的次数;声明变量并给它赋一个引用值时,这个值的引用数为1;如果同一个值又被赋给另外一个变量,那么引用数+1;类似地,如果对该值引用的变量被其他值覆盖了,那么引用数 - 1;当一个值的引用数为0,就进行垃圾回收。
    引用计数有个缺点,如果函数中有定义的变量,函数被多次调用,就会有大量内存永远不会被释放;
  • 使用let,const提升性能:let和const都以块为作用域,相比于使用var,使用这两个关键字可能会更早地让垃圾回收机制回收程序介入;
  • 新生代和老生代:
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值