这堂课讲的是前端语法规范中的8种数据类型,以及如何判断它们的类型、回收机制,以及面试最爱问的原型和原型链、闭包问题
一、JS中的8种数据类型及区别
今天我们开篇先从JS的几种数据类型说起,JS数据类型分为基本数据类型、引用数据类型。
-
基本数据类型(值类型)
a. Number 数字
b. String 字符串
c. Boolean 布尔
d. undefined 未定义
f. symbol 符号
g. null 空 -
引用数据类型(复杂数据类型)
a. Function
b. Array
c. Object
d. Date
e. RegExp
......
那么我们要怎么知道一个数据它的类型是什么呢?接着往下走
二、要如何判断JS中数据类型呢
1. 可以使用 typeof 进行检测
console.log(typeof 1); // number
console.log(typeof true); // boolean
console.log(typeof 'str'); // string
console.log(typeof Symbol) // function
console.log(typeof function(){}); // function
console.log(typeof console.log()); // function
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
console.log(typeof undefined); // undefined
可以很清晰的发现用 typeof 检验数据类型的优缺点:
优点:能够区分基本数据类型
缺点:无法区分Array Object Null,都返回Object
2. 结果是返回 true 或 false 的 instanceof
console.log(1 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
优点:能够区分Array Object和Function,适合用于判断自定义的类实例对象
缺点:Number Boolean String基本数据类型不能判断
3. 能精确判断类型但是写法太繁琐的 Object.prototype.toString.call()
var toString = Object.prototype.toString;
console.log(toString.call(1)); //[object Number]
console.log(toString.call(true)); //[object Boolean]
console.log(toString.call('mc')); //[object String]
console.log(toString.call([])); //[object Array]
console.log(toString.call({})); //[object Object]
console.log(toString.call(function(){})); //[object Function]
console.log(toString.call(undefined)); //[object Undefined]
console.log(toString.call(null)); //[object Null]
优点:精准判断数据类型
缺点:写法繁琐不容易记,推荐进行封装后使用
综上,当碰到基本数据类型时可以使用 typeof 来判断,复杂数据类型可以使用 instanceof ,力求完美可以封装 Object.prototype.toString.call 进行复用 😁
三、JS垃圾回收机制
-
为什么要有垃圾回收机制呢?如果项目中存在大量不被释放的内存(堆、栈、上下文),页面性能会变得很低。当某些代码操作不能被合理释放,就会造成内存泄露。因此我们尽可能减少使用闭包,因为它会消耗内存。
-
浏览器垃圾回收机制 / 内存回收机制
浏览器的 JavaScript 具有自动垃圾回收机制(GC:Garbage Collection),垃圾收集器会定期(周期性)找出那些不再使用的变量,然后释放其内存。
-
优化手段:内存优化;手动释放:取消内存的占用
1) 堆内存:fn = null;
2) 栈内存: 把上下文中,被外部占用的堆取消占用即可 -
内存泄漏
在js中最常见的内存泄露有4种:全局变量、闭包、DOM元素的引用、定时器。
四、作用域和作用域链
作用域就是变量与函数的可访问范围,由当前环境与上层环境的一系列变量对象组成。
作用域:
-
全局作用域:代码在程序的任何地方都能访问到,window对象的内置属性都拥有全局作用域。
-
函数作用域:在固定的代码片段才能访问。
那么,作用域的作用是什么?
作用域的最大作用就是 隔离变量,如果是不同作用域下相同变量名不会有冲突。
作用域链:
一般情况下,变量到创建该变量的函数作用域中取值。但是如果在当前作用域中没有查到,就会向上级作用域去查,直到查到全局作用域,这样一个查找的过程形成的联调就叫作用域链!
五、闭包的作用:保存 / 保护
闭包的概念
函数执行时形成的私有上下文在正常情况下,代码执行完会出栈后释放;但是在特殊情况下,如果当前私有上下文中的某个东西被上下文以外的事物占用,则上下文不会出栈释放,从而形成不销毁的上下文。执行函数执行过程中,会形成一个全新的私有上下文,可能会被释放也可能不会被释放,但是不论它是不是被释放,都有保护和保存的作用。
我们把函数执行形成私有上下文来 保护 和 保存 私有变量的机制称为闭包。
闭包的用途
- 模仿块级作用域
- 保护外部函数的变量 能够访问函数定义时所在的词法作用域
- 封装私有化变量
- 创建模块
闭包的应用场景
大部分前端JavaScript代码都是“事件驱动”的,即一个事件绑定的回调方法:发送ajax请求成功、失败的回调,settimeout的延时回调,或者一个函数内部返回另一个匿名函数。这些都是闭包的应用。
闭包的优点
延长局部变量的生命周期
闭包的缺点
会导致函数的变量一直保存在内存中,过多的闭包可能导致内存泄露
六、JS中 this 的五种情况
- 作为普通函数执行时,
this指向window - 当函数作为对象的方法被调用时,
this就会指向该对象 - 构造器调用,
this指向返回的这个对象 - 箭头函数,箭头函数的
this指向绑定的这个函数,当函数有嵌套时,指向最近一层的对象 - 基于
function.prototype上的call、apply和bind调用模式,this指向的是传入的对象,call接受参数列表(用逗号分隔开参数),apply接受的参数是数组,bind与call一样但是返回的对象是函数
七、原型&&原型链
原型关系:
- 每个class都有显示原型 prototype
- 每个实例都有隐式原型 proto
- 实例的_proto_指向对应class的prototype
原型
在 JS 中,当我们创造一个对象的时候,这个对象本身就会包含一些它生来就有(预定义)的属性,其中每个函数对象都有一个prototype属性,这个属性指向函数的原型对象。
原型链
函数的原型链对象constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针是指向上一层的原型对象,而上一层的原型对象的结构依然类似。因此可以利用__proto__一直指向Object的原型对象上,而Object原型对象用Object.prototype.__ proto__ = null表示原型链顶端。如此形成了js的原型链继承。同时所有的js对象都有Object的基本防范
特点
JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
内容持续总结中……
参考文章:🔥 连八股文都不懂还指望在前端混下去么,总结得很全面,推荐阅读 🤩