首页 文章 精选 留言 我的

精选列表

搜索[面试],共4915篇文章
优秀的个人博客,低调大师

java面试-Java并发编程(八)——闭锁、同步屏障、信号量详解

1. 闭锁:CountDownLatch 1.1 使用场景 若有多条线程,其中一条线程需要等到其他所有线程准备完所需的资源后才能运行,这样的情况可以使用闭锁。 1.2 代码实现 // 初始化闭锁,并设置资源个数 CountDownLatch latch = new CountDownLatch(2); Thread t1 = new Thread( new Runnable(){ public void run(){ // 加载资源1 加载资源的代码…… // 本资源加载完后,闭锁-1 latch.countDown(); } } ).start(); Thread t2 = new Thread( new Runnable(){ public void run(){ // 加载资源2 资源加载代码…… // 本资源加载完后,闭锁-1 latch.countDown(); } } ).start(); Thread t3 = new Thread( new Runnable(){ public void run(){ // 本线程必须等待所有资源加载完后才能执行 latch.await(); // 当闭锁数量为0时,await返回,执行接下来的任务 任务代码…… } } ).start(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 2. 同步屏障:CyclicBarrier 2.1 使用场景 若有多条线程,他们到达屏障时将会被阻塞,只有当所有线程都到达屏障时才能打开屏障,所有线程同时执行,若有这样的需求可以使用同步屏障。此外,当屏障打开的同时还能指定执行的任务。 2.2 闭锁 与 同步屏障 的区别 闭锁只会阻塞一条线程,目的是为了让该条任务线程满足条件后执行; 而同步屏障会阻塞所有线程,目的是为了让所有线程同时执行(实际上并不会同时执行,而是尽量把线程启动的时间间隔降为最少)。 2.3 代码实现 // 创建同步屏障对象,并制定需要等待的线程个数 和 打开屏障时需要执行的任务 CyclicBarrier barrier = new CyclicBarrier(3,new Runnable(){ public void run(){ //当所有线程准备完毕后触发此任务 } }); // 启动三条线程 for( int i=0; i<3; i++ ){ new Thread( new Runnable(){ public void run(){ // 等待,(每执行一次barrier.await,同步屏障数量-1,直到为0时,打开屏障) barrier.await(); // 任务 任务代码…… } } ).start(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 3. 信号量:Semaphore 3.1 使用场景 若有m个资源,但有n条线程(n>m),因此同一时刻只能允许m条线程访问资源,此时可以使用Semaphore控制访问该资源的线程数量。 3.2 代码实现 // 创建信号量对象,并给予3个资源 Semaphore semaphore = new Semaphore(3); // 开启10条线程 for ( int i=0; i<10; i++ ) { new Thread( new Runnbale(){ public void run(){ // 获取资源,若此时资源被用光,则阻塞,直到有线程归还资源 semaphore.acquire(); // 任务代码 …… // 释放资源 semaphore.release(); } } ).start(); }

优秀的个人博客,低调大师

Java动态代理机制——那些让你面试脱颖而出的技能

retrofit是一个解耦性非常高的网络请求框架,最近在研究的时候发现了动态代理这个非常强大且实用的技术,这篇文章将作为retrofit的前置知识,让大家认识:动态代理有哪些应用场景,什么是动态代理,怎样使用,它的局限性在什么地方?#动态代理的应用场景 1. AOP—面向切面编程,程序解耦简言之当你想要对一些类的内部的一些方法,在执行前和执行后做一些共同的的操作,而在方法中执行个性化操作的时候--用动态代理。在业务量庞大的时候能够降低代码量,增强可维护性。 2. 想要自定义第三放类库中的某些方法我引用了一个第三方类库,但他的一些方法不满足我的需求,我想自己重写一下那几个方法,或在方法前后加一些特殊的操作--用动态代理。但需要注意的是,这些方法有局限性,我会在稍后说明。 什么是动态代理 以上的图太过于抽象,我们从生活中的例子开始切入。 假如你是一个大房东(被代理人),你有很多套房子想要出租,而你觉得找租客太麻烦,不愿意自己弄,因而你找一个人来代理你(代理人),帮打理这些东西,而这个人(代理人也就是中介)在帮你出租房屋的时候对你收取一些相应的中介费(对房屋出租的一些额外操作)。对于租客而言,中介就是房东,代理你做一些事情。 以上,就是一个代理的例子,而他为什么叫动态代理,“动态”两个字体现在什么地方? 我们可以这样想,如果你的每一套房子你都请一个代理人帮你打理,每当你想再出租一套房子的时候你得再请一个,这样你会请很多的代理人,花费高额的中介成本,这可以看作常说的“静态代理”。 但假如我们把所有的房子都交给一个中介来代理,让他在多套房子之间动态的切换身份,帮你应付每一个租客。这就是一个“动态代理”的过程。动态代理的一大特点就是编译阶段没有代理类在运行时才生成代理类。 我们用一段代码来看一下 房屋出租的操作 /** *定义一个借口 **/ public interface RentHouse { void rent();//房屋出租 void charge(String str);//出租费用收取 } 房东 public class HouseOwner implements RentHouse { public void rent() { System.out.println("I want to rent my house"); } public void charge(String str) { System.out.println("You get : " + str + " RMB HouseCharge."); } } 中介 public class DynamicProxy implements InvocationHandler { // 这个就是我们要代理的真实对象,即房东 private Object subject; // 构造方法,给我们要代理的真实对象赋初值 public DynamicProxy(Object subject) { this.subject = subject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 在代理真实对象前我们可以添加一些自己的操作,中介收取中介费 System.out.println("before "+method.getName()+" house"); System.out.println("Method:" + method.getName()); // 如果方法是 charge 则中介收取100元中介费 if (method.getName().equals("charge")) { method.invoke(subject, args); System.out.println("I will get 100 RMB ProxyCharge."); } else { // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用 method.invoke(subject, args); } // 在代理真实对象后我们也可以添加一些自己的操作 System.out.println("after "+method.getName()+" house"); return null; } 客人 public class Client { public static void main(String[] args) { // 我们要代理的真实对象--房东 HouseOwner houseOwner = new HouseOwner(); // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的 InvocationHandler handler = new DynamicProxy(houseOwner); /* * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数 * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象 * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了 * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上 */ RentHouse rentHouse = (RentHouse) Proxy.newProxyInstance(handler.getClass().getClassLoader(), houseOwner .getClass().getInterfaces(), handler);//一个动态代理类,中介 System.out.println(rentHouse.getClass().getName()); rentHouse.rent(); rentHouse.charge("10000"); } } 我们来看一下输出 com.sun.proxy.$Proxy0 before rent house Method:rent I want to rent my house after rent house before charge house Method:charge You get : 10000 RMB HouseCharge. I will get 100 RMB ProxyCharge. after charge house Process finished with exit code 0 输出里有 before rent house以及after rent house,说明我们可以在方法的前后增加操作。再看输出 I will get 100 RMB ProxyCharge. 中介收取了100块的中介费,说明我们不仅可以增加操作,甚至可以替换该方法或者直接让该方法不执行。 刚开始看代码你可能会有很多疑惑,我们通过以下的内容来看看动态代理应该怎么用。 #动态代理该如何使用在java的动态代理机制中,有两个重要的类和接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。 每一个动态代理类都必须要实现InvocationHandler这个接口(代码中的中介),并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke(对方法的增强就写在这里面) 方法来进行调用。 Object invoke(Object proxy, Method method, Object[] args) throws Throwable 我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢? Object invoke(Object proxy, Method method, Object[] args) throws Throwable //proxy: 指代我们所代理的那个真实对象 //method: 指代的是我们所要调用真实对象的某个方法的Method对象 //args: 指代的是调用真实对象某个方法时接受的参数 接下来我们来看看Proxy这个类 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法: public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException //loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载 //interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了 //h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上 这样一来,结合上面给出的代码,我们就可以明白动态代理的使用方法了 #动态代理的局限性从动态代理的使用方法中我们看到其实可以被增强的方法都是实现了借口的(不实现借口的public方法也可以通过继承被代理类来使用),代码中的HouseOwner继承了RentHouse 。而对于private方法JDK的动态代理无能为力!以上的动态代理是JDK的,对于java工程还有大名鼎鼎的CGLib,但遗憾的是CGLib并不能在android中使用,android虚拟机相对与jvm还是有区别的。 结束语 动态代理的使用场景远不止这些,内部原理会在以后的文章中介绍,但应用类反射临时生成代理类这一机制决定它对性能会有一定的影响。本文作为retrofit原理的前置文章并没有太过详尽,如有疏漏和错误,欢迎指正!

优秀的个人博客,低调大师

WF4.0实战(十三):解微软一道面试

题目:一个整数数列,元素取值可能是0—65535中的任意一个数,相同数值不会重复出现。0是例外,可以反复出现。 请设计一个算法,当你从该数列中随意选取5个数值,判断这5个数值是否连续相邻。 注意: 1、5个数值允许是乱序的。比如: 8 7 5 0 6; 2、0可以通配任意数值。比如:8 7 5 0 6 中的0可以通配成9或者4; 3、0可以多次出现; 4、复杂度如果是O(n2)则不得分。 分析:通过分析你可以发现,题目只要满足:除0之外的最大数减去除0之外的最小数小于等于4就行了。 故只要通过一个循环就5个数字中除0之外的最大数和最小数。求差之后与4做个比较就行了。伪代码如下: 1 public booleanisContiguous( int []array) 2 { 3 int min =- 1 ; 4 int max =- 1 ; 5 for ( int i = 0 ;i < array.length;i ++ ) 6 { 7 if (array[i] != 0 ) 8 { 9 if (min ==- 1 || min > array[i]) 10 { 11 min = array[i]; 12 } 13 if (max ==- 1 || max < array[i]) 14 { 15 max = array[i]; 16 } 17 } 18 } 19 return max - min <= array.length - 1 ; 20 } 下面让我用WF4.0来实现解这个题目,思路和上面代码思想是一致的。 流程: 实现: 1、产生随机数流程GenerateRandom流程,如下图: 2、验证产生的数字是否满足条件: 3、输出结果: 4、整个流程: 运行结果: 0~65535范围太大,很难产生出5位连续的数字,将范围缩小为1-6: 总结:这篇文章主要说明使用WF4.0和你写一般的C#代码一样,复杂的逻辑它也可以实现。一些复杂的算法可能会因为它的可视化编程而变得更简单。 本文转自麒麟博客园博客,原文链接:http://www.cnblogs.com/zhuqil/archive/2010/05/07/msInterview.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

Linux云计算岗位面试时最常遇到的40个问题

1)使用云计算有哪些优点? 使用云计算有下列优点: a)备份数据和存储数据 b)强大的服务器功能 c)SaaS(软件即服务) d)信息技术沙盒功能 e)提高生产力 f)具有成本效益,并节省时间 2)可否列举哪些平台用于大规模云计算? 用于大规模云计算的平台包括: a)Apache Hadoop b)MapReduce 3)可否解释用于云计算部署的不同模式? 不同的云计算部署模式包括: a)私有云 b)公共云 c)社区云 d)混合云 4)云计算与移动计算有何区别? 移动计算使用与云计算同样的概念。借助互联网而不是借助单个设备,云计算因数据而变得活跃。它为用户提供了需要按需获取的数据。在移动计算中,应用程序在远程服务器上运行,为用户提供了访问所存储数据的权限。 5)用户如何得益于公用计算(utility computing)? 公用计算让用户可以只需要为使用的资源付费。它是由决定从云端部署哪种类型的服务的企业组织管理的一种插件。 大多数企业组织青睐混合策略。 6)由于数据在云端传输,你如何确保数据安全? 数据从一个地方传输到另一地方的过程中想确保数据安全,就要确保针对你发送的数据所使用的加密密钥没有泄露。 7)云在安全方面的措施有哪些? a)身份管理:授权应用程序服务。 b)访问控制:将权限授予用户,那样用户可以控制进入到云环境的另一个用户的访问。 c)验证和授权:只允许通过授权和验证的用户访问数据和应用程序。 8)可否列出定义云架构的不同层? 8)云架构使用的不同层包括: a)CLC即云控制器 b)Walrus c)集群控制器 d)SC即存储控制器 e)NC即节点控制器 9)云计算系统集成商的角色是什么? 在云计算中,系统集成商负责为用来设计云平台的复杂过程制定策略。集成商需要构建更准确的混合云和私有云网络,因为集成商拥有数据中心构建方面的全部知识。 10)“EUCALYPTUS”的全称是什么? “EUCALYPTUS”的全称是将你的程序连接到实用系统的弹性公用计算架构。 11)可否解释“EUCALYPTUS”在云计算中有何用处? Eucalyptus是云计算中的一种开源软件基础设施,它用来将集群实施到云计算平台上。它用来构建公共云、混合云和私有云。它能够将你自己的数据中心打造成私有云,并让你可以将其功能应用于其他许多企业组织。 12)虚拟化平台在实施云时有何要求? 虚拟化平台在实施云时的要求包括: a)管理服务级别策略 b)云操作系统 c)虚拟化平台有助于让后端级别概念和用户级别概念彼此不同。 13)在使用云计算平台前,用户需要考虑哪些必要的方面? a)合规 b)数据丢失 c)数据存储 d)业务连续性 e)正常运行时间 f)云计算的数据完整性 14)可否列举几个开源云计算平台数据库? 开源云计算平台数据库有: a)MongoDB b)CouchDB c)LucidDB 15)落实了哪些安全法规来保护云端数据的安全? 为保护云端数据安全而落实的安全法规包括: a)处理:控制在应用程序中正确、完整处理的数据。 b)文件:它管理和控制任何文件中处理的数据。 c)输出调和:它控制输入和输出之间需要调和的数据。 d)输入验证:控制输入数据。 e)安全和备份:它提供安全和备份,还控制安全泄密日志。 16)可否列举几个大型云提供商的数据库名称? a)Google bigtable b)Amazon simpleDB c)基于云的SQL 17)可否解释云与传统数据中心之间的区别? a)由于供暖和硬件/软件问题,传统数据中心的成本比较高。 b)需求增加时,云可以扩增资源。大部分开支花在了数据中心的维护上,而云计算不是这样。 18)可否解释软件即服务(SaaS)的不同模式? a)简单的多租户模式:在该模式中,每个用户有独立的资源,与其他用户分开来,这是一种高效的模式。 b)细粒度的多租户模式:在这种模式中,资源由许多租户共享,但是功能仍然一样。 19)API在云服务中有何用途? API(应用编程接口)在云平台中非常有用 a)不需要编写功能完备的程序。 b)提供了在一个或多个应用程序之间进行联系的指令。 c)易于构建应用程序,并将云服务与其他系统联系起来。 20)为云计算部署了哪些不同的数据中心? 云计算包括不同的数据中心,比如 a)容器化数据中心 b)低密度数据中心 21)云计算中有哪些不同的层? 云计算的不同层包括: a)SaaS:软件即服务,它让用户可以直接访问云应用程序,不必在系统上安装任何东西。 b)IaaS:基础设施即服务,它从硬件(比如内存和处理器速度等)等层面提供了基础设施。 c)PaaS:平台即服务,它为开发人员提供了云应用程序平台。 22)平台即服务有多重要? 平台即服务(PAAS)是云计算中一个很重要的层。它为提供商提供了应用程序平台。它负责提供基础设施层的全面虚拟化,让它运行起来如同单一的服务器。 23)云服务是什么? 云服务用来通过互联网,使用网络中的服务器来构建云应用程序。它提供了这种便利:不必将云应用程序安装到计算机上,即可直接使用。它还减少了维护和支持使用云服务开发的应用程序的工作。 24)可否列出云计算领域的三种基本云? A)专业云 B)私人云 C)高性能云 25)就基础设施即服务而言,它提供了什么资源? IAAS(基础设施即服务)提供了用来构建云的虚拟资源和物理资源。它负责处理部署和维护这一层提供的服务带来的复杂性。在这里,基础设施是服务器、存储系统及其他硬件系统。 26)云架构有什么样的业务好处? 云架构具有的好处包括: a)无需基础设施投入 b)适时的基础设施 c)更高效地利用资源 27)云架构有别于传统架构的特点有哪些? 让云架构有别于传统架构的特点包括: a)按照需求,云架构满足硬件要求。 b)云架构能够按需增减资源。 c)云架构能够管理和处理动态工作负载,顺畅无阻。 28)可否列举云计算中弹性与可扩展性的区别? 可扩展性是云计算的一个特点;借助可扩展性,只要相应增加资源容量,就可以处理增加的工作负载。作为云计算的另一个特点,弹性强调了启用和停用庞大的资源容量这一概念。 29)可否列举由Window Azure操作系统提供的服务? Window Azure提供了三种核心服务,包括: a)计算服务 b)存储服务 c)管理服务 30)在云架构中,必需的不同部分有哪些? a)云入站 b)处理器速度 c)云存储服务 d)云提供商服务 e)云间通信 31)在云架构中,经历的不同阶段有哪些? a)启动阶段 b)监测阶段 c)关闭阶段 d)清理阶段 32)可否列出云计算的基本特点? a)弹性和可扩展性 b)自助式配置和自动取消配置 c)标准化界面 d)自助计费的使用模式 33)在云架构中,基本的构建模块有哪些? a)参考架构 b)技术架构 c)部署操作架构 34)可否描述云架构以哪些方式来提供自动化和性能透明度? 为了提供性能透明度和自动化,云架构使用许多工具。它可以管理云架构和监测报告。它还可以共享使用云架构的应用程序。自动化是云架构的关键部分,有助于改善质量级别。 35)可否解释一下高性能云在云计算中的角色? 高性能云在立即传输最大数量的数据方面很有用。从事高性能计算研究的专业人员经常使用高性能云。 36)可否解释混合云和社区云? 混合云:混合云包括多家服务提供商。它结合了公共云和私有云的功能。公司同时需要私有云和公共云时,就会使用混合云。 社区云:这种模式的成本相当高;多家企业组织有着共同的目标和需求,又准备共享云服务的优点时,就会使用社区云。 37)在云中,优化策略有哪些? 为了克服维护成本,并且优化资源,用到云端三个数据中心这个概念:提供恢复和备份机制,万一出现灾难或系统故障,可确保所有数据安全无恙。 38)亚马逊SQS是什么东东? 为了在不同的连接件之间联系,就要使用亚马逊SQS消息;它在亚马逊的不同组件中充当“联络者”。 39)缓冲器如何用于亚马逊网络服务? 为了让系统更高效地应对流量或负载突增的情况,提供商使用缓冲器。缓冲器可同步不同的组件。组件始终以一种不平衡的方式接收和处理请求。不同组件之间的平衡由缓冲器来负责管理,好让它们以同样的速度来工作,从而提供更快的服务。 40)可否描述云计算中的虚拟机管理程序及其类型? 虚拟机管理程序是虚拟机监测工具,为虚拟机管理资源。虚拟机管理程序主要有两种类型。 类型1:访客虚拟机直接在主机硬件上运行,比如Xen和VMWare ESXI。 类型2:访客虚拟机通过主机操作系统在硬件上运行,比如KVM和OracleVirtualBox。 作者:运维派 来源:http://www.yunweipai.com/archives/18460.html 本文转自 ChinaUnicom110 51CTO博客,原文链接: http://blog.51cto.com/xingyue2011/1959411

优秀的个人博客,低调大师

Android面试,简要介绍一下asynctask和handler的优缺点

1 )AsyncTask实现的原理,和适用的优缺点 AsyncTask,是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程. 使用的优点: l 简单,快捷 l 过程可控 使用的缺点: l 在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来. l 最大并发数不超过5 2 )Handler异步实现的原理和适用的优缺点 在Handler 异步实现时,涉及到 Handler, Looper, Message,Thread四个对象,实现异步的流程是主线程启动Thread(子线程),thread(子线程)运行并生成Message,Looper获取Message并传递给Handler,Handler逐个获取Looper中的Message,并进行UI变更。 使用的优点: l 结构清晰,功能定义明确 l 对于多个后台任务时,简单,清晰 使用的缺点: l 在单个后台异步处理时,显得代码过多,结构过于复杂(相对性) AsyncTask介绍 Android的AsyncTask比Handler更轻量级一些,适用于简单的异步处理。 首先明确Android之所以有Handler和AsyncTask,都是为了不阻塞主线程(UI线程),且UI的更新只能在主线程中完成,因此异步处理是不可避免的。 Android为了降低这个开发难度,提供了AsyncTask。AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务。 AsyncTask直接继承于Object类,位置为android.os.AsyncTask。要使用AsyncTask工作我们要提供三个泛型参数,并重载几个方法(至少重载一个)。 AsyncTask定义了三种泛型类型 Params,Progress和Result。 Params 启动任务执行的输入参数,比如HTTP请求的URL。 Progress 后台任务执行的百分比。 Result 后台执行任务最终返回的结果,比如String。 使用过AsyncTask 的同学都知道一个异步加载数据最少要重写以下这两个方法: doInBackground(Params…)后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。 onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回 有必要的话你还得重写以下这三个方法,但不是必须的: onProgressUpdate(Progress…) 可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。 onPreExecute() 这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。 onCancelled() 用户调用取消时,要做的操作 使用AsyncTask类,以下是几条必须遵守的准则: Task的实例必须在UI thread中创建; execute方法必须在UI thread中调用; 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法; 该task只能被执行一次,否则多次调用时将会出现异常; 本文转自我爱物联网博客园博客,原文链接:http://www.cnblogs.com/yydcdut/p/3909603.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

金三银四,那些烧脑的JS面试题及原理

Q: JS代码是按顺序执行的吗? A: JS代码执行过程中,需要先做变量提升,而之所以需要实现变量提升是因为JS代码在执行之前需要先编译 1、变量提升 变量和函数声明会被存放到变量环境中,变量的默认值会被设置为undefined var scope = 'global scope' function a(){ // 3、顶层变量环境声明了scope初始化为undefined function b(){ // 2、b函数的上层作用域是a,向上找scope console.log(scope) } return b; // 1、虽然声明在return语句后面,依然会提升到a函数作用域的顶层 var scope = 'local scope' } a()() // undefined 1.1、同名处理 同名函数,选择最后声明的 变量和函数同名,选择函数 var a = 1 var getNum = function() { a = 2 } function getNum() { a = 3 } getNum() console.log(a) // 2 // 变量和函数同名选择提升函数,函数提升包含初始化和赋值,接着执行函数,var声明的getNum被赋值为一个函数执行完成更改变量a为2 1.2、提升阶段 创建 初始化 赋值 let 提升 x x var 提升 提升 x function 提升 提升 提升 在块作用域内,let声明的变量仅在创建时被提升,在初始化之前使用变量,就会形成一个暂时性死区 var name = 'World' ;(function () { if (typeof name === 'undefined') { var name = "HuaMu"; console.info('Goodbye ' + name) } else { console.info('Hello ' + name) } })() // if分支内的name会被提升到外层,且同全局变量同名,则访问不到外层的name,var仅创建和初始化,并未赋值,则值为undefined,满足if条件 // Goodbye HuaMu 2、调用栈 在执行上下文创建完成后,JS引擎会将执行上下文压入栈中,通常把这种用来管理执行上下文的栈称为执行上下文,又称调用栈 2.1、函数调用 JS引擎会为函数创建执行上下文,并将其压入调用栈 JS引擎执行函数代码 执行完毕后,JS引擎将该函数的执行上下文弹出栈 2.2、栈溢出 当分配的调用栈空间被占满时,会引发“栈溢出”问题。即超过了最大栈容量或最大调用深度 2.2.1、场景 <!-- todo --> 3、作用域 作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。主要有全局作用域、函数作用域以及块级作用域 当前作用域与上层作用域有同名变量时,无法访问和影响上层变量 let a = 1 function b(a) { a = 2 console.log(a) } b(a) // 2 console.log(a) // 1 4、作用域链 通过作用域查找变量的链条称为作用域链,而作用域链是通过词法作用域来确定的。词法作用域由函数声明的位置来决定,是静态的,即在代码阶段就决定好了,和函数是怎么调用的没有关系 // 连等操作是从右向左执行的,相当于b = 10、let a = b,相当于隐式创建为一个全局变量b let a = b = 10; ;(function(){ // 跟着作用域链查找到全局变量b,并修改为20 // 由于重新声明了,a变量只是局部变量,不影响全局变量a let a = b = 20 })() console.log(a) // 10 console.log(b) // 20 函数只会在第一次执行的时候被编译,因此编译时变量环境和词法环境最顶层数据已确定 var i = 1 function b() { console.log(i) } function a() { var i = 2 b() } // 由于a函数在全局作用域被定义,即便b函数在a函数内执行,它也只能访问到全局的作用域 a() // 1 5、闭包 一个作用域引用着一个本该被销毁的作用域,称之为闭包。即一个函数引用着父作用域的变量,在父函数执行结束后依然进行调用 6、this this是函数执行上下文对象,是动态变化的值,它没有作用域的限制,嵌套函数中的this不会从外层函数中继承,可通过箭头函数、self处理 6.1、类型 全局执行上下文中的this: window 函数执行上下文中的this: 严格模式 ? undefined: window var v_out = 'v_out'; let c_out = 'c_out'; var inner = { v_out: 'v_in', c_out: 'c_in', v_func: function () { return this.v_out }, c_func: function () { return this.c_out }, func:()=>{ return this.v_out } }; // 获取对象作用域内的函数,在全局环境下调用 this 指向 window const v_method = inner.v_func; const c_method = inner.c_func; // 顶层 v_out 变量会提升挂载到 window v_method(); // 'v_out' // 在块作用域内,const声明的变量不会挂载到 window,且父作用域不能访问子作用域 c_method(); // undefined // 赋值表达式和逗号表达式会返回最后一个值本身,即inner.v_func函数本身,调用位置是全局环境 (inner.v_func, inner.v_func)(); // 'v_out' (inner.v_func = inner.v_func)(); // 'v_out' // 对象的方法调用,this指向该对象 inner.v_func() // 'v_in' (inner.v_func)() // 'v_in' // 箭头函数没有自己的执行上下文,它继承调用函数的this,在这里是window inner.func() // 'v_out' 6.2、更改this指向 绑定优先级为:new > 显示绑定(call、apply、bind) > 隐式绑定(调用函数对象) > 默认绑定(window) 6.2.1、通过函数的call、apply、bind方法设置 c_method.call(inner) c_method.apply(inner) c_method.bind(inner)() 简单实现:call、apply 将当前函数链接到指定的上下文中,即将函数设置为对象属性 当前函数在context上下文中执行 移除context中已执行的当前函数 /** * 简单实现apply * @param {Function} fn 当前运行的函数 * @param {Object} context 指定的上下文 * @param {Array} args 参数集合 * @returns */ const apply = (fn,context=window,args=[])=>{ // Symbol是es6增加的第六个基本类型,对于对象属性就是uuid const id = Symbol(); // 将当前函数链接到指定的上下文中 context[id] = fn; // 当前函数在context上下文中执行 const res = context[id](...args) // 移除context中已执行的当前函数 delete context[id] return res; } // -------test------- // const context = { value:1 } function fn (name,isGirl){ console.log("🚀 ~ ", name,isGirl,this.value) } apply(fn,context,['huamu',true]) // 🚀 ~ huamu true 1 简单实现:bind 与call、apply不一样的点是bind返回一个新函数 function bind (fn, context=window, ...args) { return (...args2)=> apply(fn,context,[...args,...args2]) } 6.2.2、通过调用函数对象:指向对象本身 this的绑定是函数真正执行的位置 6.2.3、通过构造函数 function Foo() { getName = function () { console.log(1) } return this } Foo.getName = function () { console.log(2) } Foo.prototype.getName = function () { console.log(3) } var getName = function () { console.log(4) } function getName() { console.log(5) } // 执行Foo函数的静态方法 Foo.getName() // 2 // 函数getName提升并赋值,执行getName函数表达式 getName() // 4 // 在全局环境下执行Foo函数,this指向window,执行函数内的getName方法,覆盖了全局环境下的getName Foo().getName() // 1 getName() // 1 // new用于调用函数,即 new Foo.getName() 相当于 new (Foo.getName)(),执行了Foo函数的静态方法 new Foo.getName() // 2 // new 和 . 的优先级一样高,从左往右执行,相当于 (new Foo()).getName(),new会创建一个新对象,执行新对象的getName方法,在新对象本身找不到该方法,因此向原型找 new Foo().getName() // 3 new new Foo().getName() 简单实现:new 创建一个新对象,并指向函数原型 绑定this到新对象 返回对象 const newFn = (fn, ...arg) => { const obj = Object.create(fn.prototype); fn.apply(obj,arg) return obj } 拓展:Object.create方法会创建一个对象,并且将该对象的__proto__属性指向传入的对象 const person = { address: { country:"china" }, number: 111, say: function () { console.log(`it's ${this.name}, from ${this.address.country}, nums ${this.number}`) }, setCountry:function (country,number) { this.address.country=country this.number = number } } // 1、p1、p2 的原型对象指向了同一个对象 const p1 = Object.create(person) const p2 = Object.create(person) // 2、添加属性 p1.name = "huahua" // 3、在原型上找到setCountry函数,并且找到引用值address和原始值numbe属性,引用值会在所有实例共享 p1.setCountry("nanji",666) p2.name = "mumu" // 4、p2 的修改值会覆盖 p1的,最终country的值都为beiji p2.setCountry("beiji",999) p1.say() // it's huahua, from beiji, nums 666 p2.say() // it's mumu, from beiji, nums 999 6.2.4、实例 const value = 1; const objContext = { value : 2, getThis:function() { // 嵌套函数中的`this`不会从外层函数中继承 function fn1() { console.log("🚀 ~ fn1",this.value) // undefined } fn1() // 把 this 保存为一个 self 变量,再利用变量的作用域机制传递给嵌套函数 const self = this; function fn2() { console.log("🚀 ~ fn2 self",self.value) // 2 } fn2() // 箭头函数没有自己的执行上下文,会继承调用函数中的 this const fn3 = () => { console.log("🚀 ~ fn3 箭头函数",this.value) // 2 } fn3() // 箭头函数不会绑定局部变量,所有涉及它们的引用都会沿袭向上查找外层作用域链来处理,因此this的绑定只有一次 function fn4() { return () => { return () => { return () => { console.log("🚀 ~ fn4 箭头函数",this.value) // 42 }; }; }; } fn4.call( { value: 42 } )()()() // 构造函数优先级最高 function fn5(value) { this.value = value } const fn = new fn5(100) console.log("🚀 ~ fn5 构造函数", fn.value, this.value) // 100 2 } } 7、原型 7.1、函数对象 & 普通对象 通过 new Function 创建的对象称之为函数对象,其他则为普通对象,普通对象的构造函数是 Object // 函数对象 function fn(){}; const fn = () =>{}; const fn = new Function('str') // 普通对象 const obj = {} const obj = new Object() const obj = new fn() 每个对象都有内置__proto__属性,指向创建它的构造函数的原型对象,但只有函数对象才有prototype属性,指向函数的原型对象 7.2、原型对象 每个原型对象默认拥有一个constructor指针,指向prototype属性所在的函数 7.2.1、person1.__proto__ 是什么? 因为:person1 的构造函数是 Person 所以:person1.__proto__ === Person.prototype 7.2.2、Person.__proto__ 是什么? 因为:Person 的构造函数是 Function 所以:Person.__proto__ === Function.prototype 7.2.3、Person.prototype.__proto__ 是什么? 因为:Person.prototype 是构造函数的一个实例,是个普通对象,其构造函数是 Object 所以:Person.prototype.__proto__ === Object.prototype Function.prototype.__proto__ === Object.prototype 7.2.4、Object.__proto__ 是什么? 因为:所有函数对象的__proto__都指向Function.prototype,它是一个空函数 所以:Object.__proto__ === Function.prototype 7.2.5、Object.prototype.__proto__ 是什么? 因为:Object.prototype 处于 原型链的顶端,为null 所以:Object.prototype.__proto__ === null 7.2.6、12个JS内置构造器对象 8个可访问构造器 同 Object 指向 Function.prototype Number、Boolean、String、Function、Array、RegExp、Error、Date 2个以对象形式存在, 其 proto 指向 Object.prototype Math、JSON 1个不能直接访问的 Global、1个仅在函数调用时由JS引擎创建的 Arguments 8、继承 原型继承 构造函数继承 组合继承 class 继承:主要依靠extends、super(让JavaScript引擎去实现原来需要我们自己编写的原型链代码) 9、模块 9.1、模块化的意义 若不采用模块化,则引入js文件时必须确保引入顺序正确,否则无法运行。在文件数量大、依赖关系不明确的情况很难保证,因此出现了模块化 9.2、CommonJS & AMD 在ES6以前,JS没有模块体系。只有社区指定的一些模块加载方案,如用于服务器端同步加载的CommonJS和用于浏览器端异步加载的AMD、CMD 9.2.1、CommonJS(Node.js) 一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数。拥有四个重要变量:module、exports、require、global exports本身是一个变量对象,指向module.exports的{}模块,只能通过.语法向外暴露变量。而module.exports既可通过.也可使用=赋值,其中exports是module的属性,指向{}模块 //在这里写上需要向外暴露的函数、变量 module.exports = { add, update } // 引用自定义模块必须加./路径,不加的话只会去node_modules文件找 var math = require('./math') // 引用核心模块时,不需要带路径 var http = require('http') 9.2.2、AMD(require.js)、CMD(sea.js) 虽然都是并行加载js文件,但AMD推崇依赖前置、提前执行,即预加载,CMD推崇依赖就近、延迟执行,即懒加载。拥有三个重要变量:指定引用路径的require.config、定义模块的definde以及加载模块的require /** AMD写法 **/ define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { // 在最前面声明并初始化了要用到的所有模块 a.doSomething(); if (false) { // 即便没用到某个模块 b,但 b 还是提前执行了 b.doSomething() } }); /** CMD写法 **/ define(function(require, exports, module) { //在需要时申明 var a = require('./a'); a.doSomething(); if (false) { var b = require('./b'); b.doSomething(); } }); 9.3、ESM CommonJS和AMD输出的是对象,引入时需查找对象属性,只能在运行时确定模块的依赖关系以及输入输出变量,即运行时加载,而ES6模块的设计思想,是尽量的静态化,它导出的不是对象,而是一个个接口,使得编译时就能确定模块的依赖关系和输入输出变量,即静态加载 9.3.1、原理解析 // index.js import { m } from './module'; // module.js const m = 1; const n = 2; export { m, n }; 将上面源码进行打包 // 1. 是一个立即执行函数 (function (modules) { var installedModules = {}; // 4. 处理入口文件模块 function __webpack_require__(moduleId) { if (installedModules[moduleId]) { return installedModules[moduleId].exports; } // 5. 创建一个模块 var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // 6. 执行入口文件模块函数 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); module.l = true; // 7. 返回 return module.exports; } __webpack_require__.d = function (exports, name, getter) { if (!__webpack_require__.o(exports, name)) { // 判断name是不是exports自己的属性 Object.defineProperty(exports, name, {enumerable: true, get: getter}); } }; __webpack_require__.r = function (exports) { if (typeof Symbol !== 'undefined' && Symbol.toStringTag) { // Symbol.toStringTag作为对象的属性,值表示这个对象的自定义类型 [Object Module] // 通常只作为Object.prototype.toString()的返回值 Object.defineProperty(exports, Symbol.toStringTag, {value: 'Module'}); } Object.defineProperty(exports, '__esModule', {value: true}); }; __webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; // 3. 传入入口文件id return __webpack_require__(__webpack_require__.s = "./index.js"); })( // 2. 模块对象作为参数传入 { "./index.js": (function (module, __webpack_exports__, __webpack_require__) { // __webpack_exports__就是module.exports "use strict"; // 添加了__esModule和Symbol.toStringTag属性 __webpack_require__.r(__webpack_exports__); var _module__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./module.js"); }), "./module.js": (function (module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); // 把m/n这些变量添加到module.exports中,并设置getter为直接返回值 __webpack_require__.d(__webpack_exports__, "m", function () {return m;}); __webpack_require__.d(__webpack_exports__, "n", function () {return n;}); var m = 1; var n = 2; }) }); 将模块对象传入一个立即执行的函数 传入文件id,执行__webpack_require__函数 创建一个module,并绑定this到module.exports,同时传入module和module.exports对象 __webpack_require__.r给module.exports对象添加一个Symbol.toStringTag属性,值为{value: 'Module'},使得module.exports调用toString可返回[Object Module]表示一个模块 __webpack_require__.d将要导出的变量添加到module.exports,设置getter返回同名变量的值,使得变量更改,外边的引用也会变化 返回module.exports 注意喔:ESM遇到加载命令import时,只生成一个动态的只读引用,在需要调用时才去模块里取值 9.4、require和import的区别 CommonJS 模块化方案 require/exports 是为服务器端开发设计的。服务器模块系统同步读取模块文件内容,编译执行后得到模块接口。而ES6 模块化方案 import/export 是为浏览器设计的,浏览器模块系统异步加载脚本文件 require/exports 是运行时动态加载,,import/export 是静态编译 require/exports 输出的是一个值的拷贝,import/export 模块输出的是值的引用,即文件引用的模块值改变,require 引入的模块值不会改变,而 import 引入的模块值会改变 用法不同 ES6 模块可以在 import 引用语句前使用模块,CommonJS 则需要先引用后使用 import/export 只能在模块顶层使用,不能在函数、判断语句等代码块之中引用,而require/exports可以 import/export 默认采用严格模式 // require/exports const fs = require('fs') exports.fs = fs module.exports = fs // import/export import fileSystem, {readFile} from 'fs' // 引入 export default 导出的模块不用加 {},引入非 export default 导出的模块需要加 {} 10、babel 对源码字符串进行 parse,生成 AST,把对代码的修改转为对 AST 的增删改,转换完 AST 之后再打印成目标代码字符串 10.1、babel插件开发的API 10.1.1、parse 阶段 使用@babel/parser 把源码转成 AST require('@babel/parser').parse(source, { sourceType: 'module', // 解析 es module 语法 plugins: ['jsx'], // 指定jsx 等插件来解析对应的语法 }); 10.1.2、transform 阶段 使用 @babel/traverse 遍历 AST,并调用 visitor 函数修改 AST,@babel/types 用于创建、判断 AST 节点,提供了 isX、assertX 等 api,若批量创建,则可使用@babel/template require('@babel/traverse').default(ast, { // do something }) 10.1.3、generate 阶段 使用@babel/generate 把 AST 打印为目标代码字符串,同时生成 sourcemap,@babel/code-frame 用于错误时打印代码位置 const { code,map } = generator(ast, { sourceMaps: true }) 11、正则 11.1、基础语法 匹配模式 // + 前导字符必须在目标字符串中连续出现1次起 /\d+/ // * ~ 连续0次起 /\d*/ // ? ~ 0、1次 /\d?/ // ^ 定位字符串首个字符,$ 末尾字符 /^\d$/ // () 为一个捕获组,[]匹配单个字符,元素关系为或 /([\s\S]*?)["']/ // 任意字符 + "|' 匹配字符 // \s 匹配空白字符(空格、换行、缩进等)、\S相反 /[\s\S]*/ // 匹配全部内容 // \w 匹配单词([A-Za-z0-9_])、\W相反 /[\w\W]*/ // 匹配全部内容 // \b 匹配不全是\w的位置 /\bnice\b/ // a nice day -> a是显式位置,a和n之间的位置则为隐式位置,即a位置到n位置前则是\b匹配的位置,同样,e位置到d位置前也是\b匹配的,因此可匹配到 nice /\b.\bnice\b/ // a nice day -> 匹配到 a nice 匹配数字 /[0-9]/ /\d/ // 单个数字 /[0-9]+/ /\d*/ // 多个数字 /[\d]{1,3}/ // 指定个数,1-3个数字 11.2、实例解析 实用http路由表达式 /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/ 拆解匹配 http + (0|1个)s + :// + (0|1个) www. + (1-256个) -|a-z|A-Z|0-9|@|:|%|.|_|+|~|#|= + . + (1-6个) a-z|A-Z|0-9|(|) + 匹配不全是\w的位置 + (任意个)-|a-z|A-Z|0-9|(|)|@|:|%|_|+|.|~|#|?|&|/|=

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。