1、介绍一下js的数据类型有哪些,值是如何存储的
javaScript一共有8种数据类型,其中7种基本数据类型: Undefined、 Null、 Boolean、 Number、 String、 Symbol(es6新增。表示独一无二)和Biglnt(es10新增)
一种引用数据类型Object(本质上是由一组无序的键值对组成)。里面包含 function、Array、Data等
原始数据类型直接存放在栈中,占据空间小、大小固定。属于被频繁使用数据,所以放入栈中存储
引用数据类型: 同时存储在栈和堆中,占据空间大、大小不固定,引用数据类型在栈中存储了指针
2、&& 、|| 和 !! 运算符分别能做什么
- && 叫逻辑与,在其操作数中找到第一个虚值表达式并返回它,如果没有找到 任何虚值表达式,则返回最后一个真值表达式,它采用短路来防止不必要的返回
- || 叫逻辑或,在其操作数中找到第一个真值表达式并返回它。这也使用了短路来防止不必要的工作,在支持ES6默认函数参数之前,它用于初始化函数中的默认值
- !! 运算符可以将右侧的值强制转换为布尔值
3、js的数据类型转换
- 转换为布尔值(调用Boolean())
- 转换为数字(调用Number()、parselnt() 和 parseFloat())
- 转换成字符串(调用.toString()或者String())
4、JS中的数据类型的判断(typeof,instanceof,constructor,Object.prototype.toString.call())
- typeof对于对象来说,除了函数都会显示object,所以说typeof并不能判断变量到底是什么类型,正确判断一个对象的正确类型,就要用到instanceof
- instanceof可以正确判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的prototype
- Object.prototype.toString.call() 使用Object对象的原型方法toString
5、null 和 undefined的区别
- 都是基本数据类型
- undefined 代表的含义是未定义,null代表的含义是空对象,一般变量生命了但还没有定义的时候就返回undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化
6、{}和[] 的valueOf和toString的结果是什么
- {}的valueOf 结果为 {},toString的结果为"[object object]"
- []的valueOf 结果为 [], toString的结果为 " "
7、javaScript的作用域和作用域链
- 作用域: 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来滚利浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量进行变量查找
- 作用域链:作用域链的作用是保证对执行环境有访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和函数,本质上来说就是一个指向变量的指针列表
8、javaScript创建对象的几种方式?
- 第一种: 工厂模式,工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用
- 第二种: 构造函数模式,js中每一个函数都可以作为构造函数,只要一个函数通过new来调用的
- 第三种: 原型模式,每一个函数都有一个prototype属性,这个属性是一个对象
9、谈谈对this、call、apply和bind的理解
- 在浏览器里,全局范围内this指向window对象
- 在函数中,this永远指向最后抵用他的那个对象上
- 在构造函数中,this指向new出来的新对象
- call、apply、bind中的this被强行绑定在指定的那个对象上
- 箭头函数中的this比较特殊,声明的时候就已经确定下来了,指向父作用域
10、JavaScript原型、原型链
- 使用构造函数新建一个对象,每个构造函数都会有prototype属性值,这个属性值是一个对象,它包含了该构造函数所有的属性和方法,新建一个对象的内部就会有一个指针指向prototype属性对应的值,我们称这个指针为对象的原型。我们可以通过Object.getPrototypeOf()方法来获取对象的原型。
- 当我们访问一个对象属性时,如果对象的内部不存在这个属性,那么他就会去他的原型对象查找,原型对象又会有自己的原型,这样查找下去也就是原型链的概念
- 特点:javascript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一分属于自己的原型副本,当我们修改原型时,与之相关的对象也会继承这一改变
11、什么是闭包,为什么要用它?
闭包就是指有权访问另一个函数作用域内变量的函数,常见的创建闭包的方式就是在一个函数背部创建子函数,它可以访问父级函数的局部变量
- 闭包可能会造成内存泄漏,用完及时清除
12、什么是DOM什么是BOM?
DOM指的是文档对象模型,指的是把文档当做一个对象来算,这个对象主要定义了处理网页内容的方法和接口
BOM指的是浏览器对象模型,指的是把浏览器当做一个对象来看,这个对象主要定义了与浏览器进行交互的方法和接口。BOM的核心是window,DOM的最根本的对象document对象也是BOM的window对象的子对象
13、什么是事件委托?
事件委托本质上是利用了浏览器事件的冒泡机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式成为事件代理。
14、事件传播
当事件发生在DOM元素上时,该事件并不完全发生在那个元素上
有三个阶段:
- 捕获阶段:事件从window开始,然后向下到每个元素,知道到达目标元素事件或者event.target
- 目标阶段:事件已到达目标元素
- 冒泡阶段:事件从目标元素冒泡,然后上升到每个元素,直到到达window
15、什么是事件捕获
window——document——html——body——目标元素
16、事件冒泡
当前元素——body——html——document——window
17、DOM操作——怎么添加、移除、移动、复制、创建和查找节点
- 创建新节点
careateDocumentFragment() // 创建一个DOM片段 createElement() // 创建一个具体的元素 careateTextNode() // 创建一个文本节点
- 添加、移除、替换、插入
appendChild(node) removeChild(node) replaceChild(new,old) insertBefore(new,old)
- 查找
getElementsByName(); getElementsByTagName(); getElementsByClassName(); querySelector(); querySelectorAll();
- 属性操作
getAttribute(key); setAttribute(key, value); hasAttribute(key); removeAttribute(key);
18、js数组和对象的方法
- 数组方法
- push:向数组的末尾添加一个或多个元素,并返回新的长度,
- shift:删除数组的第一个元素,返回第一个元素的值
- unshift:数组开头添加一个或者多个元素,并返回新的长度
- pop:删除并返回数组的最后一个元素
- splice:用于插入、删除或者替换数组的元素
- concat:用于连接两个或者多个数组
- join:用于把数组中的所有元素放在一个字符串,通过指定的分隔符进行分隔
- toString:把数组转换成字符串,并返回结果
- reverse:用于颠倒数组中元素的顺序
- slice:从已有的数组中返回选定的元素
- sort:对数组中的元素进行排序
- indexOf: 返回获取项在数组中的索引
- lastIndexOf: 返回获取项在数组中的最后一次索引
- forEach: 循环遍历数组, 参数是一个匿名函数,默认返回undefined
- map:循环遍历,参数是一个匿名函数
- 对象方法
- charAt:返回指定位置的字符
- charCodeAt: 返回在指定位置的字符的UniCode编码
- concat: 连接字符串
- indexOf: 检索字符串
- match:找到一个或者多个正则表达式的匹配
- replace:替换与正则表达式匹配的字段
- search:检索与正则表达式相匹配的值
- slice:提取字符串片段,并在新的字符串返回被提取的值
- split:字符串分割成数组
- toLocaleLowerCase: 字符串转成小写
- toLocaleUpperCase:字符串转换成大写
- toLowerCase:字符串转换成小写
- toUpperCase: 字符串转换成大写
- substr: 从起始索引号提取字符串到指定书目的字符
- substring: 提取两个索引号之间的字符
19、正则表达式
//(1)匹配 16 进制颜色值 var color = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g; //(2)匹配日期,如 yyyy-mm-dd 格式 var date = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/; //(3)匹配 qq 号 var qq = /^[1-9][0-9]{4,10}$/g; //(4)手机号码正则 var phone = /^1[34578]\d{9}$/g; //(5)用户名正则 var username = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/; //(6)Email正则 var email = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/; //(7)身份证号(18位)正则 var cP = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/; //(8)URL正则 var urlP= /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/; // (9)ipv4地址正则 var ipP = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; // (10)//车牌号正则 var cPattern = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/; // (11)强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间): var pwd = /^(?=.\d)(?=.[a-z])(?=.[A-Z]).{8,10}$/
20、Ajax是什么?如何创建一个Ajax?
ajax是一种异步通讯的方法,通过直接由js脚本向服务器发起http通讯,然后根据服务器返回的数据,跟新网页的相应部分,而不用刷新整个页面
创建xhr对象——配置Ajax请求地址——发送请求——监听请求,接收响应
原生:
// 1:创建Ajax对象 var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP'); // 2: 配置Ajax请求地址 xhr.open('get', 'index.xml', true); // 3: 发送请求 xhr.send(null); // 严谨写法 // 4: 监听请求,接受响应 xhr.onreadysatechang = function () { if (xhr.readySates == 4 && xhr.status == 200 || xhr.status == 304) { console.log(xhr.responsetXML) } }
21、js延迟加载的方式有哪些?
- 将js脚本放在文档的底部,来使js脚本尽可能的在最后来加载执行
- 给js脚本添加async属性,使脚本异步加载,不会阻塞页面解析过程
- 动态创建DOM标签,对文档的加载进行监听,文档加载完毕再动态创建script标签来引入js脚本
22、模块化的理解
一个模块是实现一个特定的功能的一组方法,最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染
23、js的几种模块规范
- CommonJS:通过require来引入模块,通过module.exports定义模块的输出接口
- AMD:方案采用异步加载的方式加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里面,等加载完成后再执行回调函数
- CMD:解决异步模块加载问题,sea.js实现了CMD规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块执行时机的处理不同
- ES6提出使用import和export的形式来导入导出模块
24、requireJS的核心原理
通过动态创建script脚本来异步引入模块,然后对每个脚本的load事件进行监听,如果每个脚本都加载完成了,再调用回调函数
25、JS的运行机制
- js是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入栈中来保证代码的有序执行。
- 在执行代码的时候,如果遇到了异步事件,js引擎并不会等待返回的结果,而是将事件挂起,继续执行栈中的其他任务
- 当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行
- 任务队列可以分为宏任务和微任务队列,当前执行栈中的事件执行完毕后,js引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行
- 当微任务队列中的任务都执行完成后再去判断宏任务队列中的任务
26、arguments的对象是什么?
它是函数中传递的参数值得集合。它是一个类似数组的对象,因为它有一个length属性,我们可以使用数组索引表示法arguments[]来访问单个值,但它没有数组中的内置方法
27、哪些操作会造成内存泄漏
- 意外的全局变量
- 被遗忘的计时器或者回调函数
- 脱离DOM的引用
- 闭包
28、ECMAScript
它是编写脚本语言的标准,意味着JavaScript遵循ECMAScript标准中的规范变化,因为它是JavaScript的蓝图。
29、ECMAScript2015(ES6)有哪些新特性?
- 块级作用域
- 类
- 箭头函数
- 模板字符串
- 加强的对象字面
- 对象解构
- Promise
- 模块
- symbol
- 代理(proxy)Set
- 函数默认参数
- rest和展开
30、var,let和const的区别
var声明的变量会挂载在window上,let和const不会
var声明会存在变量提升,let和const不会
let和const声明形成块级作用域
同一作用域下let和const不能声明同名变量,var可以
暂存死区
var a = 100; if(1){ a = 10; //在当前块作用域中存在a使用let/const声明的情况下, // 给a赋值10时,只会在当前作用域找变量a, // 而这时,还未到声明时候,所以控制台Error:a is not defined let a = 1; }
const 一旦声明必须赋值,不能使用null站位,声明后不能修改,如果声明的是符合类型数据,可以修改其属性
31、箭头函数
箭头函数表达式语法比函数表达式简洁,并且没有自己的this、arguments、super或者new.target,箭头函数表达式更适用于哪些本来需要匿名函数的地方,并且它不能用作构造函数
32、什么是类?
它是使用构造函数的语法糖,在底层中使用仍然是原型和基本原型的继承
33、什么是set对象
set对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用
我们都可以使用Set构造函数创建Set实例
34、什么是map对象?
它类似于对象,也是键值对的集合,包括各种类型的值都可以当做键
35、什么是Proxy
用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程
可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,
36、实现 Array.prototype.map方法
map方法创建一个新数组,结果是该数组中的每一个元素都调用一个提供的函数后返回的结果
function map(arr, mapCallback) { // 先检查传递的参数是否正确 if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') { return []; } else { let result = []; // 每次调用此函数时,我们会创建一个result数组 // 因为我们不想改变原始数组 for (let i = 0; i < arr.length; i++) { result.push(mapCallback(arr[i], i, arr)); // 将mapCallback返回的结果push到result数组中 } return result; } }
37、手动实现Array.prototype.filter方法
filter() 方法创建一个新数组,其中包含通过所提供函数实现的测试的所有元素
function filter(arr, mapCallback) { // 先检查传递的参数是否正确 if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') { return []; } else { let result = []; // 每次调用此函数时,我们会创建一个result数组 // 因为我们不想改变原始数组 for (let i = 0; i < arr.length; i++) { result.push(mapCallback(arr[i], i, arr)); // 将mapCallback返回的结果push到result数组中 } return result; } }
38、js深浅拷贝
- 浅拷贝: 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝,如果属性是基本类型,拷贝的就是基本数据类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象
- 深拷贝: 讲一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
浅拷贝的实现方式
- Object.assign()方法: 用于将所有可枚举属性的值从一个或多个原对象复制到目标对象。它将返回目标对象
- Array.prototype.slice(): 返回一个新数组对象,这一对象是由begin和end决定的原数组的浅拷贝,原始数组不会改变
- 扩展运算符:...
深拷贝的实现方式:
- JSON.parse(JSON.stringify(object))
- 基础版
function cloneDeep(target, map = new WeakMap()) { if (typeOf taret === 'object') { let cloneTarget = Array.isArray(target) ? [] : {}; if (map.get(target)) { return target; } map.set(target, cloneTarget); for(const key in target) { clonetarget[key] = cloneDeep(target[key], map); } return cloneTarget } else { return target } }
39、new一个对象的过程
- 创建一个空的简单的对象
var obj = {}
- 链接该对象(设置该对象的构造函数)到另一个对象
obj.__proto__ = Dog.protoype
- 将新创建的对象作为this的上下文
Dog.apply(obj, ['大黄', 'yellow', 3])
- 如果该函数没有返回对象,则返回this。
var dog = obj dog.getName() // '大黄'
40、什么是Promise?
承诺它过段时间会给你一个结果
一个Promise有以下几种状态:
- pending:初始状态,既不是成功,也不是失败
- fulfilled: 意味着操作成功完成
- rejected: 意味着操作失败
一旦从等待状态变成其他状态就永远不能改变
手写Promise
function myPromise(constructor) { let self = this; self.status = "pending" // 定义状态改变前的初始状态 self.value = undefined; // 定义状态为resolved的时候的状态 self.reason = undefined; // 定义状态为rejected的时候的状态 function resolve(value) { // 两个 === "pending",保证了状态的改变是不可逆的 if (self.status === 'pending') { self.value = value; self.status = "resolved"; } } function reject(reason) { // 两个 === "pending", 保证了状态的改变是不可逆的 if (self.status === "pending") { self.reason = reason; self.status = "rejected"; } } // 捕获构造异常 try{ constructor(resolve, reject); } catch (e) { reject(e); } } // 定义链式调用的then方法 myPromise.prototype.then = function(onFullfilled, onRejected) { let self = this; switch(self.status) { case "resolved": onFullfilled() } }
41、js的节流与防抖
函数防抖: 是指在事件被触发n秒后再执行回调,如果在这n秒内事件又被触发,则重新计时,这可以使用在一些点击请求事件上,避免因为用户的多次点击向后端发送多次请求
函数节流: 在规定的一个单位时间,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效,节流可以使用scroll函数的事件监听上,通过事件节流来讲题事件调用的频率。
// 函数防抖的实现 function debounce(fn, wait) { var timer = null; return function() { var context = this, args = arguments; // 如果此时存在定时器的话,则取消之前的定时器重新计时 if(timer) { clearTimerout(timer); timer = null; } //设置定时器,使事件间隔指定事件后执行 timer = setTimer(() => { fn.apply(context, args); }, wait) } } // 函数节流的实现 function throttle(fn, delay) { var preTime = Date.now(); return function () { var context = this, args = arguments, nowTime = Date.now(); // 如果两次时间间隔都超过了指定时间,则执行函数。 if (nowTime - preTime >= delay) { preTime = Date.now(); return fn.apply(context, args); } }; }