面试-JS相关

一、JS执行上下文和执行栈
执行上下文:浏览器的JavaScript引擎会创造一个特殊的环境来处理这些JavaScript代码的转换和执行,js中运行任何代码都是在执行上下文中运行
执行上下文类型:全局、函数、eval
执行上下文的生命周期:创建阶段(this绑定、词法环境、变量环境)、执行阶段(编译、代码执行)、回收阶段
执行上下文栈:先进后出原则(进栈出栈)

二、JS内存
栈数据结构-基本类型:后进先出
堆数据结构-引用类型:树状结构
队列:先进先出(事件循环 Event Loop)
垃圾回收:引用计数(不再使用:害怕循环引用,相互引用造成内存泄漏)、标记清除(常用,无法到达的对象,从根部扫描,从根部无法到达的对象标记不再使用)
注意:闭包中的变量保存在堆内存中, 返回函数后闭包还能引用到函数內的变量

function A() {
  let a = 1
  function B() {//闭包 函数 A 返回了一个函数 B,并且函数 B 中使用了函数 A 的变量,函数 B 就被称为闭包
      console.log(a)
  }
  return B  
}

内存泄漏:不再用到的内存,没有及时释放,就叫做内存泄漏
常见的js内存泄漏:
(1)意外的全局变量:例如未定义的变量会在全局创建新变量(或this指向导致)

function foo(arg) {
    bar = "this is a hidden global variable"; // 挂载到全局对象上,意外创建全局变量
}
function foo() {
    this.variable = "potential accidental global"; //this 指向了全局对象(window)
}// 而不是 undefined
foo();
// 办法:在 JavaScript 文件头部加上 'use strict',使用严格模式避免意外的全局变量

(2)被遗忘的计时器或回调函数

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // 处理 node 和 someResource
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);
// 不管node是否移除,定时器一直存活且垃圾回收机制无法回收

(3)脱离 DOM的引用:不是在dom树中引用,例如数组或对象中引用

var elements = { // 要清除时两个引用都得 清除
    button: document.getElementById('button'),
    image: document.getElementById('image'),
    text: document.getElementById('text')
};
// 另外注意:如果代码包含子dom的引用,清除整体时不会清楚子的引用,因为父子之前存在引用关系

(4)闭包:闭包的关键是匿名函数可以访问父级作用域的变量,可以手动设置null解决

三、作用域链和闭包
闭包:有权访问另一函数作用域中变量的函数
特点:可以访问当前函数以外的变量,即使外部函数已返回闭包仍能访问外部函数定义的变量,也可更新外部值

function getOuter(){
  var date = '815';
  function getDate(str){
    date = str; // 可以修改变量的值
    console.log(str + date);  //访问外部的date
  }
  return getDate;     //外部函数返回,正常
}
var today = getOuter();
today('今天是:');   //"今天是:815"
today('明天不是:');   //"明天不是:815"

作用域链:一层层拿变量(向上拿变量)

// var引入的问题 
var scope="global";
function scopeTest(){
    console.log(scope); // 变量提升,拿到undefined,但不会报错
    var scope="local"  
}
scopeTest(); //undefined

// 没有块级作用域
var data = [];
for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}
data[0]();	// 3
data[1]();	// 3
data[2]();	// 3

// 办法使用:返回一个 匿名函数赋值
var data = [];
for (var i = 0; i < 3; i++) {
  data[i] = (function (num) {
      return function(){
          console.log(num);
      }
  })(i);
}
data[0]();	// 0
data[1]();	// 1
data[2]();	// 2

// 办法使用let块级作用域
var data = [];
for (let i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}
data[0]();
data[1]();
data[2]();

四、this解析
优先级:
独立调用优先级最低(默认调用,函数调用,非严格window,严格undefined)
显示调用(call apply bind)优先级高于隐式调用(对象中的函数)
new绑定优先级高于隐式调用
new绑定优先级高于显示调用

五、深浅拷贝原理
1、赋值:
(1)基本数据类型:赋值,赋值之后互不影响
(2)引用数据类型:赋址,变量有相同的引用,互相间有影响

let a = "muyiy";
let b = a;
console.log(b); // muyiy
a = "change";
console.log(a); // change
console.log(b); // muyiy

// 引用类型相互受影响 
let a = {
    name: "muyiy",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let b = a;
console.log(b);
// {
// 	name: "muyiy",
// 	book: {title: "You Don't Know JS", price: "45"}
// } 
a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 
console.log(b);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

2、浅拷贝:基本类型拷贝就是值(互相不影响),引用类型拷贝的是地址,相互之间有影响
浅拷贝场景:
(1)简单的赋值实现
(2)Object.assign()实现,任意多个的源对象自身的可枚举属性拷贝给目标对象(一层是深拷贝)
(3)函数库lodash的_.clone方法
(4)扩展运算符 …
(5)Array.prototype.concat()
(6)Array.prototype.slice()

let a = {
    name: "muyiy",
    book: {
        title: "You Don't Know JS",
        price: "45"
    }
}
let b = Object.assign({}, a);
console.log(b);
// {
// 	name: "muyiy",
// 	book: {title: "You Don't Know JS", price: "45"}
// } 

a.name = "change";
a.book.price = "55";
console.log(a);
// {
// 	name: "change",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

console.log(b);
// {
// 	name: "muyiy",
// 	book: {title: "You Don't Know JS", price: "55"}
// } 

3、深拷贝:拷贝所有的属性并拷贝 属性所指向的内存(前后所有的操作互不影响)
深拷贝使用场景:
(1)JSON.parse(JSON.stringify(object)) 对数组对象都可以
问题:会忽略symbol|undefined,不能序列化函数(忽略即执行 Object.create(oldObj))、不能正确处理new Date() 不能处理正则,不能解决循环引用的对象(报错)
所以遇到symbol、undefined、函数直接忽略
(2)若对象只有一层的话Object.assign()函数也可 实现
(3)lodash的.cloneDeep方法
(4) Object.create(oldObj)
(5)手动递归实现

var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1);
// { body: { a: 10 } } <-- 沒被改到
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// false

问题:忽略函数、symbol、undefined,你能处理new Date()处理错误 不能解决循环引用的对象(报错)
// 木易杨
let obj = {
    name: 'muyiy',
    a: undefined,
    b: Symbol('muyiy'),
    c: function() {}
}
console.log(obj);
// {
// 	name: "muyiy", 
// 	a: undefined, 
//  b: Symbol(muyiy), 
//  c: ƒ ()
// }

let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: "muyiy"}

let obj = {
    a: 1,
    b: {
        c: 2,
   		d: 3
    }
}
obj.a = obj.b;
obj.b.c = obj.a;

let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON

JSON.stringify(new Date());
// ""2018-12-24T02:59:25.776Z""

// 递归实现
function deepCopy(target) {
    if (
        Object.prototype.toString.call(target) !== '[object Object]' &&
        Object.prototype.toString.call(target) !== '[object Array]'
    ) {
        return target
    }
    const newTarget = Array.isArray(target) ? [] : {} // 给引用初始化结构
    // 自身的可枚举的(不含symbol)若使用for in 则的过滤出自身的(不能包含继承的)
    Object.keys(target).forEach(
        (key) =>
        (newTarget[key] =
         target[key] instanceof Object ? deepCopy(target[key]) : target[key])
    )
    return newTarget
}
// 调用
deepCopy(obj)

六、原型Prototype
1、什么是构造函数
构造函数本身就是函数,与普通函数不同是首字母大写,使用 new 生成实例的函数就是构造函数,直接调用的就是普通函数

// 普通函数
function parent2(age) {
    this.age = age;
}
var p2 = parent2(50); // 因为普通函数不用返回新创见的对象,需手动执行
// undefined
// 普通函数
function parent3(age) {
    return {
        age: age
    }
}
var p3 = parent3(50);
p3.constructor === Object; // true

2、Symbol是构造函数吗?
Symbol作为构造函数不完整,因不能通过new创建,但原型上有constructor属性指向Symbol函数

var sym = Symbol(123); 
console.log( sym );
// Symbol(123)
console.log( sym.constructor );
// ƒ Symbol() { [native code] }

3、constructor值只读吗?
(1)对于引用类型constructor是可修改的
(2)对于基本类型是只读的(null 和 undefined 是没有 constructor 属性的)

七、高阶函数
(1)有一个或多个函数输入
(2)有一个函数输出

八、节流防抖:对事件的触发频次进行限制
(1)节流:一定时间内只执行一次
(2)防抖:无论触发多少次只执行最后一次,在触发期间再触发不执行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值