JS笔记

141 阅读12分钟

数据类型存储

基本数据类型存储在栈,引用数据类型存储在堆中,并在栈中存储一个引用地址。
栈中大小固定,需要被频繁存取,而堆可以按优先级排序,且大小不固定,JS中不能直接对堆进行操作。

判断数据类型

  1. typeof typeof -对象数组null都会返回object,其他正确, typeof null返回object是因为底层二进制数据用前三位判断类型,000被判断为object。
  2. instanceof instanceof只能判断引用数据类型
  3. constructor 第一个作用:实例对象通过constructor访问原型,第二个作用,判断数据类型(注意,改变原型后不能准确判断)
  4. Object.prototype.toString.call(),截取字符串得到结果
Object.prototype.toString.call(a).slice(8, -1)

同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?

这是因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。

undefined 和 null 区别

undefined表示未定义,null表示空对象,typeofnull返回Object,两者==返回true,===返回false,在进行数值运算时,比如说null + 1 = 1, 而undefined + 1 = NAN

string为什么会有length属性

对于基本数据类型,当我们读取其值时,后台会创建对应包装类型的实例,然后调用其属性或方法,调用完就会销毁,所以我们能够读取属性,但不能为他添加属性

== 和 === 区别

==会根据变量的值进行类型转换,如1 == '1'true,而 === 是严格比较,有如下几种情况

  • 字符串就是逐位比较
  • 数字数值相等,其中正0等于负0,NaN不等于任何值,包括NaN
  • 布尔值也是相等
  • 对象的引用地址相等,即引用同一个对象,指向同一个堆地址
  • Null==undefined 为true ===为false

手写instanceof

const _instanceof = function(left, right) {
    let proto = Object.getPrototypeOf(left)
    let prototype = right.prototype
    while(1) {
        if(!proto) return false
        if(proto === proto) return true
        proto = Object.getPrototypeOf(proto)
    }
}

0.1+0.2 !== 0.3

二进制相加,修正可用toFix(指定位数)

isNaN 和 Number.isNaN 函数的区别?

  • 函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

  • 函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

|| 和 &&

  • ||如果结果为true返回第一个表达式的值,否则返回第二个
  • &&相反,如果结果为true返回第二个表达式的值,否则返回第一个

隐式类型转换

首先简单讲讲原理,js每个值自带一个ToPrimitive方法,这个方法会调用ValueOf和toString方法进行转换

  • +如果两边至少有一个字符串,则结果会转变为字符串
  • -,*,/转化为数字
  • ==会将两边尽量转化为Number
  • ><如果两边都是字符串,就按字母表比较,否则尽量转为数字
  • 对象的转换规则
var objToNumber = value => Number(value.valueOf().toString())
objToNumber([]) === 0
objToNumber({}) === NaN

Bigint解决了什么问题

JS中最大安全数字为Number.MAX_SAFE_INTEGER,超过这个数就会造成精度缺失的问题。

let var const

  • let和const都是es6新提出的,这两个的作用域都是块级作用域,且二者都没有变量提升,都不能在同一块级作用域内进行重复声明,如果在声明前使用变量,就会出现暂时性死区。
  • let声明的变量可以修改,const声明的变量如果是基本数据类型,是不能更改的;如果是引用数据类型如对象数组,由于其在栈中存放的是引用地址,所以只要不改变其引用地址,即往对象或数组的增删改内容是可以的。

new运算符

let person = new Person()

这段代码中,new的作用是什么?

  1. 创建一个空对象
  2. 将这个空对象的隐式原型指向构造函数的显式原型
  3. 将构造函数中的this指向这个空对象
  4. 判断返回值,如果构造函数有返回值,则判断这个返回值是否为对象类型,如果不是则返回第一步的空对象。

new一个箭头函数会发生什么

由于箭头函数不能改变this指向,也没有prototype属性,更没有arguments参数,所以无法完成上面的第二三步。

箭头函数和普通函数的区别

  • 箭头函数没有自己的this,它的this是从上一次作用域继承而来的
  • 箭头函数的this不能被call, apply, bind修改
  • 箭头函数没有arguments,prototype属性

箭头函数的this指向

箭头函数的this指向继承自上一层作用域的this,可以通过babel理解

// ES6 
const obj = { 
  getArrow() { 
    return () => { 
      console.log(this === obj); 
    }; 
  } 
}
// ES5,由 Babel 转译
var obj = { 
   getArrow: function getArrow() { 
     var _this = this; 
     return function () { 
        console.log(_this === obj); 
     }; 
   } 
};

Map WeakMap

WeakMap和Map的区别就是WeakMap只能以对象作为key,其存在的意义在于:当我们以某个对象作为Map的键时,我们就形成了对那个对象的引用,可当我们不需要这个对象时,除非我们手动释放这个引用,否则垃圾回收机制是不会释放这个对象占用的内存的
而WeakMap键名所引用的对象都是弱引用,即垃圾回收机制不会将其考虑在内,所以当这个对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存,即一旦不再需要,WeakMap里面的键值对会自动消失。
Set和WeakSet也是同理。

让js延迟加载的方式

  • defer/async标签
  • 监听文档解析事件,解析完成后动态创建script标签引入js脚本
  • 把script标签放在最下面

类数组对象的定义和转换方式

类数组就是有一个length属性和若干索引值的对象,如arguments,转换方法如下:

  • Array.prototype.slice.call()
  • Array.from
  • [...]扩展运算符
  • Array.prototype.splice.call(arrayLike, 0)
  • Array.prototype.concat.call([], arrayLike)

常见的位运算符

  • 与&
  • 或|
  • 异或^ 二者不同时返回1
  • 取反~ 如~3 0011取反为 1100 得-4
  • 左移运算符 << 左移一位相当于乘2
  • 右移运算符 >> 右移一位相当于除2

遍历arguments的方法

  • 第一种,把arguments转换成数组在遍历
  • 第二种,调用Array原型链上的方法,用call等改变指向使arguments调用

DOM 和 BOM

  • DOM是文档对象模型,定义了处理网页的方法与接口
  • BOM是浏览器对象模型,其中核心为window对象,window对象既是js访问视窗的接口,也是全局对象,document为window的子对象

AJAX请求

创建AJAX请求的步骤:

  • 首先创建一个XMLHTTPRequest对象xhr
  • 然后在这个对象上用open方法创建一个HTTP请求,参数为请求方式,url和是否异步
  • 在这个对象上用onreadystatechange方法监听状态变化,当readyState等于4时,判断http状态码,如果为200就可以处理responseText,否则就处理statusText,当然也可以通过onerror处理。
  • 除此之外,也可以通过setRequestHeader方法设置请求头等
  • 最后xhr.send()发送请求

fetch

  • fetch是es6新推出的API,原生支持promise,但不支持abort,也不能监测请求进度,且默认不携带cookie
let url = ''
fetch(url, {
    method: 'POST',
    body: {
        a: 1
    }
}).then(res => res.json())
.then((res) => {
    
})

变量提升

变量提升是因为JS引擎在预编译时会创建一个全局执行上下文环境,创建时会将变量设置为undefined, 函数声明为可用。函数执行前也是同理,会创建一个函数执行上下文,进行相同操作。

DOM节点的相关操作

  • 创建 document.createElement()
  • 插入 fatherElement.appendChild()或InsertBefore
  • 删除 fatherElement.removeChild()
  • 获取 ...

for in 和 for of 区别

  • for in 用来专门遍历对象,由于会遍历整个原型链,所以性能很差,不推荐使用
  • for of可以遍历任何具有iterator接口的数据类型,包括数组、类数组,set,map等,遍历的值为value。

this绑定

  • 默认绑定:全局定义变量或函数,this默认指向window,严格模式下为undefined
  • 隐式绑定,函数作为某个对象的方法使用,this默认指向函数的上级对象
  • 显示绑定,call apply bind
  • new操作符, 指向实例对象

map和forEach的区别

  • forEach没有返回值,而且forEach并不会改变原来的对象,但forEach里的callback可能会对原来的对象进行修改。因为forEach本身是一个值引用。
let objArr = [
    { name: '云牧', age: 20 },
    { name: '许嵩', age: 30 },
];

objArr.forEach((item) => {
    item = {
        name: '邓紫棋',
        age: '29',
    };
});
console.log(JSON.stringify(objArr)); 
// 打印结果:[{"name": "云牧","age": 20},{"name": "许嵩","age": 30}]

首先forEach相当于在栈中开启一个空间与原数组的这个键指向同一个地址,然后你对这个item进行赋值,相当于切断了这个指向,那么原来的数组中指向未变,自然不会修改原数组

let objArr = [
    { name: '云牧', age: 20 },
    { name: '许嵩', age: 30 },
];

objArr.forEach((item) => {
    item = {
        name: '邓紫棋',
        age: '29',
    };
    
});
console.log(JSON.stringify(objArr)); 
// 打印结果:[{"name": "云牧","age": 20},{"name": "许嵩","age": 30}]

而你直接修改对象的某个属性,当然是可以更改的,因为都指向同一个地址

let objArr = [
    { name: '云牧', age: 28 },
    { name: '许嵩', age: 30 },
];

objArr.forEach((item) => {
    item.name = '邓紫棋';
});
console.log(JSON.stringify(objArr));
// 打印结果:[{"name":"邓紫棋","age":28},{"name":"邓紫棋","age":30}]
  • map有返回值,会返回修改之后的数组,然后对每个元素都进行回调函数的操作。

原型与原型链

先从原型开始将起。

  • 每一个构造函数都有一个prototype属性,指向一个对象,这个对象就是原型对象,原型对象上存放着实例共有的属性。
  • 每一个原型对象都有一个constructor属性,指向指向它的那个构造函数。
  • 每一个对象都有一个隐式原型__proto__属性,指向他的原型对象。
  • 访问对象的属性时,会首先在对象的自身上查找,如果没有,就会到对象的原型对象上查找,直到原型对象为null。

而这些对象与其原型对象构成层层相连,则为原型链。原型链的终点是Object.prototype,指向null

作用域与作用域链

作用域就是程序运行时某些特定部分中变量,对象等的可访问性。作用域可以分为全局作用域和局部作用域,局部作用域可以分为函数作用域和块级作用域。块级作用域是由{}创建,let和const的作用域即为块级作用域,所声明的变量在指定块的作用域外无法访问。(需注意,当用let声明时,for循环中每一次循环会是一个独立的块级作用域)。

  • 从使用方面,作用域链是js的变量查找方式,当访问一个变量时,js引擎首先会在当前作用域查找,如果没有,就会往其上一层作用域寻找。以此类推直到找到全局作用域内仍未找到,则报错。
  • 作用域链在js内以数组形式存储,第一个索引是函数本身的执行上下文,下一个是对象外部执行上下文

Object.proto == Function.prototype

返回true,因为Object就是function Object()

webpack基础题

用过哪些常见loader

  • url-loader 由于处理图片、字体等。可以设置options的limit阈值,大于阈值则交给file-loader处理,小于则返回base64编码格式
  • style-loader、css-loader,less/sass-loader 编译css\sass文件
  • babel-loader 把ES6转化为ES5
  • ts-loader 编译ts文件
  • postcss-loader 配合autoprefixer补齐浏览器前缀
  • vue-loader 编译vue单文件组件

用过哪些常见plugin

  • mini-css-extract-plugin:分离样式,从js中将css样式提取成单独文件
  • html-webpack-plugin:自动引入js文件
  • clean-webpack-plugin:清理打包目录
  • Uglify-js-plugin:压缩js,清除生产环境的console等
  • speed-measure-webpack-plugin:测量每个loader和plugin耗费时间

loader和plugin区别

  • loader本质上是一个函数,对除js外的资源进行处理。由于webpack只认识js,所以需要loader对其他类型的资源进行转译的预处理。
  • plugin就是插件,它可以扩展webpack的功能,它通过监听webpack运行时广播的事件,在合适时间通过webpack的api改变输出结果。

webpack构建流程

  • 初始化:加载和合并配置参数,加载plugin,实例化Compiler对象
  • 确定出口 根据配置的entry找到所有入口文件
  • 从入口文件出发,调用配置的loader进行编译,然后在找到这个模块依赖的模块,通过递归完成所有编译
  • 通过入口文件和模块的依赖关系,将Module组装成Chunk,将Chunk转化成文件输出到文件系统

sourcemap是什么

source map就是把编译打包压缩后的代码,映射回源代码的过程,用来调试源码。