问:什么是变量提升?为什么存在变量提升
问:变量和函数怎么进行提升的? 优先级是怎么样的?
问:var、let、const 三者的区别是什么
map(function(element,index,arr), thisValue)
传入一个函数,该函数会遍历数组,对每一个元素做变换之后,返回一个新数组
let arr = [2, 3, 4]
let arr1 = arr.map(function (element, index, arr) {
return arr[index] + 1
})
let arr2 = arr.map(function (element, index, arr) {
return element + 1
})
console.log(arr); // [2, 3, 4]
console.log(arr1); // [3, 4, 5]
console.log(arr2); // [3, 4, 5]
filter(function(currentValue,index,arr), thisValue)
传入一个函数,函数返回值为布尔类型,将返回值为真的元素放入新数组,返回这个新数组
let arr = [1, 2, 3, 4]
let arr1 = arr.filter(function(element) {
return element < 3
})
console.log(arr); // [1, 2, 3, 4]
console.log(arr1); // [1, 2]
reduce(function(total, element, index, arr), initialValue)
传入一个函数,返回一个值
total: 累计值(第一次的值代表初始化的值)
element: 对应数组的每个元素
index: 数组元素的下标
arr: 原数组
initialValue: 可选。传递给函数的初始值
let arr = [1, 2, 3]
let sum = arr.reduce(function(acc, element) {
return acc + element
}, 1)
console.log(arr); // [1, 2, 3]
console.log(sum); // 7
forEach(function(element, index, arr), thisValue)
传入一个函数,直接操作原数组 没有返回值
let arr = [1, 3, 5]
let arr1 = arr.forEach(function(element, index, arr) {
arr[index] = element+1
})
console.log(arr); // [2, 4, 6]
console.log(arr1); // undefined
箭头函数和普通函数的区别?箭头函数可以当做构造函数 new 吗?
箭头函数是普通函数的简写,可以更优雅的定义一个函数,和普通函数相比,有以下几点差异:
对象调用,this指向该对象(谁调用this就指向谁)
var obj = {
name:'小鹿',
age: '21',
print: function(){
console.log(this)
console.log(this.name + ':' + this.age)
}
}// 通过对象的方式调用函数 obj.print(); // this 指向 obj
直接调用的函数,this指向的是全局window对象
function print(){
console.log(this);
}// 全局调用函数
print(); // this 指向 window
通过new的方式,this永远指向新创建的对象
function Person(name, age){
this.name = name;
this.age = age;
console.log(this);
}
var xiaolu = new Person('小鹿',22); // this = > xaiolu
箭头函数中的this
由于箭头函数没有单独的 this 值。箭头函数的 this 与声明所在的上下文相同。也就是说调用箭头函数的时候,不会隐式的调用 this 参数,而是从定义时的函数继承上下文。
const obj = {
a:()=>{
console.log(this);
}
}
// 对象调用箭头函数
obj.a(); // window
我们可以通过调用函数的call、apply、bind来改变this的指向
var obj ={
name:'zhangsan',
age:18
}
function print() {
console.log(this); //打印this的指向
console.log(arguments); // 打印传递的参数
}
// 通过call 改变this指向
print.call(obj,1,2,3);
// 通过 apply 改变this 指向
print.apply(obj,[1,2,3]);
// 通过 bind 改变this的指向
let fn = print.bind(obj, 1,2,3);
fn();
再说一说这三者的共同点和不同点
共同点:
不同点:
主要应用场景:
由于箭头函数没有自己的this指针,通过call()或者apply()方法调用一个函数时,只能传递参数(不能绑定this), 他们的第一个参数会被忽略
var obj={
name:'lxy'
}
问:new 内部发生了什么过程?可不可以手写实现一个 new 操作符?
对于new关键字,我们第一想到的就是在面向对象中new 一个实例对象,但是在JS中new和Java中的new的机制不一样
一般Java中,声明一个构造函数,通过new 类名() 来创建一个实例对象,而这个构造函数是一种特殊的函数。但是在JS中,只要new一个函数,就可以new一个对象,函数和构造函数没有任何的区别
对于new创建对象:
var arr = new Array();
new 的过程包括一下四个阶段:
对于Object.create() 方式创建对象:
Object.create(proto,[propertiesObject]);
proto: 新创建对象的原型对象。
propertiesObject: (可选) 可为创建的新对象设置属性和值。
一般用于继承:
var People= function(name) {
this.name = name;
}
People.peototype.sayName= function() {
console.log(this.name);
}
function Person(name, age) {
this.age = age;
People.call(this, name); // 使用call, 实现了People属性的继承
};
// 使用Object.create()方法,实现People原型方法的继承,并且修改了constructor 指向
Person.prototype = Object.create(People.peototype, {
constructor:{
configurable:true,
enumerable: true,
value:Person,
writable:true
}
});
Person.prototype.sayAge = function() {
console.log(this.age);
}
var p1 = new Person('person1',25);
p1.sayName() //'person1'
p1.sayAge(); // 25
闭包就是能够访问其他函数内部变量的函数
闭包长期占用内存,内存消耗很大,可能导致内存泄漏
不再用到的内存,没有及时释放,就叫做内存泄漏。
**内存泄漏是指我们已经无法再通过js代码来引用到某个对象,但垃圾回收器却认为这个对象还在被引用,因此在回收的时候不会释放它。**导致了分配的这块内存永远也无法被释放出来。如果这样的情况越来越多,会导致内存不够用而系统崩溃
问:怎么解决内存泄漏?说一说JS垃圾回收机制的运行原理?
需要我们手动管理好内存,但是对于JS有自动垃圾回收机制,自行对内存进行管理
垃圾回收器主要的功能就是每隔一段时间,就去周期性的执行收集不再继续用到的内存,然后将其释放掉
标记清除法
它的实现原理就是通过判断一个变量是否再执行环境中被引用,来进行标记删除
引用计数法
引用计数的最基本的含义就是跟踪记录每个值被引用的次数。
每个JS对象都有_proto_ 属性,这个属性指向了原型
原型链就是多个对象通过_proto_ 的方式连接了起来形成一条链
总结:
继承的核心思想就是,能够继承父类方法的同时,保证自己的私有属性和方法。
原型继承
组合继承
寄生组合继承
// 父类
function Father(name){
this.name = name;
this.colors = ["red","blue","green"];
}
// 方法定义在原型对象上(共享)
Father.prototype.sayName = function(){
alert(this.name);
};
function Son(name,age){
Father.call(this,name); // 核心
this.age = age;
}
Son.prototype = Object.create(Father.prototype); // 核心:
Son.prototype.constructor = Son; // 修复子类的 constructor 的指向
ES6的 extend 继承
ES6 的extend 继承其实就是寄生组合式继承的语法糖。
核心思想:
小结:
class Son extends Father {
// Son.prototype.__proto__ = Father.prototype
constructor(y) {
super(200); // super(200) => Father.call(this,200)
this.y = y } }
深浅拷贝是只针对Object和Array这样的引用数据类型的
let arr1 = arr2 = [1,2,3]
let obj1 = obj2 = {
a:1, b:2, c:3}
arr1[0] = 2
obj1.a = 2
console.log(arr2[0]) // 2
console.log(obj2.a) // 2
从上面的代码可以看出:同一个Array或者Object赋值给两个不同变量时,变量指向的是同一个内存地址,改变其中一个变量的属性值,另一个也会改变。如果我们想要的是两个初始值相等但互不影响的变量,就要使用到拷贝。
浅拷贝:
扩展运算符(ES6新语法)
let a = {
c: 1}
let b = {
...a}
a.c = 2
console.log(b.c) // 1
Object.assign(target, source)
将source的值浅拷贝到target目标对象上
let a = {
c: 1}
let b = Object.assign({
}, a)
a.c = 2
console.log(b.c) // 1
深拷贝:
JSON.stringify()
let obj = {
name: 'lxy',
city: {
city1: '北京',
city2: '上海'
}
}
// 浅拷贝
let obj1 = {
...obj}
// 深拷贝
let obj2 = JSON.stringify(obj)
// 改变源对象的引用类型值
obj.city.city1 = '杭州'
console.log(obj1.city.city1) // 杭州
console.log(JSON.parse(obj2).city.city1) // 北京
浅拷贝:
循环遍历对象,将对象的属性值拷贝到另一个对象中,返回该对象。
function shallowClone(o) {
const onj = {
};
for(let i in o) {
obj[i] = o[i]
}
return obj;
}
深拷贝:(简单实现)
对于深拷贝来说,就是在浅拷贝的基础上加上递归
var a1 = {
b: {
c: {
d: 1
}
}
}
function deepClone(obj) {
var target = {
}
for(var i in obj) {
if(obj.hasOwnProperty(i)) {
if(typeof obj[i] === 'object') {
target[i] = deepClone(obj[i])
} else {
target[i] = obj[i]
}
}
}
return target
}
JavaScript是一门单线程非阻塞的脚本语言。
主线程从任务队列读取事件,这个过程是循环不断地,所以整个运行机制又称为Event Loop(事件循环)
执行上下文是一个抽象的概念,可以理解为是代码执行的一个环境。分为全局执行上下文,函数(局部)执行上下文,Eval执行上下文
function foo() {
console.log('a');
bar();
console.log('b');
}
function bar() {
console.log('c')
}
foo()
代码解释:
宏任务一般包括:
微任务一般包括: