JavaScript面试自我补充

知识点大纲:

  1. 数据类型
  2. 形参传值与传址
  3. 预解析、作用域和作用域链
  4. Object.freeze()冻结对象属性变量
  5. 严格模式
  6. IIFE
  7. 闭包(这个弄详细点!)
  8. 事件流
  9. this,以及call、apply、bind作用和场景
  10. 面向对象编程之三种简单的设计模式
  11. 浅拷贝、深拷贝
  12. Event Loop(事件循环系统)
  13. 模块化规范

本文是根据自己的情况,总结/补充的易于“入坑”面试考点,部分较新颖。对大家来说,可能内容介绍不是很全面,请见谅!

1.数值类型

NaN

(注意:这个不是数据类型!)
NaN和任何值都不相等,即使本身也不相等!

NaN==NaN
<false
10=='10'
<true

所以
ES5
检测NaN,用不等于!=检测
ES6
检测NaN,用isNaN检测

Symbol(独一无二的简单数据类型)

Symbol是ES6新增的一个基本数据类型,表示独一无二的值。
使用场景:

  1. 对象的属性名,确保对象中不会出现相同的属性名;
  2. 防止不小心被修改或覆盖
//独一无二
let a = Symbol('name');
let b = Symbol('name');
console.log(a==b);		//false
//解决相同属性名问题(ES6之前属性名设置name1、name2、name3......)
let a = Symbol('name');
let b = Symbol('name');
let obj = {
  name: '张三',
  name: '李四',
  name: '王五'
}
console.log(obj);       //{name: "王五"}
console.log(obj.name);  //王五
let a = Symbol('name');
let b = Symbol('name');
let obj = {
  [a]: '张三',
  [b]: '李四',
  [Symbol('name')]: '王五'
}
console.log(obj);       //{Symbol(name): "张三", Symbol(name): "李四", Symbol(name): "王五"}
console.log(obj[a]);  //张三

还有几个需要了解的方法:

  • Symbol.for
    • 根据描述获取Symbol,如果不存在则新建一个Symbol(在全局被登记)
  • Symbol.keyFor
    • 根据使用Symbol.for登记的Symbol返回描述,如果找不到返回undefined 。
let a= Symbol.for("name");
let b= Symbol.for("name");
console.log(a== b); //true
console.log(Symbol.keyFor(a));	//name

2.形参传值与传址

变量在参数传递方式上,有所不同(理解栈和堆)
       函数的参数如果是简单类型,会将一个值类型的数值副本传到函数内部,函数内部不影响函数外部传递的参数变量

       如果是一个参数是引用类型,会将引用类型的地址值复制给传入函数的参数,函数内部修改会影响传递参数的引用对象。
1.形参传值的误区:
变量和形参
在这里插入图片描述

// 引用类型
var a = {x: 10, y: 20}
var b = a
b.x = 100
b.y = 200
console.log(a) // {x: 100, y: 200}
console.log(b) // {x: 100, y: 200}

3.作用域、作用域链

作用域

全局作用域:

window.a = 'hello';

//非严格模式下
b = 10;		//容易造成全局污染(影响其他文件的值)

函数级作用域:
var(函数级作用域,在函数中,只有当前函数内部访问;在块中,父级和块内都可以访问到)
let(块级作用域)-----没有预解析(推荐)
const(常量-块级作用域)-----没有预解析

for(var i = 0; i < 5; i++) {
    var num = 10;
}
console.log(num);		//10

for(var j = 0; j < 5; j++) {
    let num2 = 100;
}
console.log(num2);		//<VM232 helloword.js:18 Uncaught ReferenceError: num2 is not defined
function show(){
    var a = "hello";
}
show(); 	//调用执行完成,show被释放(出栈)
console.log(a); //helloword.js:5 Uncaught ReferenceError: a is not defined
let a = 10;
function show(){
    console.log(a);        //10
}
show();
let a = 10;
function show(){
    console.log(a);        //Uncaught ReferenceError: Cannot access 'a' before initialization(初始化之前不能访问‘a’)
    let a = 100;
}
show();
案例
var a = 666;
function show() {
    var a = 12;
    show2();
}
function show2(){
    console.log(a);
}
show();				//666
var a = 666
function show3() {
    var a = 12;
    function show4(){
    	console.log(a);
	}
	show4();
}
show3();		//12

4.Object.freeze()冻结对象属性变量

const stu = {
  "id": 'st02023',
  "name": '王磊'
};
stu.name = '张三';
console.log(stu);   //{id: "st02023", name: "张三"}
const stu = {
  "id": 'st02023',
  "name": '王磊'
};
Object.freeze(stu);
stu.name = '张三';
console.log(stu);   //{id: "st02023", name: "王磊"}

5.严格模式-“use strict”——避免全局污染(作用之一)

"use strict"
var a = 15;
b = 25;     //helloword.js:3 Uncaught ReferenceError: b is not defined

6.IIFE(普通话口语“亦菲~”)——自调用函数

作用:

  1. 防止全局变量污染
  2. 函数和变量,在执行完都被释放
var a = 100;
var a = 200;
console.log(a);     //200
(function(){
    var a = 100;  
    console.log(a);     //100  
})();

(function(){
    var a = 200;    
    console.log(a);     //200
})();

console.log(a);     //helloword.js:9 Uncaught ReferenceError: a is not defined

7.闭包

//案例1
function show() {
  let a = 100;
  return function() {
    return a;
  }
}

let show2 = show();
console.log(show());    //ƒ () { return a; }	//show返回的匿名函数
console.log(show2());   //100
show2 = null;

闭包官方定义:闭包就是能够读取其他函数内部变量的函数。(包括其他语言)
在JavaScript中,
闭包定义:函数内部的子函数就是闭包(比如:上面代码的匿名函数)
闭包本质:是连接函数内部和函数外部的桥梁(比如:上面匿名函数是连接show和show2的桥梁)
闭包现象:能够读取其他函数内部变量(比如:show2访问show的变量a)
最大特点:闭包记住了闭包函数诞生的生产环境,可以直接使用生产环境的变量(比如:上面代码的匿名函数可以访问show的所有变量,甚至是show的父环境的变量)
闭包作用:函数可以访问其他函数内部变量
满足闭包的三个条件:

  • 函数嵌套(函数内部有子函数(闭包函数))
  • 闭包访问所在的作用域
  • 所在的作用域被调用

闭包使用场景:框架、(我在迭代事件添加监听使用过沙箱式闭包),具体十种场景,后面案例介绍!

理解闭包的缓存和手动释放闭包占用资源

//案例2
//用闭包实现点赞
function click_like() {
  let num = 0;
  return function() {
    return ++num;
  }
}

let show2 = click_like();
console.log(show2());    //1
console.log(show2());    //2
console.log(show2());    //3
console.log(show2());    //4
console.log(click_like());   //ƒ () { return num++; }
console.log(click_like());   //ƒ () { return num++; }
console.log(click_like());   //ƒ () { return num++; }
console.log(click_like());  //ƒ () { return num++; }
console.log(typeof click_like());	//function
console.log(click_like()());//1
console.log(click_like()());//1
console.log(click_like()());//1
show2 = null;

根据代码来帮助理解缓存没有被释放(个人理解):因为全局引用变量show2依赖函数click_like(我们知道全局变量是不会自动释放的),show2不能被回收,那被占用的函数click_like也不能回收,接着就是被占用的function匿名函数(闭包)的占用的参数都不能回收。
再换个对象讨论
let show2 = click_like(); 调用click_like(返回的是匿名函数ƒ () { return num++; }),click_like调用完成就被释放掉,所以结果总是console.log(click_like()());//1;
这就理解了闭包的另一个作用了吧!——缓存数据
回收设置:直接将全局引用变量赋为null。
注意:闭包最后一定要手动回收,避免网页加载缓慢(可能造成网页崩溃)

理解了原理,咱们再写一个使用IIFE的闭包(沙箱式闭包)…看看是不是跟容易懂了!

//案例3
//使用包含IIFE的闭包实现点赞
let show2 = (function() {
  let num = 0;
  return function() {
    return ++num;
  }
})();
console.log(show2()); //1
console.log(show2()); //2
console.log(show2()); //3
console.log(show2()); //4
show2 = null;

看一个稍微复杂的闭包:

//案例4
function show() {
  let arr = [];
  for(var i = 0; i < 10; i++) {
    arr[i] = function() {
      return i;
    }
  }
  return arr;
}

let show2 = show();   //调用show,返回函数数组:arr[0]=f(){...}, arr[1]=f(){...}, arr[3]=f(){...} .........
console.log(show2[0]()); //10   //for循环执行完成,返回函数还没被执行,i的值最终为10
console.log(show2[0]()); //10
console.log(show2[1]()); //10
console.log(show2[1]()); //10
show2 = null;

结果都是10,怎么改进呢?(沙箱式闭包)

//案例5
function show() {
  let arr = [];
  for(var i = 0; i < 10; i++) {
    arr[i] = (function(x) {
      return x;
    })(i);
  }
  return arr;
}

let show2 = show();   //调用show,IIFE执行,调用完成返回arr=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(show2); //(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
show2 = null;

第二种改进方式:(直接将循环迭代的声明变量i设置为块作用域)

//案例6
function show() {
  let arr = [];
  for(let i = 0; i < 10; i++) {
    arr[i] = function() {
      return i;
    }
  }
  return arr;
}

let show2 = show();   
console.log(show2[0]()); //0
console.log(show2[0]()); //0
console.log(show2[1]()); //1
console.log(show2[2]()); //2
console.log(show2[3]()); //3
console.log(show2[4]()); //4
show2 = null;

闭包十种场景:

  1. 返回值(最常见)——前面案例1、2
  2. 函数赋值——前面案例3
  3. 函数参数——将闭包函数当做参数
  4. IIFE——前面案例3、5
  5. 循环赋值——前面案例4、5
  6. getter和setter——设置getter和setter的闭包函数
  7. 迭代器——前面的“点赞”
  8. 区分首次
let show = (function() {
  let arr = [];
  return function(id) {
    if(arr.indexOf(id) >= 0) {
      return false;
    }
    else {
      arr.push(id);
      return true;
    }
  }
})();

console.log(show(10));  //true
console.log(show(10));  //true
  1. 缓存机制
  2. image图片对象上报

8.事件流

事件流包括三个阶段:
事件捕获阶段、处于目标阶段、冒泡阶段
下图是W3C的事件执行顺序的一个标准:
事件流
不同浏览器对事件流支持不同:
1)、所有现代浏览器都支持事件冒泡,但在具体实现中略有差别:
IE5.5及更早版本中事件冒泡会跳过元素(从body直接跳到document)。
IE9、Firefox、Chrome、和Safari则将事件一直冒泡到window对象
2)、IE9、Firefox、Chrome、Opera、和Safari都支持事件捕获。尽管DOM标准要求事件应该从document对象开始传播,但这些浏览器都是从window对象开始捕获事件的。
3)、由于老版本浏览器不支持,很少有人使用事件捕获。建议使用事件冒泡。
总结一下:
浏览器默认使用冒泡(由内到外触发事件,就像“冒泡”一样)
IE9、Firefox、Chrome、和Safari等新的或高版本浏览器支持整个事件流,但起止都是window对象(IE除外,起止于document)。
IE老版本的仅支持冒泡。
使用捕获,在事件绑定第三个参数设置为true(一般不用)

9.this

this指代只能是对象
this包含该对象的全部属性和方法
对象的上下文环境不变,this指向也不会变
详细参考:javascript this详解

10.面向对象的三种简单设计模式

单体模式——(就是我们常用的“字面量对象”的使用)

var teacher = {
    name: '张三',
    age: 23,
    show: function() {
        console.log(this.name + '老师今年' + this.age + '岁!')
    }
}

teacher.show();		//张三老师今年23岁!

原型模式

function Teacher(name, age) {
    this.name = name;
    this.age = age;
}

Teacher.prototype.show = function() {
    console.log(this.name + '老师今年' + this.age + '岁!');
}

var teacher_one = new Teacher('张三', 23);
teacher_one.show();		//张三老师今年23岁!

伪类模式

class Teacher {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    show() {
        console.log(this.name + '老师今年' + this.age + '岁!');
    }
}

var teacher_one = new Teacher('张三', 23);
teacher_one.show();		//张三老师今年23岁!

11.浅拷贝、深拷贝

参考:javascript面试汇总
强烈推荐:浅拷贝、深拷贝

12.Event Loop

通过事件循环机制,理解JavaScript多线程、异步的实现原理。

宏任务:script(整体代码)、setTimeout、setInterval、setImmediate、I/O、UI rendering
微任务:promise、Object.observe、MutationObserver

要点:微任务总跟着当前宏任务后面执行(微任务放到微任务队列),遇到新的宏任务时,新的宏任务的上下文消息放到消息队列,等待当前宏任务执行完成。

一个完整的 Event Loop 过程,可以概括为以下阶段:
事件循环机制流程图

理解事件循环机制
两分钟了解事件循环机制传送门:视频

13.模块化

几种常见模块化规范的简介

CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案

AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。

CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行。不过,依赖SPM 打包,模块的加载逻辑偏重

ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值