1.JS访问内部数据变量的方式与哪些以及使用场景?
读取和设置对象属性的方式主要有两种:
(.) 方法: obj.attr
([])方法: obj[attr]
.语法是我们平时经常使用的方式,
[]的方式通常再以下场景下使用:
- 属性名包含特殊字符:- 空格等
- 变量名不确定([]内可以直接加一个变量,或者一个字符串)
使用方式:
let p={}
let address='address'
p.name='name'
p['age']=12
p[address]='安静'
console.log(p)
复制代码
2. Javascript函数调用的方式有哪四种?
- 直接调用函数(通过window)
- 通过对象调用
- new 调用(使用构造函数调用函数)
- 通过bind/call/apply调用
3.new操作符的实现原理
要想知道new操作符的原理,就得先了解new的作用是什么,我们都知道,一般将我们的构造函数实例化为对象时就要使用new操作符。
要想真正掌握并手写一个new,你需要掌握以下知识点:原型与原型链以及this绑定相关知识。
其实new的内部流程十分简单,大概就是这么几个步骤:
- 创建一个新的对象obj
- 将对象的隐式原型指向构造函数的原型对象
- 通过apply调用构造函数(改变this指向到obj)
- 返回obj,如果构造函数有返回值还需判断,如果返回值为引用类型则返回引用类型对象,如果为原始类型就返回obj
手写:
function MyNew() {
// 获取到形参列表,拿到构造函数以及对应参数
let [ArgFun, ...arg] = [...arguments]
// 定义一个空的对象
let obj = {};
// 原型指向
obj.__proto__ = ArgFun.prototype;
// 设置this指向
let tartgetObj = ArgFun.apply(obj, arg)
// 通过apply调用构造函数(改变this指向到obj)
return tartgetObj instanceof Object == true ? tartgetObj : obj
}
复制代码
使用:
function Person(name, age) {
this.name = name;
this.age = age;
console.log('执行')
return '12'
}
let zhangsan = MyNew(Person, '张三', 12)
console.log(zhangsan)
复制代码
4.数组有哪些原生的方法
- pop()和push()尾部操作,删除和增加
- shift()和unshift()首部操作,删除和增加
- sort()排序,里面跟一个函数,带两个参数,返回值是两参数相减,正序详见为升序,反序相减为降序,返回值为数组,会改变数组
- reserve()反转数组,也返回数组,会改变数组
- concat(),拼接两个数组,返回拼接后的数组,不改变原数组
- slice(),截取数组的一部分并返回,不影响原数组
- splice(),可以删除或者替换或者插入数组中的某一个或多个元素,返回数组,改变原数组
- filter()函数,过滤数组,返回过滤后的数组,不改变原数组
- map()函数,遍历数组,不改变原数组
- reduce(),汇总,可以操作每一个数组元素,参数为一个函数和一个默认值,函数有两个参数,分别代表上一次计算的结果个本次的值,默认值就是第一次循环函数第一个参数的值。
5.什么是DOM和BOM
回答这个问题可以将ECMAScript也纳入。
JavaScript是由三部分构成的,分别是ECMAScript、DOM和BOM。它们的功能和所负责的任务也是不一样的:
ECMAScript:规定了语言的基础,比如语法、关键字、操作符等等,规定了语言的标准,比如ES5,ES6等
DOM:文档对象模型,提供了访问和操作网页内容的方法和接口。DOM把整个页面映射成立一个多层节点的结构,每个节点被视为一个对象,最终形成一个DOM树。我们要想获取某个节点的对象,只需要一层一层的拿到对象再去操作就可以。
BOM:浏览器对象模型,提供了与浏览器之间的的方法和接口。BOM的核心是window对象。window下面还提供了一些操作浏览器的api,比如history属性,navigator和location对象等。
注:window有一个双重身份,它既是js操作浏览器窗口的一个接口,也是全局的global对象,可以操作网页中任何一个变量对象等
6.对类数组(伪数组)的的理解,如何转换为数组
类数组就是伪数组,是一种类似数组的对象,拥有length属性和若干索引属性的对象,但是不可以调用数组的方法
常见的类数组对象有argument和DOM方法返回的结果。
我们常见的将类数组转换为数组的方法有这些:
1.通过call调用数组的slice方法:
Array.prototype.slice.call(arrayLike);
复制代码
2.通过call调用数组的splice方法:
Array.prototype.splice.call(arrayLike, 0);
复制代码
3.通过call调用数组等concat方法
Array.prototype.splice.call(arrayLike, 0)
复制代码
4.通过 Array.from 方法来实现转换
Array.from(arrayLike);
复制代码
5.也可以使用es6扩展运算符
7.为什么要使用尾调用?
首先尾调用简单来说就是在一个函数执行结束时,调用另一个函数。我们知道函数的执行是基于执行栈的,所以我们当前函数的最后一步出栈,然后再去调用另外一个函数。再将这个函数入栈,这样做的好处是我们不用保留当前函数的执行栈,从而节省了内存,这就是尾调用优化
8.for…in和for…of的区别
- for in 获取到的是对象的键名,for of获取到的是对象的键值
- for in会遍历对象的原型链,性能较差不推荐使用,而for of只遍历当前对象不会遍历原型链
- 对于数组的遍历,for in会返回数组中所有可枚举的属性,for of会返回数组线标的对应值。
总结:for in主要是用于遍历对象,不适用于遍历数组,for of 循环可以遍历对象、数组、类数组对象等。
9.对Ajax的理解,实现一个Ajax请求
指的是JavaScript的异步通信,异步请求数据的技术,从服务器获取到json数据,再将其更新到整个页面,并且它不会刷新整个页面。这也是Ajax最大的优点。
Ajax的核心是XmlHttpRequest对象,JavaScript可以使用XmlHttpRequest对象发出请求并且处理响应而不阻塞用户。
**工作原理:**我认为Ajax就是浏览器与服务器 之间的一个中间层,客户端发送请求,请求交给xhr对象,xhr把请求交给服务器,服务器进行业务处理,服务器又将响应的数据交给xhr,再有JavaScript写入页面。
创建Ajax请求的步骤:
- 创建XmlHttpRequest对象
- 创建一个Http请求(可以设置请求方式,请求地址,是否异步)
- 添加一些信息和监听函数(比如添加请求头信息和监听xhr的状态)
- 发送请求(post时可传入参数)
我们下面使用回调函数的方式来封装一个网络请求的函数:
function getData(url, hander) {
let xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return
if (this.status == 200) {
hander(this.response)
} else {
console.error(this.statusText)
}
}
xhr.onerror = function () {
console.log(this.statusText)
}
xhr.setRequestHeader("Accept", "application/json")
xhr.send();
}
复制代码
使用方式:
getData('http://httpbin.org/get', (data) => {
console.log(data)
})
复制代码
还可以使用promise封装:
function UseP(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if (this.readyState != 4) return
if (this.status == 200) {
resolve(this.response)
} else {
reject(this.statusText)
}
}
xhr.onerror = function () {
reject(this.statusText)
}
xhr.responseType = 'json'
xhr.setRequestHeader('Accept', 'application/json')
xhr.send()
})
}
复制代码
使用:
UseP('http://httpbin.org/get').then((res) => {
console.log(res)
}).catch((err) => {
console.log(err)
})
复制代码
10.Ajax、axios和fetch的区别
Ajax:
是一种无需重新加载网页的的情况下,能够更新部分网页的技术。通过后台与服务器进行少量的数据交换。Ajax可以实现网页的异步过呢更新操作。传统的网页如果需要更新内容需要重新加载整个网页。
Ajax有以下缺点:
- 本身是针对MVC编程,不符合前端MVVM的浪潮
- 基于原生XHR开发,而XHR本身的架构不清晰
- 不符合关注分离的原则
- 配置和调用比较混乱,而且基于事件的异步模型不友好
axios:
用于浏览器和nodejs的HTTP客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本。有以下特则:
- 从浏览器端发起XMLHttpRequest请求
- node端发起http请求
- 支持Promise API
- 客户端支持防止CSRF
- 提供了一些并发请求的接口
- 拦截请求和响应
- 转换请求和响应数据
- 取消请求
- 自动转为JSON格式
PS:防止CSRF:就是让你的每个请求都带一个从cookie中拿到的key, 根据浏览器同源策略,假冒的网站是拿不到你cookie中得key的,这样,后台就可以轻松辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略。
fetch:
Ajax的替代品。基于Promise设计。Fetch的代码结构比起ajax简单多了,参数有点像jQuery ajax。但是,一定记住fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。
fetch的优点:
- 语法简洁,更加语义化
- 基于标准的Promise实现,支持async/await
- 更加底层,提供的API丰富
- 脱离了XHR
缺点:
- 只对网络请求报错,对服务器返回的状态码不报错
- fetch默认不会带cookie,需要添加配置项:fetch(url, {credentials: ‘include’})
- fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
- fetch没有办法原生监测请求的进度,而XHR可以
11.Javascript脚本延迟加载的方法有哪些?
延迟加载就是等页面加载完成之后再去加载JavaScript 文件,js延迟加载有助于提高页面加载的速度
一般有以下几种方式:
- defer属性:给js脚本添加defer属性,这个属性会让脚本的加载和文档的解析同步执行,完后再文档解析完成之后再去执行这个脚本文件,这样的话可以使我们的页面的渲染不被阻塞。多个设置了defer属性的脚本按照规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样(关键字:同步,顺序执行)
- async属性:给js脚本添加async属性,这个属性会让脚本的加载和文档的解析异步执行,不会阻塞页面的解析过程,但是当脚本加载完成之后立即执行js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个async属性的脚本的顺序是不可预测的,一般不会按照代码的顺序依次执行。(关键字:异步,可能会阻塞,乱序)
- 动态创建DOM的方式:动态创建DOM标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建script标签来引入js脚本
- 使用setTimeout延迟:设置一个定时器来延迟加载JS脚本文件
- 将JS文件最后加载:将js脚本放在文档底部,使js脚本尽可能的再最后来加载执行
12.CommonJS和ES6模块的异同点?
区别:
- CommonJS是对模块的浅拷贝,ES6module是对模块的引用;即ES6不能改变值,指针的指向不能改变,相当于const
- import接口时read-only(只读状态),不能修改其变量值。即不能修改其变量的地址指向,但是可以改变变量内部的值。可以对CommonJS重新赋值(改变指针指向)
共同点:
CommonJS和ES6 Module都可以对引入的对象进行赋值,即对对象内部属性的值进行改变