js基础

148 阅读18分钟

js数据类型

JavaScript共有八种数据类型,分别是 UndefinedNullBooleanNumberStringObjectSymbolBigInt

其中 SymbolBigIntES6 中新增的数据类型:

  • Symbol 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
  • BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。

typeof的输出值有哪些?

number string boolean undefined object function

注意:null、数组和对象都会被判定为object

如何判断object和array类型?

instanceof

instanceof可以正确判断对象的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。instanceof只能正确判断引用数据类型,而不能判断基本数据类型。instanceof 运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

constructor
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true

constructor有两个作用,一是判断数据的类型,二是对象实例通过constructor对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了。

function Fn(){};
 
Fn.prototype = new Array();
 
var f = new Fn();
 
console.log(f.constructor===Fn);    // false
console.log(f.constructor===Array); // true
Object.prototype.toString.call()

Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型:

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

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

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

闭包的定义和特性?

闭包是指有权访问另一函数作用域中变量的函数。创建闭包最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。

闭包的用途:
  • 使我们能够在函数外部访问到函数内部的变量。
  • 使已经结束函数上下文中的变量继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。

作用域

作用域是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。

全局作用域:在代码中任何地方都能访问到的对象拥有全局作用域。1.最外层函数和在最外层函数外面定义的变量拥有全局作用域。2.所有未定义直接赋值的变量自动声明为拥有全局作用域;3.所有window对象的属性拥有全局作用域;

函数作用域:就是指声明在函数内部的变量。内层作用域可以访问到外层作用域,而外层作用域不能访问到内层作用域。

块级作用域:花括号{}内的区域就是块级作用域区域。

跨域的原因和解决方案

原因:

浏览器同源策略限制,同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到xsscsrf攻击。所谓同源是指“协议+域名+端口号”三者相同,即使两个不同的域名指向同一个IP地址,也非同源。

注意:
  • 如果是协议和端口造成的跨域问题‘前台’是无能为力的;
  • 在跨域问题上,仅仅是通过‘URL的首部’来识别而不会根据域名对应的IP地址是否相同来判断。‘URL首部’可以理解为协议、域名和端口必须匹配;
  • 跨域并不是请求发不出去,请求能发出去,服务器能收到请求并正常返回结果,只是结果被浏览器拦截了。同时也说明跨域并不能完全组织csrf,因为请求毕竟是发出去了。
解决方案:
jsonp

原理:利用<script>标签没有跨域限制的漏洞,网页可以得到从其它来源动态产生的JSON数据。jsonp请求一定要对方的服务器做支持才可以。

优缺点:优点是兼容性好,可用于解决主流浏览器的跨域数据访问的问题,缺点是仅支持get方法,具有局限性,不安全可能会遭受xss攻击。

CORS(Cross-Origin Resource Sharing)

CORS需要浏览器和后端同时支持。浏览器会自动进行CORS通信,实现CORS通信的关键是后端。只要后端实现了CORS,就实现了跨域。

服务端设置Access-Control-Allow-Origin就可以开启CORS,该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。

postMessage

postMessageHTML5 XMLHTTPRequest Level 2中的API,且是为数不多可以操作跨域的window属性之一,它可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递;
  • 多窗口之间消息传递;
  • 页面与嵌套的iframe消息传递;
  • 上面三个场景的跨域数据传递

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文档、多窗口、跨域消息传递。

otherWindow.postMessage(message, targetOrigin, [transfer]);

message:将要发送到其他window的数据; targetOrigin:通过窗口的orgin属性来指定哪些窗口能接收到消息事件,其值可以是字符串'*'(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配消息才会被发送。 transfer(可选):是一串和message同时传递的Transferable对象,这些对象的所有权将被转移给消息的接收方,而发送方将不再保有所有权。

websocket

websocketHTML5的一个持久化协议,它实现了浏览器与服务器的双向通信,同时也是跨域的一种解决方案。websockethttp都是应用层协议,都基于TCP协议。但是websocket是一种双向通信协议,再建立连接之后,websocketserverclient都能主动向对方发送或接收数据。同时,websocket在建立连接时需要借助http协议,连接建立好了之后clientserver直接的双向通信就与http无关了。

node中间件代理(两次跨域)

实际原理:服务器向服务器请求无需遵循同源策略

nginx反向代理

实现原理类似node中间件代理,需要你搭建一个中转ngnix服务器,用于转发请求。

window.name + iframe

window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的name值(2MB)。 通过iframesrc属性由外域转向本地域。跨域数据即由iframewindow.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

location.hash + iframe

原理:a.html欲与c.html跨域湘湖通信,通过中间页b.html来实现。三个页面,不同域之间利用iframelocation.hash传值,相同域之间直接js访问来通信。

document.domain + iframe

该方式只能用于二级域名相同的情况下,比如a.test.comb.test.com适用于该方式。只需要给页面添加document.domain='test.com'表示二级域名都相同就可以实现跨域。 原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

promise的原理

promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息。它的出现大大改善了异步编程的困境,避免了地狱回调,它比传统的解决方案回调函数和事件更合理和强大。

所谓promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上来说,promise是一个对象,从它可以获取异步操作的消息。promise提供统一的api,各种异步操作都可以用同样的方法进行处理。

promise实例有三种状态:
  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)
promise的实例有两个过程:
  • pending --> fulfilled
  • pending --> rejected 注意:
  • 一旦从进行状态变更为其它状态就永远不能变更状态了。
  • fulfilledrejected 统称 settledresolved 是指,Promise 已经 settled ,或者已经使用另一个 promise (B)resolve 了(此时 Promise 的状态将由 B 来决定,可能 pendingfulfilledrejected 的任何一种)。
  • resolve方法的执行结果只会是 resolved ,但是不一定是 fulfilled,也有可能最终转变为 rejected
promise的特点:
  • 对象的状态不受外界影响。promise对象代表一个异步操作,有三种状态,只有异步操作的结果,可以决定当前是哪一种状态,任何其它操作都无法改变这个状态,这也是promise这个名字的由来--承诺。
  • 一旦状态改变就不会再变,任何时候都可以得到这个结果。promise对象的状态改变只有两种可能,这就称为resolved(已定型)。如果改变已经发生了,再对promise对象添加回调函数,也会立即得到这个结果。这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。
promise的缺点:
  • 无法取消promise,一旦新建就会立即执行,无法中途取消。
  • 如果不设置回调函数,promise内部抛出的错误,不会反映到外部。
  • 当初与pending状态时,无法得知目前进展到哪一阶段(刚刚开始还是即将完成)。
总结:
  • promise对象是异步编程的一种解决方案,最早由社区提出。promise是一个构造函数,接收一个函数作为参数,返回一个promise实例。一个promise实例有三种状态,分别是pendingfulfilledrejected,代表了进行中、已成功、已失败。实例的状态只能由pending转变fulfilled或者rejected状态,并且状态一经改变,就凝固了,无法再被改变了。
  • 状态的改变是通过resolve()reject()函数来实现的,可以在异步操作结束后调用这两个函数改变promise实例的状态,它在原型上定义了一个then方法,使用这个then方法可以为两个状态的改变注册回调函数,这个回调函数属于微任务,会在本轮事件循环的末尾执行。

注意:在构造promise的时候,构造函数内部的代码是立即执行的。

this对象

this是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this的指向可以通过四种调用模式来判断。

1. 函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this指向全局对象。
2. 方法调用模式,如果一个函数作为一个对象的方法来调用时,this指向这个对象。
3. 构造器调用模式,如果一个函数用new调用时,函数执行前会新创建一个对象,this指向这个新创建的对象。
4. applycallbind调用模式,这三个方法都可以显式的指定调用函数的this指向。
  • apply()接收两个参数,第一个参数是this绑定的对象,一个是参数数组。
  • call()传入的参数数量不固定,第一个是this绑定的对象,后面其余的参数是传入函数执行的参数,也就是说,在使用call()方法时,传递给函数的参数必须逐个列举出来。
  • bind()方法通过传入一个对象,返回一个this绑定了传入对象的新函数,这个函数的this指向除了使用new时会被改变,其他情况下都不会改变。

事件委托代理

事件模型分为三个阶段:
  • 捕获阶段:在事件冒泡的模型中,捕获阶段不会响应任何事件;
  • 目标阶段:目标阶段就是指事件响应到触发事件的最底层元素上;
  • 冒泡阶段:冒泡阶段就是事件的触发响应会从最底层目标一层层地向外到最外层(根节点),事件代理即使利用事件冒泡的机制把里层所需要响应的事件绑定到外层。

事件委托,通俗的讲就是把一个元素响应事件的函数委托到另一个元素。一般来讲会把一个或者一组元素的事件委托到它的父层或者更外层的元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。

委托的优点:
  • 减少内存消耗,在列表中如果给每个列表项都绑定一个函数,那么对于内存消耗是非常大的,比较好的方法是点击事件绑定到它的父层,然后在执行事件的时候再去匹配判断目标元素。可以减少大量的内存消耗,节约效率。
  • 动态绑定事件,增删列表元素时,每一次改变都需要重新给新的元素绑定事件,给即将删除的元素解绑事件,如果用了事件委托就没有这种麻烦了,所以使用事件委托在动态绑定事件的情况下是可以减少很多重复工作的。

事件循环

js是一门单线程的语言,意味着同一时间只能做一件事,但这并不意味着单线程就会阻塞,而实现单线程非阻塞的方法就是事件循环。

js中所有的任务都可以分为:

  • 同步任务:立即执行的任务,同步任务一般会直接进入主线程中执行;
  • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等。 同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕时,会去任务队列读取相应的任务,推入主线程执行。这个过程不断重复就是事件循环。
宏任务与微任务

异步任务还可以细分为微任务与宏任务。它的执行机制是,执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中;当前宏任务执行完成后,会查看微任务的事件队列,然后将里面所有的微任务依次执行完。

微任务:一个需要异步执行的函数,执行时机是在主函数执行结束之后,当前宏任务结束之前,常见的微任务有:

  • promise.then
  • Mutaionobserver
  • Object.observe(已废弃,用proxy对象代替)
  • process.nextTick(node.js)

宏任务:宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合。常见的宏任务有:

  • script(可以理解为外层同步代码)
  • setTimeoutsetInterval
  • UI RenderingUI事件
  • PostMessageMessageChannel
  • SetImmediateI/O(node.js)

get和post的区别

在万维网的世界中,TCP就像汽车,我们用TCP来运输数据,http就是交通规则,http给汽车运输设定了好几个服务类别,get/post/put/delete等等。http只是个行为准则,而TCP才是getpost怎么实现的基本。参数大小限制来自于,大多数浏览器都会限制URL长度在2k个字节,而大多数服务器最多处理64k大小的URL

REC7231里定义了HTTP方法的几个性质:

  1. Safe - 安全 这里的安全和通常理解的安全意义不同,如果一个方法的语义在本质上是只读的,那么这个方法就是安全的。客户端向服务端的资源发起请求如果是使用了安全的方法,那么就不应该引起服务端任何的状态变化,因此也是无害的。此RFC定义,GET,HEAD,OPTIONS,TRACE这几个方法是安全的。引入安全这个概念是为了方便网络爬虫和缓存,以免调用或者缓存某些不安全方法时引起某些意外的后果。User Agent(浏览器)应该在执行安全和不安全方法时做出区分对待,并给用户以提示。

  2. Idempotent - 幂等 幂等的概念是指同一个请求方法执行多次和仅执行一次的效果完全相同。PUTDELETE和安全方法都是幂等的。

  3. Cacheable - 可缓存性 就是一个方法是否可以被缓存,GET,HEAD和某些情况下的POST都是可以缓存的,但是绝大多数的浏览器实现里仅仅支持GETHEAD.

协议不等于实现:协议规定安全在实现里不一定安全,协议规定幂等在实现里不一定幂等,协议规定可缓存在实现里不一定可缓存。

GET

  • get的语义是请求获取指定的资源,get方法是安全、幂等、可缓存的(除非有Cache-ControlHeader的约束),get方法的报文主体没有任何语义。

POST

  • post语义是根据请求负荷(报文主体)对指定的资源做出处理,具体的处理方式视资源类型而不同。post不安全,不幂等,不可缓存(大部分实现)。

vue的v-for中key的作用

key是给每个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确,更快的找到对应的VNode节点。

diff算法的原理

在新老虚拟DOM对比时:

  • 首先,对比节点本身,判断是否为同一节点,如果为不同节点,则删除该节点重新创建节点进行替换;
  • 如果为相同节点,进行patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除);
  • 比较如果都有子节点,则进行updateChildren,判断如何对这些新老节点进行操作(diff核心);
  • 匹配时,找到相同的子节点,递归比较子节点; 在diff中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从O(n3)降低至O(n),也就是说只有当新旧children都为多个子节点时才需要用核心的diff算法进行同层级比较。

父子组件通讯的方式有哪些

  • props:父组件传递数据给子组件,子组件设置props属性接收父组件传递过来的参数。
  • $emit:子组件传递数据给父组件,子组件通过触发$emit自定义事件,$emit第二个参数为传递的值,父组件绑定监听器获取到子组件传递过来的参数。
  • ref:父组件在使用子组件的时候设置ref,通过this.$ref.xx(子组件实例)来获取数据
  • eventbus:兄弟间传值,创建一个中央时间线eventbus,兄弟组件通过$emit触发自定义事件,另一个兄弟组件通过$on监听自定义事件。
  • $parent$root:兄弟组件通过共同的祖辈搭建通信桥连
  • $attrs$listeners:祖先传递数据给子孙,设置批量向下传属性$attrs$listeners
  • provideinject:在祖先组件定义provide属性,返回传递的值,在后代组件通过inject接收组件传递过来的值
  • vuex:适用于复杂关系的组件数据传递。

vue如何强制刷新

  • this.$router.go(0)可以强制刷新当前页面
  • window.location.hrefwindow.location.reload,强制刷新当前页面
  • this.$forceUpdate强制重新渲染;
  • v-if指令,当v-if的值发生变化时,组件会被重新渲染一遍;
  • this.$nextTick强制刷新

vue双向数据绑定原理

Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:

  1. observe对数据对象进行递归遍历,包括子属性对象的属性,都加上settergetter,这样的话,给这个对象的某个属性赋值,就会触发setter,那么就能监听到数据变化
  2. compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
  3. Watcher订阅者是ObserverCompile之间通信的桥梁,主要做的事情是:
  • 在自身实例化时往属性订阅器(dep)里面添加自己
  • 自身必须有一个update()方法
  • 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
  1. MVVM作为数据绑定的入口,整合ObserverCompileWatcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起ObserverCompile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

vue生命周期

vue实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载DOM->渲染、更新->渲染、卸载等一系列过程,称这是vue的生命周期。

  1. beforecreate(创建前):数据观测和初始化事件还未开始,此时不能访问datacomputedwatchmethods上的方法和数据。
  2. created(创建后):实例创建完成,此时渲染的节点还未挂载到DOM,所以不能访问到$el
  3. beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下配置:编译模板,把data里面的数据和模板生成HTML。此时还没有挂载HTML到页面上。
  4. mounted(挂载后):在el被新创建的vm.$el替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的HTML内容替换el属性指向的DOM对象。完成模板中的HTML渲染到HTML页面中。此过程中进行Ajax交互。
  5. beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对于的真实DOM还没有被渲染。
  6. updated(更新后):在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时DOM已经根据响应式数据的变化更新了。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这个可能会导致更新无限循环。该钩子在服务端渲染期间不被调用。
  7. beforeDestory(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,this扔能获取到实例。
  8. destoryed(销毁后):实例销毁后调用,调用后,vue实例指示的所有东西都会解绑定,所有的事件监听器会被溢出,所有的子实例也会被销毁,该钩子在服务端渲染期间不被调用。

另外还有keep-alive独有的生命周期,分别为activateddeactivated。用keep-alive包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行deactivated钩子函数,命中缓存渲染后会执行activated钩子函数。

computed和watch的区别

computed:

  • 支持缓存,只有当依赖的数据发生变化才会重新计算;
  • 不支持异步,当computed中有有异步操作时,无法监听数据的变化;
  • computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的;
  • 如果一个属性是由其它属性计算而来的,这个属性依赖其它的属性,一般会使用computed
  • computed中,属性由一个get方法和一个set方法,当数据发生变化时,会调用set方法;如果computed属性的值是函数,那么默认使用get方法,函数的返回值就是属性的值;

watch:

  • 不支持缓存,数据变化时,就会触发相应的操作;
  • 支持异步监听;
  • 监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值;
  • 当一个属性发生变化时,就需要执行相应的操作;
  • 监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其它操作,函数有两个参数:
    1. immediate:组件加载立即触发的函数;
    2. deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生 变 化。需要注意的是,deep无法监听到数组和对象内部的变化。
  • 当需要执行异步或者昂贵的操作以响应不断的变化时,就需要使用watch

vue如何重置data数据

使用Object.assign(),vm.$data可以获取当前状态下的datavm.$options.data可以获取到组件初始状态下的data,具体可以看vm.$options.

Object.assign(this.$data,this.$options.data())

Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,将参数2赋值给参数1.

  • this.$data获取当前状态下的data
  • this.$options.data()获取该组件初始状态下的data