JS

440 阅读14分钟

JS数据类型

JS基本有5种简单数据类型:String,Number,Boolean,Null,Undefined。
引用数据类型:Object(function、Array、Date)。

ES6 中新增了一种 Symbol 。这种类型的对象永不相等,即始创建的时候传入相同的值,可以解决属性名冲突的问题,做为标记。
谷歌67版本中还出现了一种 bigInt。是指安全存储、操作大整数。即始超出 Number 能够表示的安全整数范围。是 chrome 67中的新功能。(但是很多人不把这个做为一个类型)。

NaN !== NaN //

JS数据类型:JS 中 typeof 输出分别是什么?

    { } 、[ ] 输出 object。

    console.log( ) 输出 function。

    注意一点:NaN 是 Number 中的一种,非Number 。
ECMAjavascript中的对象其实就是一组数据和功能的集合。对象可以通过执行new操作符后跟要创建的对象类型的名称来创建。创建object类型的实例并为其添加属性(或)方法,就可以自定义创建对象。

    如:var o = new Object( );

    object 的每个实例都有下列属性和方法:

    constructor:保存着用于创建当前对象的函数。(构造函数)constructor就是object();

    hasOwnProperty(propertyName):用于检查给定的当前属性在当前对象实例中)而不是在实例原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字稚串形式指定(例如:o.hasOwnProperty(“name”))。

    isPrototypeOf(object):用于检查传入的对象是否是传入对象原型。

    propertyIsEnumerable(propertyName):用于检查给定属性是否能够用for-in语句。与hasOwnProperty()方法一样,作为参数的属性名必须以字符串形式指定。

    toLocaleString( ):返回对象的字符串表示,该字符串与执行环境的地区对应。

    toString( ):返回对象的字符串表示。

    valueOf( ):返回对象的字符串、数值或者布尔值表示。通常与toString( )方法的返回值得相同。

    ECMAJS中object是所有对象的基础,因些所有对象都具有这些基本的属性和方法。
    

Number parseInt parseFloat区别

一:Number()

如果是Boolean值,true和false值将分别被转换为1和0。

如果是数字值,只是简单的传入和返回。

如果是null值,返回0。

如果是undefined,返回NaN。

如果是字符串:

  a.  如果字符串中只包含数字时,将其转换为十进制数值,忽略前导0

  b. 如果字符串中包含有效浮点格式,如“1.1”,将其转换为对应的浮点数字,忽略前导0

  c. 如果字符串中包含有效的十六进制格式,如“0xf”,将其转换为相同大小的十进制数值

  d. 如果字符串为空,将其转换为0

  e. 如果字符串中包含除上述格式之外的字符,则将其转换为NaN

如果是对象,则调用对象的valueOf()方法,然后依照前面的规则转换返回的值。如果转换的结果是NaN,则调用对象的toString()方法,然后再依照前面的规则转换返回的字符串值。
二:parseInt()

处理整数的时候parseInt()更常用。parseInt()函数在转换字符串时,会忽略字符串前面的空格,知道找到第一个非空格字符。

如果第一个字符不是数字或者负号,parseInt() 就会返回NaN,同样的,用parseInt() 转换空字符串也会返回NaN。 如果第一个字符是数字字符,parseInt() 会继续解析第二个字符,直到解析完所有后续字符串或者遇到了一个非数字字符。

parseInt()方法还有基模式,可以把二进制、八进制、十六进制或其他任何进制的字符串转换成整数。

基是由parseInt()方法的第二个参数指定的,所以要解析十六进制的值,当然,对二进制、八进制,甚至十进制(默认模式),都可以这样调用parseInt()方法。

var num1 = parseInt("AF",16);     //175
var num2 = parseInt("AF");      //NaN
var num3 = parseInt("10",2);     //2  (按照二进制解析)
var num4 = parseInt("sdasdad");   //NaN

三:parseFloat()

 与parseInt() 函数类似,parseFloat() 也是从第一个字符(位置0)开始解析每一个字符。也是一直解析到字符串末尾,或者解析到遇见一个无效的浮点数字字符为止。

 也就是说,字符串中第一个小数点是有效的,而第二个小数点就是无效的了,它后面的字符串将被忽略。

 parseFloat() 只解析十进制,因此它没有第二个参数指定基数的用法

 如果字符串中包含的是一个可解析为正数的数(没有小数点,或者小数点后都是零),parseFloat() 会返回整数。

var num1 = parseFloat("123AF");           //123
var num2 = parseFloat("0xA");            //0
var num3 = parseFloat("22.5");            //22.5
var num4 = parseFloat("22.3.56");         //22.3
var num5 = parseFloat("0908.5");          //908.5

parseInt() 和parseFloat() 的区别在于:

  • parseFloat() 所解析的字符串中第一个小数点是有效的,而parseInt() 遇到小数点会停止解析,因为小数点并不是有效的数字字符。
  • parseFloat() 始终会忽略前导的零,十六进制格式的字符串始终会被转换成0,而parseInt() 第二个参数可以设置基数,按照这个基数的进制来转换。

null 和 undefined 有什么区别

Undefined类型只有一个值,即undefined。当声明的变量还未被初始化时,变量的默认值为undefined。用法:

  • 变量被声明了,但没有赋值时,就等于undefined。
  • 调用函数时,应该提供的参数没有提供,该参数等于undefined。
  • 对象没有赋值的属性,该属性的值为undefined。
  • 函数没有返回值时,默认返回undefined。

Null类型也只有一个值,即null。null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。用法

  • 作为函数的参数,表示该函数的参数不是对象。
  • 作为对象原型链的终点。

JS数据类型:null 不存在的原因是什么?如何解决?

不存在的原因是:

            1、方法不存在

            2、对象不存在

            3、字符串变量不存在

            4、接口类型对象没初始化 

解决方法:

            做判断处理的时候,放在设定值的最前面

== 和 === 有什么区别,什么场景下使用?

== 表示相同。

        比较的是物理地址,相当于比较两个对象的 hashCode ,肯定不相等的。

        类型不同,值也可能相等。

=== 表示严格相同。

        例:同为 nullundefined ,相等。

简单理解就是 == 就是先比较数据类型是否一样。=== 类型不同直接就是 false

判断数据类型的方法

在写业务逻辑的时候,经常要用到JS数据类型的判断,面试常见的案例深浅拷贝也要用到数据类型的判断。

typeof

console.log(typeof 2);               // number
console.log(typeof true);            // boolean
console.log(typeof 'str');           // string
console.log(typeof undefined);       // undefined
console.log(typeof []);              // object 
console.log(typeof {});              // object
console.log(typeof function(){});    // function
console.log(typeof null);            // object
复制代码

优点:能够快速区分基本数据类型 缺点:不能将Object、Array和Null区分,都返回object

instanceof

console.log(2 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基本数据类型不能判断

Object.prototype.toString.call()

var toString = Object.prototype.toString;
 
console.log(toString.call(2));                      //[object Number]
console.log(toString.call(true));                   //[object Boolean]
console.log(toString.call('str'));                  //[object String]
console.log(toString.call([]));                     //[object Array]
console.log(toString.call(function(){}));           //[object Function]
console.log(toString.call({}));                     //[object Object]
console.log(toString.call(undefined));              //[object Undefined]
console.log(toString.call(null));                   //[object Null]

优点:精准判断数据类型 缺点:写法繁琐不容易记,推荐进行封装后使用、

扩展:判断数组的6种方法

instanceof 操作符判断
用法:arr instanceof Array
②对象构造函数的 constructor判断
用法:arr.constructor === ArrayArray 原型链上的 isPrototypeOf
用法:Array.prototype.isPrototypeOf(arr)
④Object.getPrototypeOf
用法:Object.getPrototypeOf(arr) === Array.prototypeObject.prototype.toString
用法:Object.prototype.toString.call(arr) === '[object Array]'Array.isArray
用法:Array.isArray(arr)

数组API

push、pop、unshift、shift、reduce、splice、slice、concat、from、map、join、reverse、indexOf、includes、forEach。
push、unshift返回修改后的数组长度,pop、shift返回被删除的元素
splice改变原始数组,slice返回新数组

定义函数的方法

  1. 函数声明
//ES5
function getSum(){}
function (){}//匿名函数
//ES6
()=>{}
复制代码
  1. 函数表达式
//ES5
var getSum=function(){}
//ES6
let getSum=()=>{}
复制代码
  1. 构造函数
const getSum = new Function('a', 'b' , 'return a + b')
复制代码

new操作符的实现步骤

  1. 创建一个对象
  2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
  3. 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)
  4. 返回新的对象

script 引入方式:

  • html 静态<script>引入
  • js 动态插入<script>
  • <script defer>: 延迟加载,元素解析完成后执行
  • <script async>: 异步加载,但执行时会阻塞元素渲染

JS 中 this 的五种情况

  1. 作为普通函数执行时,this指向window
  2. 当函数作为对象的方法被调用时,this就会指向该对象
  3. 构造器调用,this指向返回的这个对象
  4. 箭头函数 箭头函数的this绑定看的是this所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。
  5. 基于Function.prototype上的 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,bind方法通过传入一个对象,返回一个this 绑定了传入对象的新函数。这个函数的 this指向除了使用new 时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。

数组去重

let arr = [1,'1',2,'2',1,2,'x','y','f','x','y','f'];
ES6方法:let uniq=[...new Set(...arr)];
function unique1(arr){
	let result = [arr[0]];
	for (let i = 1; i < arr.length; i++) {
		let item = arr[i];
		if(result.indexOf(item) == -1){
			result.push(item);
		}
	}
	return result;
}
console.log(unique1(arr));
复制代码

更多JS去重的方法JS数组去重

call,apply和bind区别

三个函数的作用都是将函数绑定到上下文中,用来改变函数中this的指向;三者的不同点在于语法的不同。

fun.call(thisArg[, arg1[, arg2[, ...]]])
fun.apply(thisArg, [argsArray])
复制代码

所以applycall的区别是call方法接受的是若干个参数列表,而apply接收的是一个包含多个参数的数组。

而bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

var bindFn = fun.bind(thisArg[, arg1[, arg2[, ...]]])
bindFn()
复制代码

Demos:

var name = 'window';
var sayName = function (param) {
    console.log('my name is:' + this.name + ',my param is ' + param)
};
//my name is:window,my param is window param
sayName('window param')

var callObj = {
    name: 'call'
};
//my name is:call,my param is call param
sayName.call(callObj, 'call param');


var applyObj = {
    name: 'apply'
};
//my name is:apply,my param is apply param
sayName.apply(applyObj, ['apply param']);

var bindObj = {
    name: 'bind'
}
var bindFn = sayName.bind(bindObj, 'bind param')
//my name is:bind,my param is bind param
bindFn();
复制代码

==和===区别

  • ==, 两边值类型不同的时候,要先进行类型转换,再比较
  • ===,不做类型转换,类型不同的一定不等。

==类型转换过程:

  1. 如果类型不同,进行类型转换
  2. 判断比较的是否是 null 或者是 undefined, 如果是, 返回 true .
  3. 判断两者类型是否为 string 和 number, 如果是, 将字符串转换成 number
  4. 判断其中一方是否为 boolean, 如果是, 将 boolean 转为 number 再进行判断
  5. 判断其中一方是否为 object 且另一方为 string、number 或者 symbol , 如果是, 将 object 转为原始类型再进行判断

经典面试题:[] == ![] 为什么是true

转化步骤:

  1. !运算符优先级最高,![]会被转为为false,因此表达式变成了:[] == false
  2. 根据上面第(4)条规则,如果有一方是boolean,就把boolean转为number,因此表达式变成了:[] == 0
  3. 根据上面第(5)条规则,把数组转为原始类型,调用数组的toString()方法,[]转为空字符串,因此表达式变成了:'' == 0
  4. 根据上面第(3)条规则,两边数据类型为string和number,把空字符串转为0,因此表达式变成了:0 == 0
  5. 两边数据类型相同,0==0为true

深拷贝和浅拷贝

浅拷贝是地址引用,修改对象中的对象或者数组会影响原始对象。
深拷贝是内存拷贝,修改对象中的所有值不会影响原始对象。 image.png

  • 浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响

    • Object.assign
    • 展开运算符(...)
  • 深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响

    • JSON.parse(JSON.stringify(obj)): 性能最快

      • 具有循环引用的对象时,报错
      • 当值为函数、undefined、或symbol时,无法拷贝
    • 递归进行逐一赋值

浅拷贝

function simpleClone(obj) {
    var result = {};
    for (var i in obj) {
        result[i] = obj[i];
    }
    return result;
}
复制代码

深拷贝,遍历对象中的每一个属性

function deepClone(obj) {
    let result;
    if (typeof obj == 'object') {
        result = isArray(obj) ? [] : {}
        for (let i in obj) {
            result[i] = isObject(obj[i])||isArray(obj[i])?deepClone(obj[i]):obj[i]
        }
    } else {
        result = obj
    }
    return result
}
function isObject(obj) {
    return Object.prototype.toString.call(obj) == "[object Object]"
}
function isArray(obj) {
    return Object.prototype.toString.call(obj) == "[object Array]"
}
复制代码

防抖节流原理、区别、应用场景及代码实现

防抖:是指触发事件后,函数在 n 秒后只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数的执行时间。

简单的说,当一个函数连续触发,只执行最后一次。

  • 应用场景:
  1. 搜索框搜索输入。只需用户最后一次输入完,再发送请求;
  2. 用户名、手机号、邮箱输入验证;
  3. 浏览器窗口大小改变后,只需窗口调整完后,再执行resize事件中的代码,防止重复渲染。

节流:是指触发事件后,,限制一个函数在一段时间内只能执行一次,过了这段时间,在下一段时间又可以执行一次。

  • 应用场景:
  1. 输入框的联想,可以限定用户在输入时,只在每两秒钟响应一次联想。
  2. 搜索框输入查询,如果用户一直在输入中,没有必要不停地调用去请求服务端接口,等用户停止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力。
  3. 表单验证
  4. 按钮提交事件。

debounce_throttle.png

防抖

function debounce(fn, delay) {
  let timer = null;
  return function () {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, delay);
  }
}
复制代码

节流

function throttle(fn, cycle) {
  let start = Date.now();
  let now;
  let timer;
  return function () {
    now = Date.now();
    clearTimeout(timer);
    if (now - start >= cycle) {
      fn.apply(this, arguments);
      start = now;
    } else {
      timer = setTimeout(() => {
        fn.apply(this, arguments);
      }, cycle);
    }
  }
}

复制代码

ajax和axios、fetch的区别

1.传统 Ajax 指的是 XMLHttpRequest(XHR), 最早出现的发送后端请求技术,隶属于原始js中,多个请求之间如果有先后关系的话,就会出现回调地狱。JQuery ajax 是对原生XHR的封装,除此以外还增添了对JSONP的支持。
2.axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范,它本身具有以下特征:
1.从浏览器中创建 XMLHttpRequest
2.支持 Promise API
3.客户端支持防止CSRF
4.提供了一些并发请求的接口(重要,方便了很多的操作)
5.从 node.js 创建 http 请求
6.拦截请求和响应
7.转换请求和响应数据
8.取消请求
9.自动转换JSON数据
PS:防止CSRF:就是让你的每个请求都带一个从cookie中拿到的key, 根据浏览器同源策略,假冒的网站是拿不到你cookie中得key的,这样,后台就可以轻松辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略。

3.fetch号称是AJAX的替代品,是在ES6出现的,使用了ES6中的promise对象。Fetch是基于promise设计的。Fetch的代码结构比起ajax简单多了,参数有点像jQuery ajax。但是,一定记住fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象
fetch的优点:
1.符合关注分离,没有将输入、输出和用事件来跟踪的状态混杂在一个对象里
2.更好更方便的写法

axios的原生实现方式

get(url){
        return new Promise((resolve => {
            let xhr = new XMLHttpRequest();
            
            xhr.onload = function() {
                resolve({
                    data: JSON.parse(xhr.responseText),
                    status: xhr.status,
                    statusText: xhr.statusText
                });
            }
            
            xhr.open( 'get', url , true );
            xhr.send();
        }))
    }

cookie,sessionStorage和localStorage

  • cookie用来保存登录信息,大小限制为4KB左右
  • localStorage是Html5新增的,用于本地数据存储,保存的数据没有过期时间,一般浏览器大小限制在5MB
  • sessionStorage接口方法和localStorage类似,但保存的数据的只会在当前会话中保存下来,页面关闭后会被清空。
名称生命期大小限制与服务器通信
cookie一般由服务器生成,可设置失效时间。如果在浏览器端生成Cookie,默认是关闭浏览器后失效4KB每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题
localStorage除非被清除,否则永久保存5MB仅在浏览器中保存,不与服务器通信
sessionStorage仅在当前会话下有效,关闭页面或浏览器后被清除5MB仅在浏览器中保存,不与服务器通信

0.1+0.2!=0.3怎么处理

把需要计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完成后再进行降级(除以10的n次幂),即:

(0.1*10 + 0.2*10)/10 == 0.3 //true
复制代码

对于这个问题,一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3

function numberepsilon(arg1,arg2){                   
  return Math.abs(arg1 - arg2) < Number.EPSILON;        
}        

console.log(numberepsilon(0.1 + 0.2, 0.3)); // true

更多关于浮点数精度处理请看JS中浮点数精度问题

DOM事件流三阶段

一个事件触发后,会在子元素和父元素之间传播,这个传播分三个阶段:- (1)事件捕获阶段 (2)处于目标阶段 (3)事件冒泡阶段

  • 1.捕获阶段:从window对象依次向下传播,到达目标节点,即为捕获阶段。捕获阶段不会响应任何事件
  • 2.目标阶段:在目标节点触发事件,即为目标阶段
  • 3.冒泡阶段:从目标阶段依次向上传播,到达window对象,即为冒泡阶段。【事件代理就是利用事件冒泡的机制把里层元素需要响应的事件绑定到外层元素身上】

EventLoop 事件循环

JS是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。微任务队列的代表就是,Promise.thenMutationObserver,宏任务的话就是setImmediate setTimeout setInterval。 先同步后异步,先微任务后宏任务。

  • 浏览器中的任务源(task):

    • 宏任务(macrotask)
      宿主环境提供的,比如浏览器
      ajax、setTimeout、setInterval、setTmmediate(只兼容ie)、script、requestAnimationFrame、messageChannel、UI渲染、一些浏览器api
    • 微任务(microtask)
      语言本身提供的,比如promise.then
      then、queueMicrotask(基于then)、mutationObserver(浏览器提供)、messageChannel 、mutationObersve

传送门 ☞ # 宏任务和微任务

Node 的 Event Loop: 6个阶段

  • timer 阶段: 执行到期的setTimeout / setInterval队列回调

  • I/O 阶段: 执行上轮循环残流的callback

  • idle, prepare

  • poll: 等待回调

      1. 执行回调
      1. 执行定时器

      • 如有到期的setTimeout / setInterval, 则返回 timer 阶段
      • 如有setImmediate,则前往 check 阶段
  • check

    • 执行setImmediate
  • close callbacks