活动:送两本《PHP 程序员面试笔试真题解析》

考虑到近期是面试找工作潮,想搞个小活动。你好,是我琉忆。《 PHP 程序员面试笔试真题解析》历时一年,由机械工业出版社出版,在 2018 年 11 月问世。本书的适用群体:刚接触 PHP,自学一段时间 PHP 后打算去找 PHP 相关的 PHP 面试工作的群体。这部分群体可以尝试着去练习这部分企业中经常考的 PHP 相关的真题。让你在面试时顺利的通过这些 PHP 相关的真题!现免费送出 2 本《 PHP 程序员面试笔试真题解析》,具体参与规则如下:1、在本文下留言即可参与,并且一个用户的多次留言只算作一次。2、活动截止日期为北京时间 2019 年 3 月 1 日 18:00:00(本周五),以评论时间为准。(2019年3月 1 日晚上 19 点开奖)3、在活动结束后随机抽取 2 名幸运网友。4、获赠名单在本帖后会公布,并 私信该网友。5、幸运网友把收件地址、姓名和手机号发送给我安排邮寄。6、全国包邮(不含新疆、西藏、港澳台)7、保留最终解释权。PS:该书已在天猫、京东、当当等电商平台销售。获取更多PHP程序员面试笔试资料可以关注:琉忆编程库

February 25, 2019 · 1 min · jiezi

《剑指offer》分解让复杂问题更简单

1.复杂链表的复制输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用)思路拆分成三步1.复制一份链表放在前一个节点后面,即根据原始链表的每个节点N创建N,把N直接放在N的next位置,让复制后的链表和原始链表组成新的链表。2.给复制的链表random赋值,即N.random=N.random.next。3.拆分链表,将N和N进行拆分,保证原始链表不受影响。代码 function Clone(pHead) { if (pHead === null) { return null; } cloneNodes(pHead); cloneRandom(pHead); return reconnetNodes(pHead); } function cloneNodes(pHead) { var current = pHead; while (current) { var cloneNode = { label: current.label, next: current.next }; current.next = cloneNode; current = cloneNode.next; } } function cloneRandom(pHead) { var current = pHead; while (current) { var cloneNode = current.next; if (current.random) { cloneNode.random = current.random.next; } else { cloneNode.random = null; } current = cloneNode.next; } } function reconnetNodes(pHead) { var cloneHead = pHead.next; var cloneNode = pHead.next; var current = pHead; while (current) { current.next = cloneNode.next; current = cloneNode.next; if (current) { cloneNode.next = current.next; cloneNode = current.next; } else { cloneNode.next = null; } } return cloneHead; }2.二叉搜索树转换为双向链表输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。思路1.排序的双向链表-中序遍历二叉树2.记录链表的最后一个节点3.每次遍历:设置树节点的left和链表的right进行链接,链接成功后当前节点成为链表的末尾节点,并返回。代码 function Convert(pRootOfTree) { var lastNode = null; lastNode = convertToList(pRootOfTree, lastNode); while (lastNode && lastNode.left) { lastNode = lastNode.left; } return lastNode; } function convertToList(treeNode, lastNode) { if (!treeNode) { return null; } if (treeNode.left) { lastNode = convertToList(treeNode.left, lastNode); } treeNode.left = lastNode; if (lastNode) { lastNode.right = treeNode; } lastNode = treeNode; if (treeNode.right) { lastNode = convertToList(treeNode.right, lastNode); } return lastNode; }3.字符串的排列输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。思路1.把字符串分成两部分,第一个字符和后面的字符2.整个字符串的全排列等于:第一个字符+后面字符的全排列,第一个字符和后面的字符诸葛交换。第一个字符+后面字符的全排列3.除了第一个字符其他字符的全排列等于:第二个字符+后面字符的全排列。3.递归,记录一个当前节点的位置,该位置指向最后一个节点时记录一次排列。代码 function Permutation(str) { var result = []; if (!str) { return result; } var array = str.split(’’); permutate(array, 0, result); result.sort(); return [… new Set(result)]; } function permutate(array, index, result) { if (array.length - 1 === index) { result.push(array.join(’’)); } for (let i = index; i < array.length; i++) { swap(array, index, i); permutate(array, index + 1, result); swap(array, i, index); } } function swap(arr, i, j) { [arr[i], arr[j]] = [arr[j], arr[i]]; } ...

February 24, 2019 · 2 min · jiezi

构造函数内的方法与构造函数prototype属性上方法的对比

挺有用的一篇文章,今天还有人在问我关于构造函数的方法和原型,构造函数的方法是定义在函数内容,作为一个私有方法,不对外开放,而prototype则可以通过对象定义,在外面访问,更加深入请看本文。本文的目的是让大家理解什么情况下把函数的方法写在JavaScript的构造函数上,什么时候把方法写在函数的prototype属性上;以及这样做的好处.为了阅读方便,我们约定一下:把方法写在构造函数内的情况我们简称为函数内方法,把方法写在prototype属性上的情况我们简称为prototype上的方法首先我们先了解一下这篇文章的重点:函数内的方法: 使用函数内的方法我们可以访问到函数内部的私有变量,如果我们通过构造函数new出来的对象需要我们操作构造函数内部的私有变量的话, 我们这个时候就要考虑使用函数内的方法.prototype上的方法: 当我们需要通过一个函数创建大量的对象,并且这些对象还都有许多的方法的时候;这时我们就要考虑在函数的prototype上添加这些方法. 这种情况下我们代码的内存占用就比较小.在实际的应用中,这两种方法往往是结合使用的;所以我们要首先了解我们需要的是什么,然后再去选择如何使用.我们还是根据下面的代码来说明一下这些要点吧,下面是代码部分:// 构造函数Afunction A(name) { this.name = name || ‘a’; this.sayHello = function() { console.log(‘Hello, my name is: ’ + this.name); }}// 构造函数Bfunction B(name) { this.name = name || ‘b’;}B.prototype.sayHello = function() { console.log(‘Hello, my name is: ’ + this.name);};var a1 = new A(‘a1’);var a2 = new A(‘a2’);a1.sayHello();a2.sayHello();var b1 = new B(‘b1’);var b2 = new B(‘b2’);b1.sayHello();b2.sayHello();我们首先写了两个构造函数,第一个是A,这个构造函数里面包含了一个方法sayHello;第二个是构造函数B, 我们把那个方法sayHello写在了构造函数B的prototype属性上面.需要指出的是,通过这两个构造函数new出来的对象具有一样的属性和方法,但是它们的区别我们可以通过下面的一个图来说明:我们通过使用构造函数A创建了两个对象,分别是a1,a2;通过构造函数B创建了两个对象b1,b2;我们可以发现b1,b2这两个对象的那个sayHello方法 都是指向了它们的构造函数的prototype属性的sayHello方法.而a1,a2都是在自己内部定义了这个方法. 定义在构造函数内部的方法,会在它的每一个实例上都克隆这个方法;定义在构造函数的prototype属性上的方法会让它的所有示例都共享这个方法,但是不会在每个实例的内部重新定义这个方法. 如果我们的应用需要创建很多新的对象,并且这些对象还有许多的方法,为了节省内存,我们建议把这些方法都定义在构造函数的prototype属性上当然,在某些情况下,我们需要将某些方法定义在构造函数中,这种情况一般是因为我们需要访问构造函数内部的私有变量.下面我们举一个两者结合的例子,代码如下:function Person(name, family) { this.name = name; this.family = family; var records = [{type: “in”, amount: 0}]; this.addTransaction = function(trans) { if(trans.hasOwnProperty(“type”) && trans.hasOwnProperty(“amount”)) { records.push(trans); } } this.balance = function() { var total = 0; records.forEach(function(record) { if(record.type === “in”) { total += record.amount; } else { total -= record.amount; } }); return total; };};Person.prototype.getFull = function() { return this.name + " " + this.family;};Person.prototype.getProfile = function() { return this.getFull() + “, total balance: " + this.balance();};在上面的代码中,我们定义了一个Person构造函数;这个函数有一个内部的私有变量records,这个变量我们是不希望通过函数内部以外的方法 去操作这个变量,所以我们把操作这个变量的方法都写在了函数的内部.而把一些可以公开的方法写在了Person的prototype属性上,比如方法getFull和getProfile.把方法写在构造函数的内部,增加了通过构造函数初始化一个对象的成本,把方法写在prototype属性上就有效的减少了这种成本. 你也许会觉得,调用对象上的方法要比调用它的原型链上的方法快得多,其实并不是这样的,如果你的那个对象上面不是有很多的原型的话,它们的速度其实是差不多的另外,需要注意的一些地方:首先如果是在函数的prototype属性上定义方法的话,要牢记一点,如果你改变某个方法,那么由这个构造函数产生的所有对象的那个方法都会被改变.还有一点就是变量提升的问题,我们可以稍微的看一下下面的代码:func1(); // 这里会报错,因为在函数执行的时候,func1还没有被赋值. error: func1 is not a functionvar func1 = function() { console.log(‘func1’);};func2(); // 这个会被正确执行,因为函数的声明会被提升.function func2() { console.log(‘func2’);}关于对象序列化的问题.定义在函数的prototype上的属性不会被序列化,可以看下面的代码:function A(name) { this.name = name;}A.prototype.sayWhat = ‘say what…’;var a = new A(‘dreamapple’);console.log(JSON.stringify(a));我们可以看到输出结果是{“name”:“dreamapple”} ...

February 24, 2019 · 1 min · jiezi

Java面试 | 002

本博客 猫叔的博客,转载请申明出处前言本系列为猫叔综合整理的Java面试题系列,如有雷同不胜荣幸。Java与C/C++的差异?1、java为解释性语言、而C/C++为编译型语言。2、java为面向对象语言,C++则兼具面向过程和面向过程编程的特点。3、就垃圾回收而言,C++中有析构函数,而Java则是finalize()方法。4、Java提供了JDBC、分布式对象的RMI等库。为什么需要public static void main(String[] args)这个方法1、public权限修饰符,任何类、对象均可访问;2、static标记为静态方法,存储在静态存储区;3、main是JVM识别的特殊方法名;4、args为开发人员在命令行状态下雨程序交互提供的手段。5、即便如此,main函数也不是最先执行的方法!!!如何在main函数之前输出“HELLO WORLD!”由于静态块在类被加载时就会被调用,因此可以在main()方法执行前,利用静态块实现输出“HELLO WORLD”的功能。Java程序初始化的顺序是怎么样的?1、静态对象(变量)优先于非静态对象(变量)的初始化,其中,静态对象只初始化一次,而非静态变量可以初始化多次;2、父类优先于子类进行初始化;3、按照成员变量的定义顺序进行初始化Java作用域作用域与可见性当前类同一package子类其他packagepublicYESYESYESYESprivateYESNONONOprotectedYESYESYESNOdefaultYESYESNONO一个java文件可否定义多个类可以定义多个类,但是最多只能有一个类被public修饰,并且这个类的类名与文件名相同。公众号:Java猫说现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。

February 24, 2019 · 1 min · jiezi

php面试问答

结合实际PHP面试,汇总自己遇到的问题,以及网上其他人遇到的问题,尝试提供简洁准确的答案包含MySQL、Redis、Web、安全、网络协议、PHP、服务器、业务设计、线上故障、个人简历、自我介绍、离职原因、职业规划、准备问题等部分GitHub: https://github.com/colinlet/P…感觉不错的话,欢迎 Star~~一般面试流程目录 - 阅读MySQLMySQL 体系结构字段类型char 和 varchar 数据类型区别存储引擎常见索引聚族索引和非聚族索引的区别事务机制BTree 与 BTree-/BTree+ 索引原理参考资料RedisRedis 主要特点Redis 数据类型跳跃表与 Redis一致性哈希分布式锁参考资料WebJavaScript事件的三个阶段闭包原理及应用跨域JSONP 原理CSS 选择器的优先级CSS 盒子模型CSS 清除浮动相对定位 relative、浮动 float、绝对定位 absolute 区别VUE 双向绑定原理性能优化参考资料安全问题CSRF 攻击XSS 攻击SQL 注入IP 地址能被伪造吗include 请求参数md5 逆向原理DOS 攻击参考资料网络协议UDP 的主要特点TCP 握手三次,断开四次,TIME-WAITsocketHTTP 协议HTTPS 通信原理websocket 协议GET 与 POST 请求方式区别RESTful API参考资料PHPecho、print、print_r、var_dump的区别超全局变量PHP 支持回调的函数,实现一个发起 HTTP 请求有哪几种方式,它们有何区别对象关系映射/ORM(Object Relational Mapping)MVC 的理解类的静态调用和实例化调用常见 PHP 框架特点设计模式(design pattern)工厂方法模式与抽象工厂模式区别base64 编码原理ip2long 实现代码执行过程弱类型变量如何实现垃圾回收机制进程间通信方式链式调用实现多进程同时写一个文件PHP 拓展PHP7 新特性PHP7 底层优化构造函数和析构函数PHP 不实例化调用方法参考资料服务器进程、线程、协程区别Linux 进程反向代理负载均衡nginx 中 fastcgi_pass 监听,unix socket 和 tcp socket 的区别消息队列参考资料业务设计网易盖楼秒杀设计消息队列共享 SESSION下单后30分钟未支付取消订单IP对应省市效率尽可能高详细描述输入地址到打开网页过程参考资料线上故障客户端热更新失败Redis 实例 used_memory 达到80%游戏任务完成了进度未更新测试服 HTTP 请求未响应游戏账号被盗个人简历自我介绍离职原因跳槽频繁这次换工作原因职业规划准备问题工作挑战大不大?项目开发是否写测试用例,项目上线先是否会进行压力测试业务前景如何?技术氛围如何?根据这次面试,对个人进行评价,帮助成长融资计划是否有加班费/调休,公司福利,社保公积金缴纳基数声明本资料仅供参考,不保证正确性作者:凌枫 Email:colinlets@gmail.com 链接:https://github.com/colinlet/P…关键字php面试、php面试题、php面试题2019 ...

February 24, 2019 · 1 min · jiezi

【Leetcode】60. 第k个排列

题目给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:“123"“132"“213"“231"“312"“321"给定 n 和 k,返回第 k 个排列。说明:给定 n 的范围是 [1, 9]。给定 k 的范围是[1, n!]。示例 1:输入: n = 3, k = 3输出: “213"示例 2:输入: n = 4, k = 9输出: “2314"题解这道题还蛮有意思的,我首先一看,这不是backtrack的经典题目吗? backtrack的剪枝可以参看相关文章中有详细的step-by-step的流程.从小到大把数排好;用backtrack的方法遍历,每次遍历到一个全排列那么结果,count+1;遍历到n就return输出由于用的是backtrack,后面 count > k的情况都直接return掉;然后用java写了一个版本,还没有剪枝就ac啦.class Solution {int count = 0; List<Integer> finalRes; public String getPermutation(int n, int k) { int[] nums = new int[n]; for (int i = 0; i < n; i++) { nums[i] = i + 1; } //第几个解. List<Integer> resTemp = new ArrayList<>(); boolean[] haveSeen = new boolean[n]; backtrack(nums, k, resTemp, haveSeen); StringBuilder res = new StringBuilder(); for (Integer i : finalRes) { res.append(i); } return res.toString(); } public void backtrack(int[] nums, int k, List<Integer> tempIndex, boolean[] haveSeen) { if (tempIndex.size() == nums.length) { count++; } if (count == k && tempIndex.size() == nums.length) { finalRes = new ArrayList<>(tempIndex); return; } else if (count < k && tempIndex.size() == nums.length) { tempIndex = new ArrayList<>(); } for (int i = 0; i < nums.length; i++) { if (haveSeen[i]) { continue; } tempIndex.add(nums[i]); haveSeen[i] = true; backtrack(nums, k, tempIndex, haveSeen); haveSeen[i] = false; tempIndex.remove(tempIndex.size() - 1); } }}由于前几天后台有同学反馈,希望给出java版本的题解。所以又动手写了一个python版本.class Solution: def getPermutation(self, n, k): "”” :type n: int :type k: int :rtype: str "”” global count global finalRes count = 0 finalRes = [] def backtrack(nums, k, resTemp, haveSeen): global count global finalRes if count > k: return if len(resTemp) == len(nums): count += 1 if count == k and len(resTemp) == len(nums): finalRes = [str() for _ in resTemp] return elif count < k and len(resTemp) == len(nums): resTemp = [] for i in range(0, len(nums)): if count > k: break if haveSeen[i]: continue resTemp.append(nums[i]) haveSeen[i] = True backtrack(nums, k, resTemp, haveSeen) haveSeen[i] = False resTemp.pop() backtrack([ + 1 for _ in range(0, n)], k, [], [False for _ in range(0, n)]) return “".join(finalRes)后来这个版本提交的时候我以为可以洗洗睡啦.结果,卧槽,居然换一种语言就超时啦~~这倒是个意外.难道我的python写的有性能问题,不应该啊,不是:人生苦短,我用python我就继续想剪枝,还能怎么剪枝?剪枝是啥,不就是跳过某些步骤吗?那哪些步骤可以跳过呢.4的全排列是: 1 + {2,3,4}全排列2 + {1,3,4}全排列3 + {1,2,4}全排列4 + {1,2,3}全排列似乎可以转化成子问题啊.由于题目只要求出第几个,我们再看看个数的规律1 + {2,3,4}全排列(3!个)2 + {1,3,4}全排列(3!个)3 + {1,2,4}全排列(3!个)4 + {1,2,3}全排列(3!个)这就很好了呀~具体来说是:n 个数字有 n!种全排列,每种数字开头的全排列有 (n - 1)!种。所以用 k / (n - 1)! 就可以得到第 k 个全排列是以第几个数字开头的。用 k % (n - 1)! 就可以得到第 k 个全排列是某个数字开头的全排列中的第几个。数学之美啊,有木有。然后就快速实现了python的code AC了.class Solution: def getPermutation(self, n, k): nums = [str(_ + 1) for _ in range(0, n)] if k == 1: return “".join(nums) fact = 1 for i in range(2, n): fact *= i round = n - 1 k -= 1 finalRes = [] while round >= 0: index = int(k / fact) k %= fact finalRes.append(nums[index]) nums.remove(nums[index]) if round > 0: fact /= round round -= 1 return “".join(finalRes)每日英文pulicity (n.) 曝光度,知名度enhanace the ~ of yourself 提高自己的知名度publication (n.) 刊物,发表Publicize (v.) 宣传issue (v.) 发表People’s Republic Of Chinain publicRepublicans (n.)共和主义者mass (n.)群众 (v.)聚集 (a.集中的)masses of = manycivilian (a.)平民civil law 民法相关阅读46.全排列47. 全排列 II ...

February 22, 2019 · 3 min · jiezi

【持续..】基础知识梳理 - css

传送门:基础知识梳理 - css部分基础知识梳理 - JS部分最近在看大厂的一些面试题,学习巩固下这些基础知识。=================================================盒模型概念CSS盒模型本质上是一个盒子,封装周围的HTML元素,有W3C标准盒模型和IE怪异盒模型两种,由里到外分别包含:content(内容),padding(内边距),border(边框),margin(外边距)标准模型和怪异模型区别:两种盒模型的内容计算方式不同w3c标准盒模型: 总元素宽度 = content部分的宽度 总元素高度 = content部分的高度IE(怪异)盒模型: 总元素宽度 = content + padding + border 这三个部分的宽度 总元素高度 = content + padding + border 这三个部分的高度开发中常用哪种模型?为什么?常用IE盒模型,因为使用了IE盒模型的布局更可控。举例1:因为如果想保持一个盒子的真实占有宽度不变,那么加width的时候就要相应的减少左右的padding。加padding的时候就要相应的减少width,不然就会导致盒子真实占有宽度增加,导致布局发生改变,比如同一行的盒子因为宽度超出被挤下去了。举例2:移动端布局采用百分比的情况很常见,如果有两个div宽度分别为50%,横向排列,这时候如果给其中一个div加固定的border值(非百分比),还要保证两个div横向排列不掉下来,并且真实占有宽度不发生改变,这种情况下如果用IE盒模型就方便的多,不用过多调整,直接加border值就好。改变容器的盒模型组成方式借助css3的box-sizing属性 box-sizing: content-box; //W3C盒模型 box-sizing: border-box; //IE盒模型BFCBFC: 一个独立的渲染区域,它规定了内部的块级盒子如何布局,并且与这个区域外部毫不相干,在一个BFC中,块盒与行盒(行盒由一行中所有的内联元素所组成)都会垂直的沿着其父元素的边框排列。BFC布局特点:内部的Box会在垂直方向,一个接一个地放置。Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠每个元素的最左边,与包含块的最左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。BFC的区域不会与浮动盒子重叠。BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。 计算BFC的高度时,浮动元素也参与计算哪些元素会生成BFC?float属性不为noneposition为absolute或fixeddisplay为inline-block, table-cell, table-caption, flex, inline-flexoverflow不为visible应用 1. 自适应两栏布局 2. 清除内部浮动 3. 防止垂直 margin 重叠关于定位position定位的几种方式以及主要区别?static(静态定位):默认值,不脱离文档流relative(相对定位):元素相对自身偏移某个位置定位,不脱离文档流,会引起absolute(绝对定位):相对于其最近的父级块元素定位,脱离文档流fixed(固定定位):相对于可视窗口定位,脱离文档流relative和translate都是基于自身的移动,有什么区别?translate不会引起页面的重排,但relative会引起重排link 与 @import 的区别扩展:文章传送门从属关系区别: link是HTML标签,不仅可以加载 CSS 文件,还可以定义 RSS、rel 连接属性等。 @import是CSS语法,只有导入样式表的作用;加载顺序区别 link标签引入的 CSS 被同时加载; @import引入的 CSS 将在页面加载完毕后被加载。兼容性区别 link标签作为 HTML 元素,不存在兼容性问题。 @import是 CSS2.1 才有的语法,故只可在 IE5+ 才能识别;DOM可控性区别 通过JavaScript可以动态创建或者删除<link>标签,进而操作CSS @import不支持。 ...

February 22, 2019 · 1 min · jiezi

PHP面试常考内容之Memcache和Redis(3)

你好,是我琉忆。今天是周五了,再上一天班就周末了,提前祝大家周末愉快。嘿嘿。这篇文章是本周Memcache和Redis内存数据库常考的专题。本周一和周三更新的文章路径:PHP面试常考内容之Memcache和Redis(1)PHP面试常考内容之Memcache和Redis(2)本周(2019.2-18至2-22)的文章内容点为以下几点,更新时间为每周一三五,可以关注本栏持续关注,感谢你的支持。一、什么是Memcache?二、Memcache有什么特征?三、Memcache的内存管理机制是什么样的?四、Memcache和Memcached有什么区别?五、如何操作Memcache?六、如何使用Memcache做Session共享?七、什么是Redis?八、如何使用Redis?九、使用Redis需要注意哪些问题?十、新增:Redis和Memcache有什么不同?十一、新增:Redis如何实现持久化?十二、Memcache和Redis常考的面试题本章节的内容将会被分为三篇文章进行讲解完整块内容,第一篇主要讲解一到六,第二篇主要讲解七到十一(新增了十和十一),第三篇围绕第十二点。自己整理了一篇“PHP不同等级面试都问什么?”的文章,关注公众号:“琉忆编程库”,回复:“等级”,我发给你。以下正文的部分内容来自《PHP程序员面试笔试真题解析》书籍,如果转载请保留出处:十二、Memcache和Redis常考的面试题【真题1】Memcache的工作原理是什么?答案:Memcache的工作就是在专门的机器内存里维护一张巨大的hash表,存储经常被读写的一些文件与数据,从而极大地提高网站的运行效率。 Memcache的程序运行在一个或多个服务器中,Memcache把全部的数据保存在内存中,通过hash表的方式,每条数据由key/value的形式构成,随时接受客户端的请求,然后返回结果。客户端与Memcache建立连接后,存储对象主要是通过唯一的key存储value到内存中,取数据时通过这个key从内存中获取对应的value。由于Memcache的数据是存储在内存中而不是保存在cache文件中,所以Memcache访问比较快,但是由于这些数据不是永久化存储,所以不建议存储重要数据在Memcache中,因为重启服务器后这些数据就会消失。【真题2】Memcache的优点有哪些?答案:Memcache是一个高性能的分布式内存对象缓存系统,主要通过在内存里维护一个巨大的hash表进行数据缓存。它主要是将数据存储到内存中,然后从内存中读取数据,从而提高读取速度。它主要通过key-value的形式存储各种数据,包括图像、视频、文件等。它具有以下几点优点:(1)支持多台服务器使用Memcache,由于Memcache的存储数据大小必须小于内存的大小,所以可以将Memcache使用在多台服务器上,增加缓存容量;(2)支持均衡请求。当使用多台Memcache服务器时,可以均衡请求,避免所有请求都进入一台Memcache服务器中,避免服务器挂掉而丢失数据;(3)支持分布式,可以解决缓存本身水平线性扩展的问题和缓存大并发下的自身性能问题,避免缓存的单点故障问题;(4)支持部分容灾问题,如果多台服务器存储了Memcache数据,其中一台Memcache服务器挂掉,部分请求还是可以在其它服务器的Memcache中命中,为修复挂掉的服务器争取一些时间。【真题3】如何合理地使用Memcache缓存?如果缓存数据量过大,那么如何部署?(分布式,缓存时间,优化缓存数据)答案:如果要合理地使用Memcache缓存,那么需要注意以下几点内容:(1)因为Memcache支持最大的存储对象大小为1M,所以当合理使用Memcache缓存时,要求不能往Memcache存储一个大于1MB的数据;(2)Memcache存储的所有数据,如果数据大小分布于各种chunk大小区间,从64B到1MB都有,那么会造成内存的极大浪费和Memcache的异常。所以需要注意数据大小的分布区间;(3)key的长度不能大于250个字符;(4)虚拟主机不允许运行Memcache服务,所以不能把Memcache部署到虚拟主机中;(5)因为Memcache可以轻松访问到,所以可以运行在不安全的环境中,如果对数据安全要求高,那么需要着重考虑运行环境的安全问题;(6)因为Memcache存储的数据都在内存中,服务器挂掉就会清空内存,所以缓存中的数据尽量是丢失了也不会有太大影响的数据。如果缓存中的数据量过大,那么可以采取以下的办法:(1)使用Memcache服务器集群的方法,首先是将数据安排放在不同的Memcache服务器上,可以将不同硬件服务器上的Memcache服务器再做成一个数据互相备份的组,避免数据的单点丢失问题;(2)缓存数据到数据库中,在数据库中先建一张表来说明Memcache服务器集群中缓存数据的存放逻辑,然后实现把缓存数据存到数据库中,可以保证数据库和缓存的数据双向存取。【真题4】Redis 与 Memcache有什么区别?答案:Redis是一个完全开源免费的高性能key-value数据库,具有丰富的数据类型,可以支持数据的持久化,将内存中的数据保存在磁盘中,当重启服务器时可以再次加载使用。Memcache是一个高性能的分布式内存对象缓存系统,用于动态的Web应用中帮助数据库减轻负担,在内存中缓存数据和对象,减少每次访问数据时对数据库的访问次数,从而提高访问速度。它们具有以下几点区别:(1)Redis和Memcache的最大区别是,虽然Memcache和Redis都是将数据存在内存中,是内存数据库,但Redis存储时,并不是所有的数据都一直存储在内存中,而Memcache存储时,数据都存在内存中;(2)数据安全问题,由于memecache 把数据全部存在内存之中,服务器挂掉后,重启服务器数据就会丢失,而Redis可以定期保存数据到磁盘中做持久化存储,当需要时可以再加载使用。对于灾难恢复,Memcache挂掉后,数据不可恢复,但Redis数据丢失后可以通过aof恢复;(3)Redis支持多种数据结构存储,例如list,set,hash等数据结构的存储,而Memcache主要是在内存中维护一个统一的巨大的hash表进行存储数据,只支持简单的key/value类型的数据存储,但Memcache可以存储图片、视频、文件及数据库检索结果等;(4)数据备份问题,Redis支持数据的备份,即master-slave模式的数据备份。而Memcache不支持数据持久化,所以无法进行数据备份;(5)在内存使用率上,使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。具体和应用场景、数据特性有关;(6)在线程上的比较,Memcache是支持多线程的,而Redis只支持单线程,所以CPU利用方面Memcache优于Redis;(7)它们的扩展都需要做集群;实现方式:master-slave、Hash;(8)数据的读写方面,Redis和Memcache在写入性能上面差别不大,读取性能上面尤其是批量读取性能上Memcache更强。【真题5】Redis集群方案应该怎么做?都有哪些方案?答案:1.twemproxy,大概概念是,它类似于一个代理方式,使用方法和普通redis无任何区别,设置好它下属的多个redis实例后,使用时在本需要连接redis的地方改为连接twemproxy,它会以一个代理的身份接收请求并使用一致性hash算法,将请求转接到具体redis,将结果再返回twemproxy。使用方式简便(相对redis只需修改连接端口),对旧项目扩展的首选。 问题:twemproxy自身单端口实例的压力,使用一致性hash后,对redis节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。2.codis,目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新hash节点。3.redis cluster3.0自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。4.在业务代码层实现,起几个毫无关联的redis实例,在代码层,对key 进行hash计算,然后去对应的redis实例操作数据。 这种方式对hash层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。自己整理了一篇“PHP不同等级面试都问什么?”的文章,关注公众号:“琉忆编程库”,回复:“等级”,我发给你。【真题6】Redis有哪些适合的场景?答案:(1)、会话缓存(Session Cache)最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。(2)、全页缓存(FPC)除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。(3)、队列Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。(4),排行榜/计数器Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:ZRANGE user_scores 0 10 WITHSCORESAgora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。(5)、发布/订阅最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。【真题7】Redis持久化数据和缓存怎么做扩容?答案:如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。【真题8】Redis回收进程如何工作的?答案:一个客户端运行了新的命令,添加了新的数据。Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。一个新的命令被执行,等等。所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。【真题9】都有哪些办法可以降低Redis的内存使用情况呢?答案:如果你使用的是32位的Redis实例,可以好好利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。【真题10】你知道有哪些Redis分区实现方案?答案:客户端分区就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取。大多数客户端已经实现了客户端分区。代理分区 意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。自己根据不同PHP不同等级面试时,会问哪些PHP常考的知识点整理成了一篇文章自己整理了一篇“PHP不同等级面试都问什么?”的文章,关注公众号:“琉忆编程库”,回复:“等级”,我发给你。更多相关面试常考真题可以阅读《PHP程序员面试笔试真题解析》。预告:下周(2019.2.25至2019.3.1)一三五将更新的主题为:PHP面试之会话控制、网络协议、相关的面试题。以上内容摘自《PHP程序员面试笔试真题解析》书籍,该书已在天猫、京东、当当等电商平台销售。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,我将不断完善追求极致,感谢你们的支持。

February 22, 2019 · 1 min · jiezi

技术面试老是有劲使不出,该怎么办?

又到了一年金三银四,回想到很多年前我刚参加工作时的面试经历,那时都是呆呆地等着面试官问问题,被问到一些自己并不熟悉的问题时要不就是思考半天也切不中要点,要不就只能无奈地回答并不清楚了。其实不管是经验不足的初级开发,还是面临更高要求的资深开发,在面试上都会有一样的困扰:如何在掌握的知识有限的情况下,完成好一场高质量的面试呢?本文最适合以下几种情况的同学参考:知识面有限的初学者希望首次加入大公司的开发同学面临技术深度要求的开发同学虽然文中主要以后端开发为例介绍技术面试的准备方法,但是和其他岗位的面试也有相似之处,其他朋友也可以作为参考。两个悲剧的面试故事小黄去年刚从计算机专业毕业,但是感觉对技术掌握得还不够,就又报名参加了一个培训班,年底刚从培训班毕业,怀揣着精心准备的简历,想到杭州闯一闯。今天上午到了一家装修还不错的公司面试,感觉这家公司窗明桌净,办公桌前都是Mac笔记本加双屏显示器,比昨天面的那家外包公司可好多了,心想一定要好好发挥抓住这个机会。老王工作五六年了,今年刚从一家不小的公司出来,公司效益不好,年底裁员了,不幸老王在年前的最后一次裁员名单中中奖了。虽然短期内也没什么太大的经济问题,但是之前拿到offer的都是一些没上轨道的小公司,跟之前待过的公司比总感觉是明珠暗投不太甘心。这次面试的公司看上去还满正规的,如果能进去,就算不涨薪,但是心理上落差总是小一点。小黄按部就班地说完了自己准备的项目介绍,然后面试官就照着简历问了起来。“你熟悉MySQL数据库?”,“对”,“唔…那你能说说MySQL常用的有哪几种存储引擎吗?”,“啊?什么是存储引擎?”。小黄心想,昨天复习了面试宝典里的数据库索引,老师说这个是要点,面试官怎么不按常理出牌呢。“你简历上写了使用过Kafka消息中间件?”,“嗯,对的”,“那你能说说Kafka的拉模型和其他推模型消息中间件的区别吗?”,“…”,“今天面试就到这里,有后续的话HR在一周内会联系你的”。老王在隔壁的会议室里坐了下来,面试官让老王先介绍一下自己最近比较有代表性的项目,老王大概说了说自己这两年做的一个平台的功能。面试官问:“在这个项目过程中你有碰到什么印象比较深的技术难点或者业务难点吗?”,老王想了一想,感觉一下子有点想不起来。面试官继续问:“刚才你介绍的项目里有说到XXX功能,那在YYY异常情况下如果处理ZZZ问题呢?”。老王心想以前也没考虑过这个,那我就直接现场推敲一下吧,“这里要先这样,对了,那里要那样,不对,这里还有一种情况”。十分钟过去了,老王还在原地兜着圈子,而且丝毫没有停下来的意思。面试官只能打断老王换了一个问题:“那你能介绍一个你有过深入了解的技术吗?”,老王:“我好像这几个都了解一点”。“那关系型数据库中的聚集索引和非聚集索引有什么区别呢?”,“好像这两个的存储方式有点不同,非聚集索引开销会大一点”,“那么为什么非聚集索引开销会比较大呢?”,“这个问题好像要涉及操作系统之类的知识,有点太深了”。40分钟后,老王也结束了面试。程序员何苦为难程序员?为什么面试官老是会问一些莫名其妙的问题?我怎么可能了解计算机方面的所有问题呢?为什么他问得这么深,写代码的时候能有什么用?之前我在一次失败的面试之后总是感觉有点不舒服,我感觉我的水平也是挺不错的,为什么到了面试就总感觉发挥不出,而且老是被“虐”呢?下面我们一起来想想办法。该怎么办?不打无准备之仗!如何完成好一场高质量的面试?这是我们在文章一开头提出的问题。首先我们要知道,我们掌握的知识一定是有限的,不管学到了多少,仍然还会有不知道的知识。特别是对技术深度有要求的资深开发岗位,想要面面俱到几乎是一个不可能的任务。但是为什么有些人就能很轻易地拿到offer呢?首先,我们分析一下一场面试的结构。面试的结构一般技术面试都会分为四个部分:项目介绍/自我介绍、技术能力问答、综合能力问答、反向提问环节。而其中前三个部分对面试的成败影响最大。首先,面试一般都会以项目介绍/自我介绍来进行切入,其实技术面试的自我介绍主要也是介绍自己的项目经验。然后会穿插着对技术能力和综合能力的评估,一般这一步都会由之前的项目介绍引申出来。一般来说,面试官在每个环节希望了解的主要是:项目介绍——基本背景、擅长的业务领域、解决问题的能力、技术层级技术能力问答——技术深度、分析能力、抽象能力、学习能力综合能力问答——工作素养、协作能力、学习素养虽然看上去眼花缭乱的非常多,其实核心关注的就是几点:技术好、好合作、主动性强。对于大部分面试官来说,一般也不会做太多事前准备和细密的事后分析,大多都是靠一个大概的印象来做出判断的。所以面试的一个诀窍就是:突出亮点。一旦面试中有让人印象深刻的亮点,其他方面只要不是太差一般面试就十拿九稳了。单点突破但是很多读者应该和我以前一样,觉得自己好像也没什么亮点啊,总感觉没什么特别的可说。但是通过总结和准备,我相信每一个人都可以有自己的亮点,而且是很多个亮点。首先,我们可以通过总结来得到一份优秀的项目介绍。关键点就在于不能光介绍项目完成的需求,因为这些对于面试官来说并没有什么价值,毕竟我们面的是开发岗位不是产品岗位:)在项目介绍中,我们应该明确描述我们在项目中解决的问题,包括技术难题和业务难题。如何表述可以参考一下现在流行的STAR法则,即在什么情况下(Situation),面临一项什么样的任务(Task),采取了什么样的行动(Action),达到了怎样的结果(Result)。这样的表述可以很好地展示自己的解决问题能力,充分显示了自己在工作中的主动性。然后,我们还可以通过准备来深挖几个在工作中比较重要的技术点作为“技术亮点”展示,这指的不是仅仅通过“面试宝典”来准备的肤浅的理论知识,而应该是能做到真正的言之有物、切中工作要点的实践性的深入知识点。这一步是本文所有面试准备技巧中唯一对技术水平有要求而且是要求很高的步骤,技术水平不够的朋友可能发愁怎么准备,技术水平比较高的朋友可能又会疑惑到底什么是实践性的深入知识点。大家不用担心,我已经为大家准备好了学习材料或者说是参考样例。通过学习或参考工作经验较丰富者的总结文章我们可以更容易地获得自己的技术亮点。实践性的深入知识点的最大特点就是:工作中会使用且可以层层深入形成问题链条。关系型数据库是后端开发离不开的技术,而数据库索引又是程序中的SQL语句执行效率的关键,大家可以通过我之前编写的一系列数据库索引相关的文章来了解一个实践性的深入知识点到底长什么样。后续我也会不断更新更多浅显易懂的高阶技术,有兴趣的朋友可以持续关注一下。下面的文章主要以知识点讲解为主,需要具体的一系列相关面试题的朋友可以在文后留言,如果人数比较多我会考虑另外写一篇文章讲解具体的面试题并链接到对应的知识点。数据库索引是什么?新华字典来帮你 —— 理解数据库索引融会贯通 —— 深入20分钟数据库索引设计实战 —— 实战数据库索引为什么用B+树实现? —— 扩展连点成面但是有了准备之后,我们怎么能防止面试官问出很多超出我们准备范围的“奇怪”的问题呢?这就需要我们有效地引导和填充整个面试的过程。一般一场技术面试会在40分钟到60分钟,如何完成好一场技术面试的关键就在于如何用亮点来充满这40到60分钟。一般一个技术亮点会包含一系列层层递进的内容,所以可以问出大概三到五个问题。如果发现面试官不知道应该如何追问,那么你可以简短一些地把后续知识点一次性介绍完。一般这些问题都说完时间就过去了十到十五分钟,而项目介绍和相关的问答会占用大概10分钟。如果我们准备了两个技术亮点,加上前后的一些非技术性问题和反向提问,那么基本上一场面试就圆满地结束了。那么如何让面试官问出自己想问的问题呢?这就需要我们准备好一个“剧本”了。首先,面试的开场一定是项目介绍,可以将我们准备好的技术亮点与项目介绍中解决的技术难题结合起来,这样面试官基本都会根据这个点继续往下追问。其次,我们还可以对简历内容进行特意的编排来诱导面试官主动提出我们准备好的问题。例如在简历中把擅长的技术放在更靠上的位置突出显示,并且增加更多的深入解释。这样不仅可以引起面试官的注意,还能使简历显得更有技术含量,更容易通过筛选。最后,还可以厚着脸皮主动提出自己对某一个领域比较了解,尝试询问面试官对这一部分有没有兴趣。通过上述这三点,基本上80%以上的面试就能够顺顺利利地圆满完成了。问题总结在掌握了面试的技巧之后,我们再来看看之前小黄和老王到底犯了什么错误,我们也能避免踩坑。初级开发小黄的问题:惜字如金,没有充分回答问题;对于面试官提出的“是否用过”“是否了解”这样的问题,应该补充上能够证明自己是内行的解释。等着面试官提问,没有主动地引导。我们应该主动将问题引导向自己擅长的方面。资深开发老王的问题:项目经历没有准备;只讲了项目的功能,而不讲过程中遇到的业务难点和技术难点。过分纠结细节,但是表述又不流畅;问题抓不住重点,从细节开始推敲,反反复复。表述复杂问题时应该从宏观到微观。先从比较高的层次入手,划分大的模块,确定模块间的交互,然后再逐个模块地细化细节。这样不仅自己能够更容易地解决问题,而且面试官也更容易理解,避免发生即使回答了正确答案但是面试官也不认同的情况。

February 22, 2019 · 1 min · jiezi

vue:虚拟dom的实现

那么为什么要用 VDOM:现代 Web 页面的大多数逻辑的本质就是不停地修改DOM,但是 DOM 操作太慢了,直接导致整个页面掉帧、卡顿甚至失去响应。然而仔细想一想,很多 DOM 操作是可以打包(多个操作压成一个)和合并(一个连续更新操作只保留最终结果)的,同时 JS 引擎的计算速度要快得多,能不能把 DOM 操作放到 JS 里计算出最终结果来一发终极 DOM 操作?答案——当然可以!Vitual DOM是一种虚拟dom技术,本质上是基于javascript实现的,相对于dom对象,javascript对象更简单,处理速度更快,dom树的结构,属性信息都可以很容易的用javascript对象来表示:let element={ tagName:‘ul’,//节点标签名 props:{//dom的属性,用一个对象存储键值对 id:’list’ }, children:[//该节点的子节点 {tagName:’li’,props:{class:‘item’},children:[‘aa’]}, {tagName:’li’,props:{class:‘item’},children:[‘bb’]}, {tagName:’li’,props:{class:‘item’},children:[‘cc’]} ]}对应的html写法是:<ul id=‘list’> <li class=‘item’>aa</li> <li class=‘item’>aa</li> <li class=‘item’>aa</li></ul>Virtual DOM并没有完全实现DOM,Virtual DOM最主要的还是保留了Element之间的层次关系和一些基本属性. 你给我一个数据,我根据这个数据生成一个全新的Virtual DOM,然后跟我上一次生成的Virtual DOM去 diff,得到一个Patch,然后把这个Patch打到浏览器的DOM上去。我们可以通过javascript对象表示的树结构来构建一棵真正的dom树,当数据状态发生变化时,可以直接修改这个javascript对象,接着对比修改后的javascript对象,记录下需要对页面做的dom操作,然后将其应用到真正的dom树,实现视图的更新,这个过程就是Virtual DOM的核心思想。VNode的数据结构图:VNode生成最关键的点是通过render有2种生成方式,第一种是直接在vue对象的option中添加render字段。第二种是写一个模板或指定一个el根元素,它会首先转换成模板,经过html语法解析器生成一个ast抽象语法树,对语法树做优化,然后把语法树转换成代码片段,最后通过代码片段生成function添加到option的render字段中。ast语法优的过程,主要做了2件事:会检测出静态的class名和attributes,这样它们在初始化渲染后就永远不会再被比对了。会检测出最大的静态子树(不需要动态性的子树)并且从渲染函数中萃取出来。这样在每次重渲染时,它就会直接重用完全相同的vnode,同时跳过比对。src/core/vdom/create-element.jsconst SIMPLE_NORMALIZE = 1const ALWAYS_NORMALIZE = 2function createElement (context, tag, data, children, normalizationType, alwaysNormalize) { // 兼容不传data的情况 if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } // 如果alwaysNormalize是true // 那么normalizationType应该设置为常量ALWAYS_NORMALIZE的值 if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE // 调用_createElement创建虚拟节点 return _createElement(context, tag, data, children, normalizationType)}function _createElement (context, tag, data, children, normalizationType) { /** * 如果存在data.ob,说明data是被Observer观察的数据 * 不能用作虚拟节点的data * 需要抛出警告,并返回一个空节点 * 被监控的data不能被用作vnode渲染的数据的原因是: * data在vnode渲染过程中可能会被改变,这样会触发监控,导致不符合预期的操作 / if (data && data.ob) { process.env.NODE_ENV !== ‘production’ && warn( Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n + ‘Always create fresh vnode data objects in each render!’, context ) return createEmptyVNode() } // 当组件的is属性被设置为一个falsy的值 // Vue将不会知道要把这个组件渲染成什么 // 所以渲染一个空节点 if (!tag) { return createEmptyVNode() } // 作用域插槽 if (Array.isArray(children) && typeof children[0] === ‘function’) { data = data || {} data.scopedSlots = { default: children[0] } children.length = 0 } // 根据normalizationType的值,选择不同的处理方法 if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } let vnode, ns // 如果标签名是字符串类型 if (typeof tag === ‘string’) { let Ctor // 获取标签名的命名空间 ns = config.getTagNamespace(tag) // 判断是否为保留标签 if (config.isReservedTag(tag)) { // 如果是保留标签,就创建一个这样的vnode vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) // 如果不是保留标签,那么我们将尝试从vm的components上查找是否有这个标签的定义 } else if ((Ctor = resolveAsset(context.$options, ‘components’, tag))) { // 如果找到了这个标签的定义,就以此创建虚拟组件节点 vnode = createComponent(Ctor, data, context, children, tag) } else { // 兜底方案,正常创建一个vnode vnode = new VNode( tag, data, children, undefined, undefined, context ) } // 当tag不是字符串的时候,我们认为tag是组件的构造类 // 所以直接创建 } else { vnode = createComponent(tag, data, context, children) } // 如果有vnode if (vnode) { // 如果有namespace,就应用下namespace,然后返回vnode if (ns) applyNS(vnode, ns) return vnode // 否则,返回一个空节点 } else { return createEmptyVNode() }}方法的功能是给一个Vnode对象对象添加若干个子Vnode,因为整个Virtual DOM是一种树状结构,每个节点都可能会有若干子节点。然后创建一个VNode对象,如果是一个reserved tag(比如html,head等一些合法的html标签)则会创建普通的DOM VNode,如果是一个component tag(通过vue注册的自定义component),则会创建Component VNode对象,它的VnodeComponentOptions不为Null.创建好Vnode,下一步就是要把Virtual DOM渲染成真正的DOM,是通过patch来实现的,源码如下:src/core/vdom/patch.js return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { // oldVnoe:dom||当前vnode,vnode:vnoder=对象类型,hydration是否直接用服务端渲染的dom元素 if (isUndef(vnode)) { if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return } let isInitialPatch = false const insertedVnodeQueue = [] if (isUndef(oldVnode)) { // 空挂载(可能是组件),创建新的根元素。 isInitialPatch = true createElm(vnode, insertedVnodeQueue, parentElm, refElm) } else { const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(oldVnode, vnode)) { // patch 现有的根节点 patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) } else { if (isRealElement) { // 安装到一个真实的元素。 // 检查这是否是服务器渲染的内容,如果我们可以执行。 // 成功的水合作用。 if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { oldVnode.removeAttribute(SSR_ATTR) hydrating = true } if (isTrue(hydrating)) { if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { invokeInsertHook(vnode, insertedVnodeQueue, true) return oldVnode } else if (process.env.NODE_ENV !== ‘production’) { warn( ‘The client-side rendered virtual DOM tree is not matching ’ + ‘server-rendered content. This is likely caused by incorrect ’ + ‘HTML markup, for example nesting block-level elements inside ’ + ‘<p>, or missing <tbody>. Bailing hydration and performing ’ + ‘full client-side render.’ ) } } // 不是服务器呈现,就是水化失败。创建一个空节点并替换它。 oldVnode = emptyNodeAt(oldVnode) } // 替换现有的元素 const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) // create new node createElm( vnode, insertedVnodeQueue, // 极为罕见的边缘情况:如果旧元素在a中,则不要插入。 // 离开过渡。只有结合过渡+时才会发生。 // keep-alive + HOCs. (#4590) oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ) // 递归地更新父占位符节点元素。 if (isDef(vnode.parent)) { let ancestor = vnode.parent const patchable = isPatchable(vnode) while (ancestor) { for (let i = 0; i < cbs.destroy.length; ++i) { cbs.destroyi } ancestor.elm = vnode.elm if (patchable) { for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, ancestor) } // #6513 // 调用插入钩子,这些钩子可能已经被创建钩子合并了。 // 例如使用“插入”钩子的指令。 const insert = ancestor.data.hook.insert if (insert.merged) { // 从索引1开始,以避免重新调用组件挂起的钩子。 for (let i = 1; i < insert.fns.length; i++) { insert.fnsi } } } else { registerRef(ancestor) } ancestor = ancestor.parent } } // destroy old node if (isDef(parentElm)) { removeVnodes(parentElm, [oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm }patch支持的3个参数,其中oldVnode是一个真实的DOM或者一个VNode对象,它表示当前的VNode,vnode是VNode对象类型,它表示待替换的VNode,hydration是bool类型,它表示是否直接使用服务器端渲染的DOM元素,下面流程图表示patch的运行逻辑:patch运行逻辑看上去比较复杂,有2个方法createElm和patchVnode是生成dom的关键,源码如下:/* * @param vnode根据vnode的数据结构创建真实的dom节点,如果vnode有children则会遍历这些子节点,递归调用createElm方法, * @param insertedVnodeQueue记录子节点创建顺序的队列,每创建一个dom元素就会往队列中插入当前的vnode,当整个vnode对象全部转换成为真实的dom 树时,会依次调用这个队列中vnode hook的insert方法 / let inPre = 0 function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) { vnode.isRootInsert = !nested // 过渡进入检查 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } const data = vnode.data const children = vnode.children const tag = vnode.tag if (isDef(tag)) { if (process.env.NODE_ENV !== ‘production’) { if (data && data.pre) { inPre++ } if ( !inPre && !vnode.ns && !( config.ignoredElements.length && config.ignoredElements.some(ignore => { return isRegExp(ignore) ? ignore.test(tag) : ignore === tag }) ) && config.isUnknownElement(tag) ) { warn( ‘Unknown custom element: <’ + tag + ‘> - did you ’ + ‘register the component correctly? For recursive components, ’ + ‘make sure to provide the “name” option.’, vnode.context ) } } vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode) setScope(vnode) / istanbul ignore if / if (WEEX) { // in Weex, the default insertion order is parent-first. // List items can be optimized to use children-first insertion // with append=“tree”. const appendAsTree = isDef(data) && isTrue(data.appendAsTree) if (!appendAsTree) { if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } createChildren(vnode, children, insertedVnodeQueue) if (appendAsTree) { if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } } else { createChildren(vnode, children, insertedVnodeQueue) if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } if (process.env.NODE_ENV !== ‘production’ && data && data.pre) { inPre– } } else if (isTrue(vnode.isComment)) { vnode.elm = nodeOps.createComment(vnode.text) insert(parentElm, vnode.elm, refElm) } else { vnode.elm = nodeOps.createTextNode(vnode.text) insert(parentElm, vnode.elm, refElm) } }方法会根据vnode的数据结构创建真实的DOM节点,如果vnode有children,则会遍历这些子节点,递归调用createElm方法,InsertedVnodeQueue是记录子节点创建顺序的队列,每创建一个DOM元素就会往这个队列中插入当前的VNode,当整个VNode对象全部转换成为真实的DOM树时,会依次调用这个队列中的VNode hook的insert方法。/* * 比较新旧vnode节点,根据不同的状态对dom做合理的更新操作(添加,移动,删除)整个过程还会依次调用prepatch,update,postpatch等钩子函数,在编译阶段生成的一些静态子树,在这个过程 * @param oldVnode 中由于不会改变而直接跳过比对,动态子树在比较过程中比较核心的部分就是当新旧vnode同时存在children,通过updateChildren方法对子节点做更新, * @param vnode * @param insertedVnodeQueue * @param removeOnly */ function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) { if (oldVnode === vnode) { return } const elm = vnode.elm = oldVnode.elm if (isTrue(oldVnode.isAsyncPlaceholder)) { if (isDef(vnode.asyncFactory.resolved)) { hydrate(oldVnode.elm, vnode, insertedVnodeQueue) } else { vnode.isAsyncPlaceholder = true } return } // 用于静态树的重用元素。 // 注意,如果vnode是克隆的,我们只做这个。 // 如果新节点不是克隆的,则表示呈现函数。 // 由热重加载api重新设置,我们需要进行适当的重新渲染。 if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance return } let i const data = vnode.data if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode) } const oldCh = oldVnode.children const ch = vnode.children if (isDef(data) && isPatchable(vnode)) { for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode) } if (isUndef(vnode.text)) { if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) } else if (isDef(ch)) { if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ‘’) addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) } else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1) } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ‘’) } } else if (oldVnode.text !== vnode.text) { nodeOps.setTextContent(elm, vnode.text) } if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) } }updateChildren方法解析在此:vue:虚拟DOM的patch ...

February 21, 2019 · 6 min · jiezi

什么是闭包?闭包的优缺点?

什么是闭包?闭包的优缺点? 闭包(closure)是javascript的一大难点,也是它的特色。很多高级应用都要依靠闭包来实现。1、变量作用域要理解闭包,首先要理解javascript的特殊的变量作用域。变量的作用域无非就两种:全局变量和局部变量。javascript语言的特别之处就在于:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。注意点:在函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明的是一个全局变量!2、如何从外部读取函数内部的局部变量?出于种种原因,我们有时候需要获取到函数内部的局部变量。但是,上面已经说过了,正常情况下,这是办不到的!只有通过变通的方法才能实现。那就是在函数内部,再定义一个函数。 function f1(){ var n=999; function f2(){ alert(n); // 999 } }在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!3、闭包的概念上面代码中的f2函数,就是闭包。各种专业文献的闭包定义都非常抽象,我的理解是: 闭包就是能够读取其他函数内部变量的函数。由于在javascript中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成“定义在一个函数内部的函数“。所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。4、闭包的用途闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在f1调用后被自动清除。为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}“这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。5、使用闭包的注意点(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

February 21, 2019 · 1 min · jiezi

【Leetcode】75.颜色分类

作者: 码蹄疾毕业于哈尔滨工业大学。 小米广告第三代广告引擎的设计者、开发者; 负责小米应用商店、日历、开屏广告业务线研发; 主导小米广告引擎多个模块重构; 关注推荐、搜索、广告领域相关知识;题目给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。注意:不能使用代码库中的排序函数来解决这道题。示例:输入: [2,0,2,1,1,0]输出: [0,0,1,1,2,2]进阶:一个直观的解决方案是使用计数排序的两趟扫描算法。首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。你能想出一个仅使用常数空间的一趟扫描算法吗?题解首先这个题目你肯定不能用数数的方法啊,如果你用了相当于说:“面试官傻逼!”那怎么来解这种问题呢?我给大家说说一个不是那么绝对的经验,涉及到数组和链表的题目,先想想双指针法可不可以用。这个题目用三个指针:index 表示当前遍历的元素p1 记录最后一个0的位置p2 记录最开始一个2的位置然后从左到右便利,调整index、p1、p2元素java版本 public void sortColors(int[] nums) { // 1-pass int p1 = 0, p2 = nums.length - 1, index = 0; while (index <= p2) { if (nums[index] == 0) { nums[index] = nums[p1]; nums[p1] = 0; p1++; } if (nums[index] == 2) { nums[index] = nums[p2]; nums[p2] = 2; p2–; index–; } index++; } }python版本class Solution: def sortColors(self, nums): """ :type nums: List[int] :rtype: void Do not return anything, modify nums in-place instead. """ p1 = 0 p2 = len(nums) - 1 index = 0 while index <= p2: if nums[index] == 0: nums[index] = nums[p1] nums[p1] = 0 p1 += 1 if nums[index] == 2: nums[index] = nums[p2] nums[p2] = 2 p2 -= 1 index -= 1 index += 1热门文章【Leetcode】74. 搜索二维矩阵【Leetcode】73.矩阵置零【Leetcode】72.编辑距离 ...

February 21, 2019 · 1 min · jiezi

javascript 面试的完美指南(开发者视角)

为了说明 JS 面试的复杂性,首先,请尝试给出以下结果:onsole.log(2.0 == “2” == new Boolean(true) == “1”)十有八九的会给出false, 其实运行结果是true,原因请看 这里。1) 理解 JS 函数函数是 JavaScript 的精华,是 JS 一等公民。JS 函数不仅仅是一个普通的函数,与其他语言不同,JS 函数可以赋值给变量,作为参数传递给另一个函数,也可以从另一个函数返回。console.log(square(5));/* … /function square(n) { return n * n; }以为代码很简单,大家应该都知道会打印:25。接着看一个:console.log(square(5)); var square = function(n) { return n * n; }乍一看,你可能会忍不住说也打印了 25。但很不幸,会报错:TypeError: square is not a function在 JavaScript 中,如果将函数定义为变量,变量名将被提升,是 JS 执行到它的定义才能被访问。你可能在一些代码中频繁的见到如下代码。var simpleLibrary = function() { var simpleLibrary = { a, b, add: function(a, b) { return a + b; }, subtract: function(a, b) { return a - b; } } return simpleLibrary;}();为什么会做这种奇怪的事情? 这是因为一个函数变量中变量和函数被分装,可以避免全局变量污染。 JQuery 到Lodash 的库采用这种技术提供 $、_ 等2) 理解 bind、apply 和 call你可能在所有常用库中看到过这三个函数。它们允许局部套用, 我们可以把功能组合到不同的函数。一个优秀的js开发者可以随时告诉你关于这三个函数。基本上,这些是改变行为以实现某些功能的原型方法,根据 JS 开发人员 Chad 的说法,用法如下:希望使用某个上下文调用该函数,请使用 .bind() ,这在事件中很有用。 如果要立即调用函数,请使用.call() 或 .apply(),并修改上下文。举例说明让我们看看上面的陈述是什么意思! 假设你的数学老师要求你创建一个库并提交。你写了一个抽象的库,它可以求出圆的面积和周长:var mathLib = { pi: 3.14, area: function(r) { return this.pi * r * r; }, circumference: function(r) { return 2 * this.pi * r; }};提交后,老师调用了它:mathLib.area(2);12.56老师发现他给你要求是 pi 精确到小数点后 5 位数而你只精确到 2 位, 现在由于最后期限已过你没有机会提交库。 这里 JS的 call 函数可以帮你, 只需要调用你的代码如下:mathLib.area.call({pi: 3.1.159}, 2)它会动态地获取新的 pi 值,结果如下:12.56636这时,注意到 call 函数具有两个参数:Context函数参数在 area 函数中, 上下文是对象被关键词 this 代替,后面的参数作为函数参数被传递。 如下:var cylinder = { pi: 3.14, volume: function(r, h) { return this.pi * r * r * h; }};调用方式如下:cylinder.volume.call({pi: 3.14159}, 2, 6);75.39815999999999Apply 类似,只是函数参数作为数组传递。cylinder.volume.apply({pi: 3.14159}, [2, 6]);75.39815999999999如果你会使用 call 你基本就会用 apply 了,反之亦然, 那 bind 的用法又是如何呢 ?bind 将一个全新的 this 注入到指定的函数上,改变 this 的指向, 使用 bind 时,函数不会像 call 或 apply 立即执行。var newVolume = cylinder.volume.bind({pi: 3.14159});newVolume(2,6); // Now pi is 3.14159bind 用途是什么?它允许我们将上下文注入一个函数,该函数返回一个具有更新上下文的新函数。这意味着这个变量将是用户提供的变量,这在处理 JavaScript 事件时非常有用。3) 理解 js 作用域(闭包)JavaScript 的作用域是一个潘多拉盒子。从这一个简单的概念中,就可以构造出数百个难回答的面试问题。有三种作用域:全局作用域本地/函数作用域块级作用域(ES6引进)全局作用域事例如下:x = 10;function Foo() { console.log(x); // Prints 10}Foo()函数作用域生效当你定义一个局部变量时:pi = 3.14;function circumference(radius) { pi = 3.14159; console.log(2 * pi * radius); // 打印 “12.56636” 不是 “12.56”}circumference(2);ES16 标准引入了新的块作用域,它将变量的作用域限制为给定的括号块。var a = 10;function Foo() { if (true) { let a = 4; } alert(a); // alerts ‘10’ because the ’let’ keyword}Foo();函数和条件都被视为块。以上例子应该弹出 4,因为 if 已执行。但 是ES6 销毁了块级变量的作用域,作用域进入全局。现在来到神奇的作用域,可以使用闭包来实现,JavaScript 闭包是一个返回另一个函数的函数。如果有人问你这个问题,编写一个输入一个字符串并逐次返回字符。 如果给出了新字符串,则应该替换旧字符串,类似简单的一个生成器。function generator(input) { var index = 0; return { next: function() { if (index < input.lenght) { return input[index -1]; } return “”; } }}执行如下:var mygenerator = generator(“boomerang”);mygenerator.next(); // returns “b"mygenerator.next() // returns “o"mygenerator = generator(“toon”);mygenerator.next(); // returns “t"在这里,作用域扮演着重要的角色。闭包是返回另一个函数并携带数据的函数。上面的字符串生成器适用于闭包。index 在多个函数调用之间保留,定义的内部函数可以访问在父函数中定义的变量。这是一个不同的作用域。如果在第二级函数中再定义一个函数,它可以访问所有父级变量。4) this (全局域、函数域、对象域)在 JavaScript 中,我们总是用函数和对象编写代码, 如果使用浏览器,则在全局上下文中它引用 window 对象。 我的意思是,如果你现在打开浏览器控制台并输入以下代码,输出结果为 true。this === window;当程序的上下文和作用域发生变化时,this 也会发生相应的变化。现在观察 this 在一个局部上下文中:function Foo(){ console.log(this.a);}var food = {a: “Magical this”};Foo.call(food); // food is this思考一下,以下输出的是什么:function Foo(){ console.log(this); // 打印 {}?}因为这是一个全局对象,记住,无论父作用域是什么,它都将由子作用域继承。打印出来是 window 对象。上面讨论的三个方法实际上用于设置这个对象。现在,this 的最后一个类型,在对象中的 this, 如下:var person = { name: “Stranger”, age: 24, get identity() { return {who: this.name, howOld: this.age}; }}上述使用了 getter 语法,这是一个可以作为变量调用的函数。person.identity; // returns {who: “Stranger”, howOld: 24}此时,this 实际上是指对象本身。正如我们前面提到的,它在不同的地方有不同的表现。5) 理解对象 (Object.freeze, Object.seal)通常对象的格式如下:var marks = {physics: 98, maths:95, chemistry: 91};它是一个存储键、值对的映射。 javascript 对象有一个特殊的属性,可以将任何东西存储为一个值。这意味着我们可以将一个列表、另一个对象、一个函数等存储为一个值。可以用如下方式来创建对象:var marks = {};var marks = new Object();可以使用 JSON.stringify() 将一个对象转制成字符串,也可以用 JSON.parse 在将其转成对象。// returns “{“physics”:98,“maths”:95,“chemistry”:91}“JSON.stringify(marks);// Get object from stringJSON.parse(’{“physics”:98,“maths”:95,“chemistry”:91}’);使用 Object.keys 迭代对象:var highScere = 0;for (i of Object.keys(marks)) { if (marks[i] > highScore) highScore = marks[i];}Object.values 以数组的方式返回对象的值。对象上的其他重要函数有:Object.prototype(object)Object.freeze(function)Object.seal(function)Object.prototype 上提供了许多应用上相关的函数,如下:Object.prototype.hasOwnProperty 用于检查给定的属性/键是否存在于对象中。marks.hasOwnProperty(“physics”); // returns truemarks.hasOwnProperty(“greek”); // returns falseObject.prototype.instanceof 判断给定对象是否是特定原型的类型。function Car(make, model, year) { this.make = make; this.model = model; this.year = year;}var newCar = new Car(‘Honda’, ‘City’, 2007);console.log(newCar instanceof Car); // returns true使用 Object.freeze 可以冻结对象,以便不能修改对象现有属性。var marks = {physics: 98, maths:95, chemistry: 91};finalizedMarks = Object.freeze(marks);finalizedMarks[“physics”] = 86; // throws error in strict modeconsole.log(marks); // {physics: 98, maths: 95, chemistry: 91}在这里,试图修改冻结后的 physics 的值,但 JavaScript不允许这样做。我们可以使用 Object.isFrozen 来判断,给定对象是否被冻结:Object.isFrozen(finalizedMarks); // returns trueObject.seal 与 Object.freeze 略有不同。 Object.seal() 方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。var marks = {physics: 98, maths:95, chemistry: 91};Object.seal(marks);delete marks.chemistry; // returns false as operation failedmarks.physics = 95; // Works!marks.greek = 86; // Will not add a new property同样, 可以使用 Object.isSealed 判断对象是否被密封。Object.isSealed(marks); // returns true在全局对象函数上还有许多其他重要的函数/方法,在这里找到他们。6) 理解原型继承在传统 JavaScript 中,有一种伪装的继承概念,它是通过使用原型技术来实现的。在ES5、ES6中看到使用 new 的语法只是底层原型OOP的语法糖。创建类是使用 JavaScript 中的函数完成的。var animalGroups = { MAMMAL: 1, REPTILE: 2, AMPHIBIAN: 3, INVERTEBRATE: 4};function Animal(name, type) { this.name = name; this.type = type;}var dog = new Animal(“dog”, animalGroups.MAMMAL);var crocodile = new Animal(“crocodile”, animalGroups.REPTILE);这里我们为类创建对象(使用 new 关键字),可以使用如下方式对类追加方法:Animal.prototype.shout = function() { console.log(this.name+‘is’+this.sound+‘ing…’);}这里你可能会有疑问。类中并没 sound 属性。是的,它打算由继承了上述类的子类传递。JavaScript中, 如下实现继承:function Dog(name, type) { Animal.call(this, name, type); this.sound = ‘bow’;}我定义了一个更具体的函数,叫做 Dog。在这里,为了继承 Animal 类,我需要call传递this和其他参数。使用如下方式来实例化一只德国牧羊犬。var pet = Dog(“德国牧羊犬”, animalGroups.MAMMAL);console.log(pet); // returns Dog {name: “德国牧羊犬”, type: 1, sound: “bow”}我们没有在子函数中分配 name 和 type 属性,我们调用的是超级函数 Animal 并设置相应的属性。pet 具有父类的属性(name、type)。但是方法呢。他们也继承的吗? 来看看:pet.shout(); // Throws error为什么会这样? 之所以发生这种情况,是因为没有指定让 JavaScript来继承父类方法。 如何解决?// Link prototype chainsDog.prototype = Object.create(Animal.prototype);var pet = new Dog(“germanShepard”, animalGroups.MAMMAL);// Now shout method is availablepet.shout(); // 德国牧羊犬 bowing…现在可以使用 shout 方法。 我们可以使用 object.constructor 函数检查 JavaScript 中给定对象的类 来看看 pet 是什么类:pet.constructor; // returns Animal这是模糊的,Animal 是一个父类。但是 pet 到底是什么类型的呢? pet 应该是 Dog 的类型。之所以是 Animal 类型,是因为 Dog 类的构造函数:Dog.prototype.constructor; // returns Animal它是 Animal 类型的。我们应该将它设置为 Dog 本身,这样类的所有实例(对象)才能给出正确的类名。Dog.prototype.constructor = Dog;关于原型继承, 我们应该记住以下几条:类属性使用 this 绑定类方法使用 prototype 对象来绑定为了继承属性, 使用 call 函数来传递 this为了继承方法, 使用 Object.create 连接父和子的原型始终将子类构造函数设置为自身,以获得其对象的正确类型7)理解 callback 和 promise回调是在 I/O 操作完成后执行的函数。一个耗时的I/O操作会阻塞代码, 因此在Python/Ruby不被允许。但是在 JavaScript中,由于允许异步执行,我们可以提供对异步函数的回调。这个例子是由浏览器到服务器的AJAX(XMLHettpRequest)调用,由鼠标、键盘事件生成。如下:function reqListener () { console.log(this.responseText);}var req = new XMLHttpRequest();req.addEventListener(“load”, reqListener);req.open(“GET”, “http://www.example.org/example.txt");req.send();这里的 reqListener 是一个回调函数,当成功响应 GET 请求时将执行该回调函数。Promise 是回调函数的优雅的封装, 使得我们优雅的实现异步代码。在以下给出的这篇文章中讨论了很多 promise,这也是在 JS 中应该知道的重要部分。Writing neat asynchronous Node JS code with Promises8)理解正则表达正则表达式有许多应用地方,处理文本、对用户输入执行规则等。JavaScript 开发人员应该知道如何执行基本正则表达式并解决问题。Regex 是一个通用概念,来看看如何从 JS 中做到这一点。创建正则表达式,有如下两种方式:var re = /ar/;var re = new RegExp(‘ar’); 上面的正则表达式是与给定字符串集匹配的表达式。定义正则表达式之后,我们可以尝试匹配并查看匹配的字符串。可以使用 exec 函数匹配字符串:re.exec(“car”); // returns [“ar”, index: 1, input: “car”]re.exec(“cab”); // returns null有一些特殊的字符类允许我们编写复杂的正则表达式。RegEx 中有许多类型的元素,其中一些如下:字符正则:\w-字母数字, \d- 数字, \D- 没有数字字符类正则:[x-y] x-y区间, [^x] 没有x数量正则:+ 至少一个、? 没或多个、 多个边界正则,^ 开始、$ 结尾例子如下:/* Character class /var re1 = /[AEIOU]/;re1.exec(“Oval”); // returns [“O”, index: 0, input: “Oval”]re1.exec(“2456”); // nullvar re2 = /[1-9]/;re2.exec(‘mp4’); // returns [“4”, index: 2, input: “mp4”]/ Characters /var re4 = /\d\D\w/;re4.exec(‘1232W2sdf’); // returns [“2W2”, index: 3, input: “1232W2sdf”]re4.exec(‘W3q’); // returns null/ Boundaries /var re5 = /^\d\D\w/;re5.exec(‘2W34’); // returns [“2W3”, index: 0, input: “2W34”]re5.exec(‘W34567’); // returns nullvar re6 = /^[0-9]{5}-[0-9]{5}-[0-9]{5}$/;re6.exec(‘23451-45242-99078’); // returns [“23451-45242-99078”, index: 0, input: “23451-45242-99078”]re6.exec(‘23451-abcd-efgh-ijkl’); // returns null/ Quantifiers /var re7 = /\d+\D+$/;re7.exec(‘2abcd’); // returns [“2abcd”, index: 0, input: “2abcd”]re7.exec(‘23’); // returns nullre7.exec(‘2abcd3’); // returns nullvar re8 = /<([\w]+).>(.*?)</\1>/;re8.exec(’<p>Hello JS developer</p>’); //returns ["<p>Hello JS developer</p>”, “p”, “Hello JS developer”, index: 0, input: “<p>Hello JS developer</p>"]有关 regex 的详细信息,可以看 这里。除了 exec 之外,还有其他函数,即 match、search 和 replace,可以使用正则表达式在另一个字符串中查找字符串,但是这些函数在字符串本身上使用。“2345-678r9”.match(/[a-z A-Z]/); // returns [“r”, index: 8, input: “2345-678r9”]“2345-678r9”.replace(/[a-z A-Z]/, “”); // returns 2345-6789Regex 是一个重要的主题,开发人员应该理解它,以便轻松解决复杂的问题。9)理解 map、reduce 和 filter函数式编程是当今的一个热门讨论话题。许多编程语言都在新版本中包含了函数概念,比如 lambdas(例如:Java >7)。在 JavaScrip t中,函数式编程结构的支持已经存在很长时间了。我们需要深入学习三个主要函数。数学函数接受一些输入和返回输出。纯函数都是给定的输入返回相同的输出。我们现在讨论的函数也满足纯度。mapmap 函数在 JavaScript 数组中可用,使用这个函数,我们可以通过对数组中的每个元素应用一个转换函数来获得一个新的数组。map 一般语法是:arr.map((elem){ process(elem) return processedValue}) // returns new array with each element processed假设,在我们最近使用的串行密钥中输入了一些不需要的字符,需要移除它们。此时可以使用 map 来执行相同的操作并获取结果数组,而不是通过迭代和查找来删除字符。var data = [“2345-34r”, “2e345-211”, “543-67i4”, “346-598”];var re = /[a-z A-Z]/;var cleanedData = data.map((elem) => {return elem.replace(re, “”)});console.log(cleanedData); // [“2345-34”, “2345-211”, “543-674”, “346-598”]map 接受一个作为参数的函数, 此函数接受一个来自数组的参数。我们需要返回一个处理过的元素, 并应用于数组中的所有元素。reducereduce 函数将一个给定的列表整理成一个最终的结果。通过迭代数组执行相同的操作, 并保存中间结果到一个变量中。这里是一个更简洁的方式进行处理。js 的 reduce 一般使用语法如下:arr.reduce((accumulator, currentValue, currentIndex) => { process(accumulator, currentValue) return intermediateValue/finalValue}, initialAccumulatorValue) // returns reduced valueaccumulator 存储中间值和最终值。currentIndex、currentValue分别是数组中元素的 index 和 value。initialAccumulatorValue 是 accumulator 初始值。reduce 的一个实际应用是将一个数组扁平化, 将内部数组转化为单个数组, 如下:var arr = [[1, 2], [3, 4], [5, 6]];var flattenedArray = [1, 2, 3, 4, 5, 6];我们可以通过正常的迭代来实现这一点,但是使用 reduce,代码会更加简洁。var flattenedArray = arr.reduce((accumulator, currentValue) => { return accumulator.concat(currentValue);}, []); // returns [1, 2, 3, 4, 5, 6]filterfilter 与 map 更为接近, 对数组的每个元素进行操作并返回另外一个数组(不同于 reduce 返回的值)。过滤后的数组可能比原数组长度更短,因为通过过滤条件,排除了一些我们不需要的。filter 语法如下:arr.filter((elem) => { return true/false})elem 是数组中的元素, 通过 true/false 表示过滤元素保存/排除。假设, 我们过滤出以 t 开始以 r 结束的元素:var words = [“tiger”, “toast”, “boat”, “tumor”, “track”, “bridge”]var newData = words.filter((str) => { return str.startsWith(’t’) && str.endsWith(‘r’);})newData // (2) [“tiger”, “tumor”]当有人问起JavaScript的函数编程方面时,这三个函数应该信手拈来。 如你所见,原始数组在所有三种情况下都没有改变,这证明了这些函数的纯度。10) 理解错误处理模式这是许多开发人员最不关心的 JavaScript。 我看到很少有开发人员谈论错误处理, 一个好的开发方法总是谨慎地将 JS 代码封装装在 try/catch 块周围。在 JavaScript中,只要我们随意编写代码,就可能会失败,如果所示:$(“button”).click(function(){ $.ajax({url: “user.json”, success: function(result){ updateUI(result[“posts”]); }});});这里,我们陷入了一个陷阱,我们说 result 总是 JSON 对象。但有时服务器会崩溃,返回的是 null 而不是 result。在这种情况下,null[“posts”] 将抛出一个错误。正确的处理方式可能是这样的:$(“button”).click(function(){ $.ajax({url: “user.json”, success: function(result){ try { updateUI(result[“posts”]); } catch(e) { // Custom functions logError(); flashInfoMessage(); } }});});logError 函数用于向服务器报告错误。flashInfoMessage 是显示用户友好的消息,如“当前不可用的服务”等。Nicholas 说,当你觉得有什么意想不到的事情将要发生时,手动抛出错误。区分致命错误和非致命错误。以上错误与后端服务器宕机有关,这是致命的。在那里,应该通知客户由于某种原因服务中断了。在某些情况下,这可能不是致命的,但最好通知服务器。为了创建这样的代码,首先抛出一个错误,, 从 window 层级捕捉错误事件,然后调用API将该消息记录到服务器。reportErrorToServer = function (error) { $.ajax({type: “POST”, url: “http://api.xyz.com/report", data: error, success: function (result) {} });}// Window error eventwindow.addEventListener(’error’, function (e) { reportErrorToServer({message: e.message})})}function mainLogic() { // Somewhere you feel like fishy throw new Error(“user feeds are having fewer fields than expected…”);}这段代码主要做三件事:监听window层级错误无论何时发生错误,都要调用 API在服务器中记录你也可以使用新的 Boolean 函数(es5,es6)在程序之前监测变量的有效性并且不为null、undefinedif (Boolean(someVariable)) {// use variable now} else { throw new Error(“Custom message”)}始终考虑错误处理是你自己, 而不是浏览器。其他(提升机制和事件冒泡)以上所有概念都是 JavaScript 开发人员的需要知道基本概念。有一些内部细节需要知道,这些对你会有很在帮助。 这些是JavaScript引擎在浏览器中的工作方式,什么是提升机制和事件冒泡?提升机制变量提升是 在代码执行过程中将声明的变量的作用域提升到全局作用哉中的一个过程,如:doSomething(foo); // used beforevar foo; // declared later当在 Python 这样的脚本语言中执行上述操作时,它会抛出一个错误,因为需要先定义然后才能使用它。尽管 JS 是一种脚本语言,但它有一种提升机制,在这种机制中,JavaScript VM 在运行程序时做两件事:首先扫描程序,收集所有的变量和函数声明,并为其分配内存空间通过填充分配的变量来执行程序, 没有分配则填充 undefined在上面的代码片段中,console.log 打印 “undefined”。 这是因为在第一次传递变量 foo 被收集。 JS 虚拟机 查找为变量 foo 定义的任何值。 这种提升可能导致许多JavaScript 在某些地方抛出错误,和另外地方使用 undefined 。 学习一些 例子 来搞清楚提升。事件冒泡现在事件开始冒泡了! 根据高级软件工程师 Arun P的说法:“当事件发生在另一个元素内的元素中时,事件冒泡和捕获是 HTML DOM API 中事件传播的两种方式,并且这两个元素都已为该事件注册了处理程序,事件传播模式确定元素接收事件的顺序。“通过冒泡,事件首先由最内部的元素捕获和处理,然后传播到外部元素。对于捕获,过程是相反的。我们通常使用addEventListener 函数将事件附加到处理程序。addEventListener(“click”, handler, useCapture=false)useCapture 是第三个参数的关键词, 默认为 false。因此, 冒泡模式是事件由底部向上传递。 反之, 这是捕获模式。冒泡模式:<div onClick=“divHandler()"> <ul onClick=“ulHandler”> <li id=“foo”></li> </ul></div><script>function handler() { // do something here}function divHandler(){}function ulHandler(){}document.getElementById(“foo”).addEventListener(“click”, handler)</script>点击li元素, 事件顺序:handler() => ulHandler() => divHandler()在图中,处理程序按顺序向外触发。类似地,捕获模型试图将事件从父元素向内触发到单击的元素。现在更改上面代码中的这一行。document.getElementById(“foo”).addEventListener(“click”, handler, true)事件顺序:divHandler => ulHandler() => handler()你应该正确地理解事件冒泡(无论方向是指向父节点还是子节点),以实现用户界面(UI),以避免任何不需要的行为。这些是 JavaScrip t中的基本概念。正如我最初提到的,除了工作经验和知识之外,准备有助理于你通过 JavaScript 面试。始终保持学习。留意最新的发展(第六章)。深入了解JavaScript的各个方面,如 V6 引擎、测试等。最后,没有掌握数据结构和算法的面试是不成功的。Oleksii Trekhleb 策划了一个很棒的 git repo,它包含了所有使用 JS 代码的面试准备算法。代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。原文:https://medium.com/dev-bits/a…你的点赞是我持续分享好东西的动力,欢迎点赞!一个笨笨的码农,我的世界只能终身学习!更多内容请关注公众号《大迁世界》! ...

February 21, 2019 · 6 min · jiezi

webpack打包原理(待续)

打包工具要解决的问题:文件依赖管理 梳理文件之间的依赖关系资源加载管理 处理文件的加载顺序(先后时机)和文件的加载数量(合并、嵌入、拆分)效率与优化管理 提高开发效率,完成页面优化

February 21, 2019 · 1 min · jiezi

这一次,彻底弄懂 JavaScript 执行机制

本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我。不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的几行代码,我们需要知道其输出内容和顺序。因为javascript是一门单线程语言,所以我们可以得出结论:javascript是按照语句出现的顺序执行的看到这里读者要打人了:我难道不知道js是一行一行执行的?还用你说?稍安勿躁,正因为js是一行一行执行的,所以我们以为js都是这样的: let a = ‘1’; console.log(a); let b = ‘2’; console.log(b);然而实际上js是这样的:setTimeout(function(){ console.log(‘定时器开始啦’)});new Promise(function(resolve){ console.log(‘马上执行for循环啦’); for(var i = 0; i < 10000; i++){ i == 99 && resolve(); }}).then(function(){ console.log(‘执行then函数啦’)});console.log(‘代码执行结束’);依照js是按照语句出现的顺序执行这个理念,我自信的写下输出结果://“定时器开始啦”//“马上执行for循环啦”//“执行then函数啦”//“代码执行结束"去chrome上验证下,结果完全不对,瞬间懵了,说好的一行一行执行的呢?我们真的要彻底弄明白javascript的执行机制了。1.关于javascriptjavascript是一门 单线程 语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变。所以一切javascript版的"多线程"都是用单线程模拟出来的,一切javascript多线程都是纸老虎!2.javascript事件循环既然js是单线程,那就像只有一个窗口的银行,客户需要排队一个一个办理业务,同理js任务也要一个一个顺序执行。如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?因此聪明的程序员将任务分为两类:同步任务异步任务当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。关于这部分有严格的文字定义,但本文的目的是用最小的学习成本彻底弄懂执行机制,所以我们用导图来说明:导图要表达的内容用文字来表述的话:同步和异步任务分别进入不同的执行"场所”,同步的进入主线程,异步的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。上述过程会不断重复,也就是常说的Event Loop(事件循环)。我们不禁要问了,那怎么知道主线程执行栈为空啊?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。说了这么多文字,不如直接一段代码更直白:let data = [];$.ajax({ url:www.javascript.com, data:data, success:() => { console.log(‘发送成功!’); }})console.log(‘代码执行结束’);上面是一段简易的ajax请求代码:ajax进入Event Table,注册回调函数success。执行console.log(‘代码执行结束’)。ajax事件完成,回调函数success进入Event Queue。主线程从Event Queue读取回调函数success并执行。相信通过上面的文字和代码,你已经对js的执行顺序有了初步了解。接下来我们来研究进阶话题:setTimeout。3.又爱又恨的setTimeout大名鼎鼎的setTimeout无需再多言,大家对他的第一印象就是异步可以延时执行,我们经常这么实现延时3秒执行:setTimeout(() => { console.log(‘延时3秒’);},3000)渐渐的setTimeout用的地方多了,问题也出现了,有时候明明写的延时3秒,实际却5,6秒才执行函数,这又咋回事啊?先看一个例子:setTimeout(() => { task();},3000)console.log(‘执行console’);根据前面我们的结论,setTimeout是异步的,应该先执行console.log这个同步任务,所以我们的结论是://执行console//task()去验证一下,结果正确!然后我们修改一下前面的代码:setTimeout(() => { task()},3000)sleep(10000000)乍一看其实差不多嘛,但我们把这段代码在chrome执行一下,却发现控制台执行task()需要的时间远远超过3秒,说好的延时三秒,为啥现在需要这么长时间啊?这时候我们需要重新理解setTimeout的定义。我们先说上述代码是怎么执行的:task()进入Event Table并注册,计时开始。执行sleep函数,很慢,非常慢,计时仍在继续。3秒到了,计时事件timeout完成,task()进入Event Queue,但是sleep也太慢了吧,还没执行完,只好等着。sleep终于执行完了,task()终于从Event Queue进入了主线程执行。上述的流程走完,我们知道setTimeout这个函数,是经过指定时间后,把要执行的任务(本例中为task())加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。我们还经常遇到setTimeout(fn,0)这样的代码,0秒后执行又是什么意思呢?是不是可以立即执行呢?答案是不会的,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。举例说明://代码1console.log(‘先执行这里’);setTimeout(() => { console.log(‘执行啦’)},0);//代码2console.log(‘先执行这里’);setTimeout(() => { console.log(‘执行啦’)},3000);代码1的输出结果是://先执行这里//执行啦代码2的输出结果是://先执行这里// … 3s later// 执行啦关于setTimeout要补充的是,即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒。有兴趣的同学可以自行了解。4.又恨又爱的setInterval上面说完了setTimeout,当然不能错过它的孪生兄弟setInterval。他俩差不多,只不过后者是循环的执行。对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。唯一需要注意的一点是,对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。这句话请读者仔细品味。5.Promise与process.nextTick(callback)传统的定时器我们已经研究过了,接着我们探究Promise与process.nextTick(callback)的表现。Promise的定义和功能本文不再赘述,不了解的读者可以学习一下阮一峰老师的Promise。而process.nextTick(callback)类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。我们进入正题,除了广义的同步任务和异步任务,我们对任务有更精细的定义:macro-task(宏任务):包括整体代码script,setTimeout,setIntervalmicro-task(微任务):Promise,process.nextTick不同类型的任务会进入对应的Event Queue,比如setTimeout和setInterval会进入相同的Event Queue。事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。听起来有点绕,我们用文章最开始的一段代码说明:setTimeout(function() { console.log(‘setTimeout’);})new Promise(function(resolve) { console.log(‘promise’);}).then(function() { console.log(’then’);})console.log(‘console’);这段代码作为宏任务,进入主线程。先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue。(注册过程与上同,下文不再描述)接下来遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue。遇到console.log(),立即执行。好啦,整体代码script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行。ok,第一轮事件循环结束了,我们开始第二轮循环,当然要从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行。 结束。事件循环,宏任务,微任务的关系如图所示:我们来分析一段较复杂的代码,看看你是否真的掌握了js的执行机制:console.log(‘1’);setTimeout(function() { console.log(‘2’); process.nextTick(function() { console.log(‘3’); }) new Promise(function(resolve) { console.log(‘4’); resolve(); }).then(function() { console.log(‘5’) })})process.nextTick(function() { console.log(‘6’);})new Promise(function(resolve) { console.log(‘7’); resolve();}).then(function() { console.log(‘8’)})setTimeout(function() { console.log(‘9’); process.nextTick(function() { console.log(‘10’); }) new Promise(function(resolve) { console.log(‘11’); resolve(); }).then(function() { console.log(‘12’) })})第一轮事件循环流程分析如下:整体script作为第一个宏任务进入主线程,遇到console.log,输出1。遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1。遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1。遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1。又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2。宏任务Event Queue | 微任务Event QueuesetTimeout1 | process1setTimeout2 | then1上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。我们发现了process1和then1两个微任务。执行process1,输出6。执行then1,输出8。好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮时间循环从setTimeout1宏任务开始:首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2。new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2。宏任务Event Queue | 微任务Event QueuesetTimeout2 | process2null | then2第二轮事件循环宏任务结束,我们发现有process2和then2两个微任务可以执行。输出3。输出5。第二轮事件循环结束,第二轮输出2,4,3,5。第三轮事件循环开始,此时只剩setTimeout2了,执行。直接输出9。将process.nextTick()分发到微任务Event Queue中。记为process3。直接执行new Promise,输出11。将then分发到微任务Event Queue中,记为then3。宏任务Event Queue | 微任务Event Queuenull | process3 null | then3第三轮事件循环宏任务执行结束,执行两个微任务process3和then3。输出10。输出12。第三轮事件循环结束,第三轮输出9,11,10,12。整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)6.写在最后(1)js的异步我们从最开头就说javascript是一门单线程语言,不管是什么新框架新语法糖实现的所谓异步,其实都是用同步的方法去模拟的,牢牢把握住单线程这点非常重要。(2)事件循环Event Loop事件循环是js实现异步的一种方法,也是js的执行机制。(3)javascript的执行和运行执行和运行有很大的区别,javascript在不同的环境下,比如node,浏览器,Ringo等等,执行方式是不同的。而运行大多指javascript解析引擎,是统一的。(4)setImmediate微任务和宏任务还有很多种类,比如setImmediate等等,执行都是有共同点的,有兴趣的同学可以自行了解。(5)最后的最后javascript是一门单线程语言Event Loop是javascript的执行机制牢牢把握两个基本点,以认真学习javascript为中心,早日实现成为前端高手的伟大梦想! ...

February 21, 2019 · 1 min · jiezi

2019前端面试题汇总(主要为Vue)

毕业之后就在一直合肥小公司工作,没有老司机、没有技术氛围,在技术的道路上我只能独自摸索。老板也只会画饼充饥,前途一片迷茫看不到任何希望。于是乎,我果断辞职,在新年开工之际来到杭州,这里的互联网公司应该是合肥的几十倍吧。。。。刚来3天,面试了几家公司,有些规模比较小,有些是创业公司,也有些已经发展的不错了;今天把最近的面试题目做个汇总,也给自己复个盘,由于我的技术栈主要为Vue,所以大部分题目都是Vue开发相关的。1. 谈谈你对MVVM开发模式的理解MVVM分为Model、View、ViewModel三者。Model 代表数据模型,数据和业务逻辑都在Model层中定义;View 代表UI视图,负责数据的展示;ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom。2. Vue 有哪些指令?v-html、v-show、v-if、v-for等等3. v-if 和 v-show 有什么区别?v-show 仅仅控制元素的显示方式,将 display 属性在 block 和 none 来回切换;而v-if会控制这个 DOM 节点的存在与否。当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。4. 简述Vue的响应式原理当一个Vue实例创建时,vue会遍历data选项的属性,用 Object.defineProperty 将它们转为 getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。5. Vue中如何在组件内部实现一个双向数据绑定?假设有一个输入框组件,用户输入时,同步父组件页面中的数据具体思路:父组件通过 props 传值给子组件,子组件通过 $emit 来通知父组件修改相应的props值,具体实现如下:import Vue from ‘vue’const component = { props: [‘value’], template: &lt;div&gt; &lt;input type="text" @input="handleInput" :value="value"&gt; &lt;/div&gt; , data () { return { } }, methods: { handleInput (e) { this.$emit(‘input’, e.target.value) } }}new Vue({ components: { CompOne: component }, el: ‘#root’, template: &lt;div&gt; &lt;comp-one :value1="value" @input="value = arguments[0]"&gt;&lt;/comp-one&gt; &lt;/div&gt; , data () { return { value: ‘123’ } }})可以看到,当输入数据时,父子组件中的数据是同步改变的:我们在父组件中做了两件事,一是给子组件传入props,二是监听input事件并同步自己的value属性。那么这两步操作能否再精简一下呢?答案是可以的,你只需要修改父组件:template: &lt;div&gt; &lt;!--&lt;comp-one :value1="value" @input="value = arguments[0]"&gt;&lt;/comp-one&gt;--&gt; &lt;comp-one v-model="value"&gt;&lt;/comp-one&gt; &lt;/div&gt; v-model 实际上会帮我们完成上面的两步操作。6. Vue中如何监控某个属性值的变化?比如现在需要监控data中,obj.a 的变化。Vue中监控对象属性的变化你可以这样:watch: { obj: { handler (newValue, oldValue) { console.log(‘obj changed’) }, deep: true } }deep属性表示深层遍历,但是这么写会监控obj的所有属性变化,并不是我们想要的效果,所以做点修改:watch: { ‘obj.a’: { handler (newName, oldName) { console.log(‘obj.a changed’) } } }还有一种方法,可以通过computed 来实现,只需要:computed: { a1 () { return this.obj.a }}利用计算属性的特性来实现,当依赖改变时,便会重新计算一个新值。7. Vue中给data中的对象属性添加一个新的属性时会发生什么,如何解决?示例:<template> <div> <ul> <li v-for=“value in obj” :key=“value”> {{value}} </li> </ul> <button @click=“addObjB”>添加obj.b</button> </div></template><script>export default { data () { return { obj: { a: ‘obj.a’ } } }, methods: { addObjB () { this.obj.b = ‘obj.b’ console.log(this.obj) } }}</script><style></style>点击button会发现,obj.b 已经成功添加,但是视图并未刷新:原因在于在Vue实例创建时,obj.b并未声明,因此就没有被Vue转换为响应式的属性,自然就不会触发视图的更新,这时就需要使用Vue的全局api $set():addObjB () { // this.obj.b = ‘obj.b’ this.$set(this.obj, ‘b’, ‘obj.b’) console.log(this.obj) }$set()方法相当于手动的去把obj.b处理成一个响应式的属性,此时视图也会跟着改变了:8. delete和Vue.delete删除数组的区别delete只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。Vue.delete直接删除了数组 改变了数组的键值。 var a=[1,2,3,4] var b=[1,2,3,4] delete a[1] console.log(a) this.$delete(b,1) console.log(b)9.如何优化SPA应用的首屏加载速度慢的问题?将公用的JS库通过script标签外部引入,减小app.bundel的大小,让浏览器并行下载资源文件,提高下载速度;在配置 路由时,页面和组件使用懒加载的方式引入,进一步缩小 app.bundel 的体积,在调用某个组件时再加载对应的js文件;加一个首屏 loading 图,提升用户体验;10. 前端如何优化网站性能?减少 HTTP 请求数量在浏览器与服务器进行通信时,主要是通过 HTTP 进行通信。浏览器与服务器需要经过三次握手,每次握手需要花费大量时间。而且不同浏览器对资源文件并发请求数量有限(不同浏览器允许并发数),一旦 HTTP 请求数量达到一定数量,资源请求就存在等待状态,这是很致命的,因此减少 HTTP 的请求数量可以很大程度上对网站性能进行优化。CSS Sprites:国内俗称 CSS 精灵,这是将多张图片合并成一张图片达到减少 HTTP 请求的一种解决方案,可以通过 CSS background 属性来访问图片内容。这种方案同时还可以减少图片总字节数。合并 CSS 和 JS 文件:现在前端有很多工程化打包工具,如:grunt、gulp、webpack等。为了减少 HTTP 请求数量,可以通过这些工具再发布前将多个 CSS 或者 多个 JS 合并成一个文件。采用 lazyLoad:俗称懒加载,可以控制网页上的内容在一开始无需加载,不需要发请求,等到用户操作真正需要的时候立即加载出内容。这样就控制了网页资源一次性请求数量。控制资源文件加载优先级浏览器在加载 HTML 内容时,是将 HTML 内容从上至下依次解析,解析到 link 或者 script 标签就会加载 href 或者 src 对应链接内容,为了第一时间展示页面给用户,就需要将 CSS 提前加载,不要受 JS 加载影响。一般情况下都是 CSS 在头部,JS 在底部。利用浏览器缓存 浏览器缓存是将网络资源存储在本地,等待下次请求该资源时,如果资源已经存在就不需要到服务器重新请求该资源,直接在本地读取该资源。减少重排(Reflow)基本原理:重排是 DOM 的变化影响到了元素的几何属性(宽和高),浏览器会重新计算元素的几何属性,会使渲染树中受到影响的部分失效,浏览器会验证 DOM 树上的所有其它结点的 visibility 属性,这也是 Reflow 低效的原因。如果 Reflow 的过于频繁,CPU 使用率就会急剧上升。减少 Reflow,如果需要在 DOM 操作时添加样式,尽量使用 增加 class 属性,而不是通过 style 操作样式。减少 DOM 操作图标使用 IconFont 替换11. 网页从输入网址到渲染完成经历了哪些过程?大致可以分为如下7步:输入网址;发送到DNS服务器,并获取域名对应的web服务器对应的ip地址;与web服务器建立TCP连接;浏览器向web服务器发送http请求;web服务器响应请求,并返回指定url的数据(或错误信息,或重定向的新的url地址);浏览器下载web服务器返回的数据及解析html源文件;生成DOM树,解析css和js,渲染页面,直至显示完成;12. jQuery获取的dom对象和原生的dom对象有何区别?js原生获取的dom是一个对象,jQuery对象就是一个数组对象,其实就是选择出来的元素的数组集合,所以说他们两者是不同的对象类型不等价。原生DOM对象转jQuery对象:var box = document.getElementById(‘box’);var $box = $(box);jQuery对象转原生DOM对象:var $box = $(’#box’);var box = $box[0];13. jQuery如何扩展自定义方法(jQuery.fn.myMethod=function () { alert(‘myMethod’);})// 或者:(function ($) { $.fn.extend({ myMethod : function () { alert(‘myMethod’); } })})(jQuery)使用:$("#div").myMethod();目前来看公司面试的问题还是比较基础的,但是对于某些只追求会用并不研究其原理的同学来说可能就没那么容易了。所以大家不仅要追求学习的广度,更要追求深度。OK,希望自己能早日拿到心仪的offer.参考:浅谈网站性能之前端性能优化 ...

February 20, 2019 · 2 min · jiezi

JS中定时器线程理解

最近在准备面试,对于JS原理性的文章,感觉很有必要系统整理下,不必每一次都要查询资料,节约时间。问题setTimeout(function(){ console.log(“开始执行定时器回调: “+ new Date()) console.log(“我是定时器”)},0)大家觉得这个定时器定时时间设为0,有意义吗?是否觉得上述代码效果等同于console.log(“开始执行定时器回调: “+ new Date())console.log(“我是定时器”)实践是检验真理的最好途径。我们不排斥拿来主义,但是如果能自己实践验证,对于提升自身格物致知的精神很有裨益。针对上述问题,我们用两个实验来解开答案:实验一:console.log(“1”)console.log(“我是定时器”)console.log(“2”)打印结果实验二:console.log(“1”)setTimeout(function(){ console.log(“我是定时器”)},0)console.log(“2”)打印结果通过上述两个实验结果,我们可以得知 定时器定时为0时,JS执行到定时器这一步,并不是直接开始执行定时回调,而是执行了后续代码之后,才执行。那为什么会这样呢?我们仍然拿两个例子来说明:实验三: console.log(“1”) console.log(“定时器线程开始计时: “+ new Date()) setTimeout(function(){ console.log(“开始执行定时器回调: “+ new Date()) },5000) for(var i=0;i<500;i++){ console.log(“我是循环”) } console.log(“事件队列最后一位: “+ new Date())打印结果:从结果中可以看出,从定时器线程开始定时,到定时5秒结束后,将定时回调事件放入事件队列中执行,用了5秒。实验四: console.log(“1”) console.log(“定时器线程开始计时: “+ new Date()) setTimeout(function(){ console.log(“开始执行定时器回调: “+ new Date()) },5000) for(var i=0;i<50000;i++){ console.log(“我是循环”) } console.log(“事件队列最后一位: “+ new Date())打印结果:从结果中可以看出,从定时器线程开始定时,到定时5秒结束后,将定时回调事件放入事件队列中执行,用了9秒。两次结果不一致,是因为JS代码执行到定时器时,此时定时器线程开始定时,定时时间到之后,将定时回调事件推入事件队列而最后,JS线程依据事件队列中顺序执行。而之所以有的延时5秒,有的延时9秒,是因为如果定时器开始计时时,JS事件队列中执行剩余的事件小于5秒,则定时结束后,将定时回调事件推入队列中,JS能够立即执行定时回调事件,所以是5秒;而如果JS事件队列中执行剩余的事件大于5秒,那么在定时结束后,将定时回调事件推入队列后,还需一些时间来执行定时回调事件之前的事件,所以为9秒。

February 20, 2019 · 1 min · jiezi

面试官问:JS的继承

用过React的读者知道,经常用extends继承React.Component。// 部分源码function Component(props, context, updater) { // …}Component.prototype.setState = function(partialState, callback){ // …}const React = { Component, // …}// 使用class index extends React.Component{ // …}点击这里查看 React github源码面试官可以顺着这个问JS继承的相关问题,比如:ES6的class继承用ES5如何实现。据说很多人答得不好。<br/>构造函数、原型对象和实例之间的关系要弄懂extends继承之前,先来复习一下构造函数、原型对象和实例之间的关系。代码表示:function F(){}var f = new F();// 构造器F.prototype.constructor === F; // trueF.proto === Function.prototype; // trueFunction.prototype.proto === Object.prototype; // trueObject.prototype.proto === null; // true// 实例f.proto === F.prototype; // trueF.prototype.proto === Object.prototype; // trueObject.prototype.proto === null; // true笔者画了一张图表示:ES6 extends 继承做了什么操作我们先看看这段包含静态方法的ES6继承代码:// ES6class Parent{ constructor(name){ this.name = name; } static sayHello(){ console.log(‘hello’); } sayName(){ console.log(‘my name is ’ + this.name); return this.name; }}class Child extends Parent{ constructor(name, age){ super(name); this.age = age; } sayAge(){ console.log(‘my age is ’ + this.age); return this.age; }}let parent = new Parent(‘Parent’);let child = new Child(‘Child’, 18);console.log(‘parent: ‘, parent); // parent: Parent {name: “Parent”}Parent.sayHello(); // helloparent.sayName(); // my name is Parentconsole.log(‘child: ‘, child); // child: Child {name: “Child”, age: 18}Child.sayHello(); // hellochild.sayName(); // my name is Childchild.sayAge(); // my age is 18其中这段代码里有两条原型链,不信看具体代码。// 1、构造器原型链Child.proto === Parent; // trueParent.proto === Function.prototype; // trueFunction.prototype.proto === Object.prototype; // trueObject.prototype.proto === null; // true// 2、实例原型链child.proto === Child.prototype; // trueChild.prototype.proto === Parent.prototype; // trueParent.prototype.proto === Object.prototype; // trueObject.prototype.proto === null; // true一图胜千言,笔者也画了一张图表示,如图所示:结合代码和图可以知道。ES6 extends 继承,主要就是:把子类构造函数(Child)的原型(proto)指向了父类构造函数(Parent),把子类实例child的原型对象(Child.prototype) 的原型(proto)指向了父类parent的原型对象(Parent.prototype)。这两点也就是图中用不同颜色标记的两条线。子类构造函数Child继承了父类构造函数Preant的里的属性。使用super调用的(ES5则用call或者apply调用传参)。也就是图中用不同颜色标记的两条线。看过《JavaScript高级程序设计-第3版》 章节6.3继承的读者应该知道,这2和3小点,正是寄生组合式继承,书中例子没有第1小点。1和2小点都是相对于设置了__proto__链接。那问题来了,什么可以设置了__proto__链接呢。new、Object.create和Object.setPrototypeOf可以设置__proto__说明一下,proto__这种写法是浏览器厂商自己的实现。再结合一下图和代码看一下的new,new出来的实例的__proto__指向构造函数的prototype,这就是new做的事情。摘抄一下之前写过文章的一段。面试官问:能否模拟实现JS的new操作符,有兴趣的读者可以点击查看。new做了什么:创建了一个全新的对象。这个对象会被执行[[Prototype]](也就是__proto)链接。生成的新对象会绑定到函数调用的this。通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。Object.create ES5提供的Object.create(proto, [propertiesObject])方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 它接收两个参数,不过第二个可选参数是属性描述符(不常用,默认是undefined)。对于不支持ES5的浏览器,MDN上提供了ployfill方案。MDN Object.create()// 简版:也正是应用了new会设置__proto__链接的原理。if(typeof Object.create !== ‘function’){ Object.create = function(proto){ function F() {} F.prototype = proto; return new F(); }}Object.setPrototypeOf ES6提供的Object.setPrototypeOf MDNObject.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null。Object.setPrototypeOf(obj, prototype)ployfill// 仅适用于Chrome和FireFox,在IE中不工作:Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) { obj.proto = proto; return obj; }nodejs源码就是利用这个实现继承的工具函数的。nodejs utils inheritsfunction inherits(ctor, superCtor) { if (ctor === undefined || ctor === null) throw new ERR_INVALID_ARG_TYPE(‘ctor’, ‘Function’, ctor); if (superCtor === undefined || superCtor === null) throw new ERR_INVALID_ARG_TYPE(‘superCtor’, ‘Function’, superCtor); if (superCtor.prototype === undefined) { throw new ERR_INVALID_ARG_TYPE(‘superCtor.prototype’, ‘Object’, superCtor.prototype); } Object.defineProperty(ctor, ‘super_’, { value: superCtor, writable: true, configurable: true }); Object.setPrototypeOf(ctor.prototype, superCtor.prototype);}ES6的extends的ES5版本实现知道了ES6 extends继承做了什么操作和设置__proto__的知识点后,把上面ES6例子的用ES5就比较容易实现了,也就是说实现寄生组合式继承,简版代码就是:// ES5 实现ES6 extends的例子function Parent(name){ this.name = name;}Parent.sayHello = function(){ console.log(‘hello’);}Parent.prototype.sayName = function(){ console.log(‘my name is ’ + this.name); return this.name;}function Child(name, age){ // 相当于super Parent.call(this, name); this.age = age;}// newfunction object(){ function F() {} F.prototype = proto; return new F();}function _inherits(Child, Parent){ // Object.create Child.prototype = Object.create(Parent.prototype); // proto // Child.prototype.proto = Parent.prototype; Child.prototype.constructor = Child; // ES6 // Object.setPrototypeOf(Child, Parent); // proto Child.proto = Parent;}_inherits(Child, Parent);Child.prototype.sayAge = function(){ console.log(‘my age is ’ + this.age); return this.age;}var parent = new Parent(‘Parent’);var child = new Child(‘Child’, 18);console.log(‘parent: ‘, parent); // parent: Parent {name: “Parent”}Parent.sayHello(); // helloparent.sayName(); // my name is Parentconsole.log(‘child: ‘, child); // child: Child {name: “Child”, age: 18}Child.sayHello(); // hellochild.sayName(); // my name is Childchild.sayAge(); // my age is 18我们完全可以把上述ES6的例子通过babeljs转码成ES5来查看,更严谨的实现。// 对转换后的代码进行了简要的注释"use strict";// 主要是对当前环境支持Symbol和不支持Symbol的typeof处理function _typeof(obj) { if (typeof Symbol === “function” && typeof Symbol.iterator === “symbol”) { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === “function” && obj.constructor === Symbol && obj !== Symbol.prototype ? “symbol” : typeof obj; }; } return _typeof(obj);}// _possibleConstructorReturn 判断Parent。call(this, name)函数返回值 是否为null或者函数或者对象。function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === “object” || typeof call === “function”)) { return call; } return _assertThisInitialized(self);}// 如何 self 是void 0 (undefined) 则报错function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(“this hasn’t been initialised - super() hasn’t been called”); } return self;}// 获取__proto__function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.proto || Object.getPrototypeOf(o); }; return getPrototypeOf(o);}// 寄生组合式继承的核心function inherits(subClass, superClass) { if (typeof superClass !== “function” && superClass !== null) { throw new TypeError(“Super expression must either be null or a function”); } // Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto。 // 也就是说执行后 subClass.prototype.proto === superClass.prototype; 这条语句为true subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass);}// 设置__proto__function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.proto = p; return o; }; return _setPrototypeOf(o, p);}// instanceof操作符包含对Symbol的处理function _instanceof(left, right) { if (right != null && typeof Symbol !== “undefined” && right[Symbol.hasInstance]) { return rightSymbol.hasInstance; } else { return left instanceof right; }}function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError(“Cannot call a class as a function”); }}// 按照它们的属性描述符 把方法和静态属性赋值到构造函数的prototype和构造器函数上function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (“value” in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); }}// 把方法和静态属性赋值到构造函数的prototype和构造器函数上function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor;}// ES6var Parent = function () { function Parent(name) { _classCallCheck(this, Parent); this.name = name; } _createClass(Parent, [{ key: “sayName”, value: function sayName() { console.log(‘my name is ’ + this.name); return this.name; } }], [{ key: “sayHello”, value: function sayHello() { console.log(‘hello’); } }]); return Parent;}();var Child = function (_Parent) { _inherits(Child, _Parent); function Child(name, age) { var _this; _classCallCheck(this, Child); // Child.proto => Parent // 所以也就是相当于Parent.call(this, name); 是super(name)的一种转换 // _possibleConstructorReturn 判断Parent.call(this, name)函数返回值 是否为null或者函数或者对象。 _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name)); _this.age = age; return _this; } _createClass(Child, [{ key: “sayAge”, value: function sayAge() { console.log(‘my age is ’ + this.age); return this.age; } }]); return Child;}(Parent);var parent = new Parent(‘Parent’);var child = new Child(‘Child’, 18);console.log(‘parent: ‘, parent); // parent: Parent {name: “Parent”}Parent.sayHello(); // helloparent.sayName(); // my name is Parentconsole.log(‘child: ‘, child); // child: Child {name: “Child”, age: 18}Child.sayHello(); // hellochild.sayName(); // my name is Childchild.sayAge(); // my age is 18如果对JS继承相关还是不太明白的读者,推荐阅读以下书籍的相关章节,可以自行找到相应的pdf版本。推荐阅读JS继承相关的书籍章节《JavaScript高级程序设计第3版》-第6章 面向对象的程序设计,6种继承的方案,分别是原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承。图灵社区本书地址,后文放出github链接,里面包含这几种继承的代码demo。《JavaScript面向对象编程第2版》-第6章 继承,12种继承的方案。1.原型链法(仿传统)、2.仅从原型继承法、3.临时构造器法、4.原型属性拷贝法、5.全属性拷贝法(即浅拷贝法)、6.深拷贝法、7.原型继承法、8.扩展与增强模式、9.多重继承法、10.寄生继承法、11.构造器借用法、12、构造器借用与属性拷贝法。ES6标准入门-第21章class的继承《深入理解ES6》-第9章 JavaScript中的类《你不知道的JavaScript-上卷》第6章 行为委托和附录A ES6中的class总结继承对于JS来说就是父类拥有的方法和属性、静态方法等,子类也要拥有。子类中可以利用原型链查找,也可以在子类调用父类,或者从父类拷贝一份到子类等方案。继承方法可以有很多,重点在于必须理解并熟悉这些对象、原型以及构造器的工作方式,剩下的就简单了。寄生组合式继承是开发者使用比较多的。回顾寄生组合式继承。主要就是三点:子类构造函数的__proto__指向父类构造器,继承父类的静态方法子类构造函数的prototype的__proto__指向父类构造器的prototype,继承父类的方法。子类构造器里调用父类构造器,继承父类的属性。行文到此,文章就基本写完了。文章代码和图片等资源放在这里github inhert和demo展示es6-extends,结合console、source面板查看更佳。读者发现有不妥或可改善之处,欢迎评论指出。另外觉得写得不错,可以点赞、评论、转发,也是对笔者的一种支持。关于作者:常以轩辕Rowboat若川为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。个人博客segmentfault前端视野专栏,开通了前端视野专栏,欢迎关注掘金专栏,欢迎关注知乎前端视野专栏,开通了前端视野专栏,欢迎关注github,欢迎follow~ ...

February 20, 2019 · 5 min · jiezi

【Leetcode】100. 相同的树

题目给定两个二叉树,编写一个函数来检验它们是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。示例 1:输入: 1 1 / \ / \ 2 3 2 3 [1,2,3], [1,2,3]输出: true示例 2:输入: 1 1 / \ 2 2 [1,2], [1,null,2]输出: false示例 3::输入: 1 1 / \ / \ 2 1 1 2 [1,2,1], [1,1,2]输出: false题解大多数的二叉树题目都是用递归可以解的。所以当拿到二叉树的题目的时候,我们首先就是看看能拆解成哪些子问题。这个问题的子问题很简单,就是左子树,右子树都相等的二叉树是相同的二叉树。/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } /class Solution { public boolean isSameTree(TreeNode p, TreeNode q) { if (p == null && q == null) { return true; } else if (p == null || q == null) { return false; } if (p.val == q.val) { return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); } else { return false; } }}那如果用非递归解怎么解呢?如果遇到二叉树的问题,没思路还有第二招,就是想想看是不是遍历的变种:先序遍历中序遍历后序遍历层次遍历我们可以用队列,一起进行层序遍历,同时比较左右两颗树:/* * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */class Solution { public boolean isSameTree(TreeNode p, TreeNode q) { if (p == null && q == null) { return true; } else if (p == null || q == null) { return false; } LinkedList<TreeNode> queue = new LinkedList<>(); queue.add(p); queue.add(q); while(!queue.isEmpty()) { TreeNode left = queue.poll(); TreeNode right = queue.poll(); if (left == null && right == null) { // 都是空的,遍历到叶子节点了 continue; } else if (left == null || right == null) { // 有一个为null return false; } else if (left.val != right.val) { // 不相等. return false; } // 左子树入队 queue.add(left.left); queue.add(right.left); // 右子树入队 queue.add(left.right); queue.add(right.right); } return true; }}其实我们本质上就是要比较左右两棵树,也没必要非要是队列,其实stack也是可以的,大同小异。所以并不是你记住哪种数据结构,关键是你能理解后,灵活应用. public boolean isSameTree(TreeNode p, TreeNode q) { if (p == null && q == null) { return true; } else if (p == null || q == null) { return false; } Stack<TreeNode> stack = new Stack<>(); stack.push(p); stack.push(q); while(!stack.isEmpty()) { TreeNode left = stack.pop(); TreeNode right = stack.pop(); if (left == null && right == null) { continue; } else if (left == null || right == null) { return false; } else if (left.val != right.val) { return false; } stack.push(left.left); stack.push(right.left); stack.push(left.right); stack.push(right.right); } return true; }相关阅读技术文章汇总【Leetcode】98. 验证二叉搜索树【Leetcode】95~96 不同的二叉搜索树 ...

February 20, 2019 · 2 min · jiezi

【Leetcode】98. 验证二叉搜索树

题目给定一个二叉树,判断其是否是一个有效的二叉搜索树。假设一个二叉搜索树具有如下特征:节点的左子树只包含小于当前节点的数。节点的右子树只包含大于当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。示例 1:输入: 2 / \ 1 3输出: true示例 2:输入: 5 / \ 1 4 / \ 3 6输出: false解释: 输入为: [5,1,4,null,null,3,6]。 根节点的值为 5 ,但是其右子节点值为 4 。题解这道题目主要是利用二叉搜索树的一个性质:二叉搜索树的中序遍历结果是一个升序的序列。那么问题转变成:中序遍历 + 验证是不是升序./** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */class Solution { private List<Integer> inPrint(TreeNode root, List<Integer> res) { if (root != null) { inPrint(root.left, res); res.add(root.val); inPrint(root.right, res); } return res; } public boolean isValidBST(TreeNode root) { List<Integer> res = inPrint(root, new LinkedList<>()); if (res.size() == 1) { return true; } for (int i = 1; i < res.size(); i++) { if (res.get(i) <= res.get(i - 1)) { return false; } } return true; }} ...

February 19, 2019 · 1 min · jiezi

刷前端面经笔记(十二)

1.以下递归函数存在栈溢出的风险,请问如何优化?function factorial(n){ return nfactorial(n-1)}解答:function factorial(n){ if(n === 1) { return 1;}; return nfactorial(n-1)}2.请实现一个计算最大公约数的函数:function greatestCommonDivisor(a,b){//在这里编写代码}greatestCommonDivisor(8, 12) //4greatestCommonDivisor(8, 16) //8greatestCommonDivisor(8, 17) //1解答:function greatestCommonDivisor(a,b){ var num=0; while(b!=0){ num=a%b; a=b; b=num; } return a;}3.数组去重(如果数组中有NaN)Array.prototype.uniq = function () { var resArr = []; var flag = true; for(var i=0;i<this.length;i++){ if(resArr.indexOf(this[i]) == -1){ if(this[i] != this[i]){ //排除 NaN if(flag){ resArr.push(this[i]); flag = false; } }else{ resArr.push(this[i]); } } } return resArr;}4.用 JavaScript 实现斐波那契数列函数,返回第n个斐波那契数。 f(1) = 1, f(2) = 1 等function fibonacci(n) { if(n ==1 || n == 2){ return 1 } return fibonacci(n - 1) + fibonacci(n - 2);}5.根据包名,在指定空间中创建对象输入描述:namespace({a: {test: 1, b: 2}}, ‘a.b.c.d’)输出描述:{a: {test: 1, b: {c: {d: {}}}}}function namespace(oNamespace, sPackage) { var properties = sPackage.split(’.’); var parent = oNamespace; for (var i = 0, lng = properties.length; i < lng; ++i) { var property = properties[i]; if (Object.prototype.toString.call(parent[property])!== ‘[object Object]’) { parent[property] = {}; } parent = parent[property]; } return oNamespace; }6.封装函数 f,使 f 的 this 指向指定的对象function bindThis(f, oTarget) { return function(){ var parames = Array.prototype.slice.call(arguments); return f.apply(oTarget,parames); //注意这里需要返回f的执行结果 } }7.dom 节点查找查找两个节点的最近的一个共同父节点,可以包括节点自身 输入描述:oNode1 和 oNode2 在同一文档中,且不会为相同的节点function commonParentNode(oNode1, oNode2) { if(oNode1.contains(oNode2)){ return oNode1; }else if(oNode2.contains(oNode1)){ return oNode2; }else{ return commonParentNode(oNode1.parentNode,oNode2); }}8.关系型数组转换成树形结构对象关系型数组:var obj = [ { id:3, parent:2 }, { id:1, parent:null }, { id:2, parent:1 },]期望结果:o = { obj: { id: 1, parent: null, child: { id: 2, parent: 1, child: { id: ,3, parent: 2 } } }}实现源码:function treeObj(obj) { obj.map(item => { if (item.parent !== null) { obj.map(o => { if (item.parent === o.id) { if (!o.child) { o.child = []; } o.child.push(item); o.child = o.child; } }); } }); return obj.filter(item => item.parent === null)[0]}或者:function treeObj(obj) { return obj.sort((a, b) => b.parent - a.parent) .reduce((acc, cur) => (acc ? { …cur, child: acc } : cur));}9.JS如何判断一组数字是否连续// 当出现连续数字的时候以‘-’输出[1, 2, 3, 4, 6, 8, 9, 10]期望结果:[“1-4”, 6, “8-10”]实现代码:判断是否连续:var arrange = function(arr){ var result = [],temp = []; arr.sort(function(source, dest){ return source - dest; }).concat(Infinity).reduce(function(source, dest){ temp.push(source); if(dest-source > 1){ result.push(temp); temp = []; } return dest; }); return result;};格式化实现:var formatarr = function(arr) { var newArr = [] var arr1 = arrange(arr) for (var i in arr1) { var str = ‘’; if (arr1[i].length > 1) { str = arr1[i][0] + ‘-’ + arr1[i][arr1[i].length - 1]; newArr.push(str) } else { newArr.push(arr1[i][0]); } } return newArr;}10.创建子类Child,使用原型和构造函数的方式继承父类People的方法,并调用say函数说出姓名和年龄。父类:function People(name,age){ this.name=name; this.age=age; this.say=function(){ console.log(“我的名字是:"+this.name+“我今年”+this.age+“岁!”); };}原型继承:function Child(name, age){ this.name = name; this.age = age;}Child.prototype = new People();var child = new Child(‘Rainy’, 20);child.say()构造函数继承:function Child(name, age){ People.call(this) this.name = name; this.age = age;}var child = new Child(‘Rainy’, 20);child.say()组合继承:function Child(name, age){ People.call(this); this.name = name; this.age = age;}Child.prototype = People.prototype;var child = new Child(‘Rainy’, 20);child.say()组合继承优化:function Child(name, age){ People.call(this); this.name = name; this.age = age;}Child.prototype = Object.create(People.prototype);Child.prototype.constructor = Child;var child = new Child(‘Rainy’, 20);child.say() ...

February 19, 2019 · 3 min · jiezi

前端进击的巨人(七):走进面向对象,原型与原型链,继承方式

“面向对象” 是以 “对象” 为中心的编程思想,它的思维方式是构造。“面向对象” 编程的三大特点:“封装、继承、多态”:封装:属性方法的抽象继承:一个类继承(复制)另一个类的属性/方法多态:方法(接口)重写"面向对象” 编程的核心,离不开 “类” 的概念。简单地理解下 “类”,它是一种抽象方法。通过 “类” 的方式,可以创建出多个具有相同属性和方法的对象。但是!但是!但是JavaScript中并没有 “类” 的概念,对的,没有。ES6 新增的 class 语法,只是一种模拟 “类” 的语法糖,底层机制依旧不能算是标准 “类” 的实现方式。在理解JavaScript中如何实现 “面向对象” 编程之前,有必要对JavaScript中的对象先作进一步地了解。什么是对象对象是"无序属性"的集合,表现为"键/值对"的形式。属性值可包含任何类型值(基本类型、引用类型:对象/函数/数组)。有些文章指出"JS中一切都是对象",略有偏颇,修正为:“JS中一切引用类型都是对象"更为稳妥些。函数 / 数组都属于对象,数组就是对象的一种子类型,不过函数稍微复杂点,它跟对象的关系,有点"鸡生蛋,蛋生鸡"的关系,可先记住:“对象由函数创建”。简单对象的创建字面量声明(常用)new 操作符调用 Object 函数// 字面量let person = { name: ‘以乐之名’};// new Object()let person = new Object();person.name = ‘以乐之名’;以上两种创建对象的方式,并不具备创建多个具有相同属性的对象。TIPS:new 操作符会对所有函数进行劫持,将函数变成构造函数(对函数的构造调用)。对象属性的访问方式. 操作符访问 (也称 “键访问”)[] 操作符访问(也称 “属性访问”). 操作符 VS [] 操作符:. 访问属性时,属性名需遵循标识符规范,兼容性比 [] 略差;[] 接受任意UTF-8/Unicode字符串作为属性名;[] 支持动态属性名(变量);[] 支持表达式计算(字符串连接 / ES6的Symbol)TIPS: 标识符命名规范 —— 数字/英文字母/下划线组成,开头不能是数字。// 任意UTF-8/Unicode字符串作为属性名person[’$my-name’];// 动态属性名(变量)let attrName = ’name’;person[attrName]; // 表达式计算let attrPrefix = ‘my_’;person[attrPrefix + ’name’]; // person[‘my_name’]person[Symbol.name]; // Symbol在属性名的应用属性描述符ES5新增 “属性描述符”,可针对对象属性的特性进行配置。属性特性的类型1. 数据属性Configurable 可配置(可删除)?[true|false]Enumerable 可枚举 [true|false]Writable 可写? [true|false]Value 值?默认undefined2. 访问器属性Get [[Getter]] 读取方法Set [[Setter]] 设置方法访问器属性优先级高于数据属性访问器属性会优于 writeable/value获取属性值时,如果对象属性存在 get(),会忽略其 value 值,直接调用 get();设置属性值时,如果对象属性存在 set(),会忽略 writable 的设置,直接调用 set();访问器属性日常应用:属性值联动修改(一个属性值修改,会触发另外属性值修改);属性值保护(只能通过 set() 制定逻辑修改属性值)定义属性特性Object.defineProperty() 定义单个属性Object.defineProperties() 定义多个属性let Person = {};Object.defineProperty(‘Person’, ’name’, { writable: true, enumerable: true, configurable: true, value: ‘以乐之名’});Person.name; // 以乐之名TIPS:使用 Object.defineProperty/defineProperties 定义属性时,属性特性 configurable/enumerable/writable 值默认为 false,value 默认为 undefined。其它方式创建对象属性时,前三者值都为 true。可使用Object.getOwnPropertyDescriptor() 来获取对象属性的特性描述。原型JavaScript中模拟 “面向对象” 中 “类” 的实现方式,是利用了JavaScript中函数的一个特性(属性)——prototype(本身是一个对象)。每个函数默认都有一个 prototype 属性,它就是我们所说的 “原型”,或称 “原型对象”。每个实例化创建的对象都有一个 proto 属性(隐式原型),它指向创建它的构造函数的 prototype 属性。new + 函数(实现"原型关联”)let Person = function(name, age) { this.name = name; this.age = age;};Person.prototype.say = function() {};let father = new Person(‘David’, 48);let mother = new Person(‘Kelly’, 46);new操作符的执行过程,会对实例对象进行 “原型关联”,或称 “原型链接”。new的执行过程创建(构造)一个全新的空对象“这个新对象会被执行"原型"链接(新对象的__proto__会指向函数的prototype)”构造函数的this会指向这个新对象,并对this属性进行赋值如果函数没有返回其他对象,则返回这个新对象(注意构造函数的return,一般不会有return)原型链"对象由函数创建",既然 prototype 也是对象,那么它的 proto 原型链上应该还有属性。Person.prototype.proto 指向 Function.prototype,而Function.prototype.proto 最终指向 Object.prototype.proto。TIPS:Object.prototype.proto 指向 null(特例)。日常调用对象的 toString()/valueOf() 方法,虽然没有去定义它们,但却能正常使用。实际上这些方法来自 Object.prototype,所有普通对象的原型链最终都会指向 Object.prototype,而对象通过原型链关联(继承)的方式,使得实例对象可以调用 Object.prototype 上的属性 / 方法。访问一个对象的属性时,会先在其基础属性上查找,找到则返回值;如果没有,会沿着其原型链上进行查找,整条原型链查找不到则返回 undefined。这就是原型链查找。基础属性与原型属性hasOwnProperty()判断对象基础属性中是否有该属性,基础属性返回 true。涉及 in 操作都是所有属性(基础 + 原型)for…in… 遍历对象所有可枚举属性in 判断对象是否拥有该属性Object.keys(…)与Object.getOwnPropertyNames(…)Object.keys(…) 返回所有可枚举属性Object.getOwnPropertyNames(…) 返回所有属性屏蔽属性修改对象属性时,如果属性名与原型链上属性重名,则在实例对象上创建新的属性,屏蔽对象对原型属性的使用(发生屏蔽属性)。屏蔽属性的前提是,对象基础属性名与原型链上属性名存在重名。创建对象属性时,属性特性对屏蔽属性的影响对象原型链上有同名属性,且可写,在对象上创建新属性(屏蔽原型属性);对象原型链上有同名属性,且只读,忽略;对象原型链上有同名属性,存在访问器属性 set(),调用 set()批量创建对象的方式创建多个具有相同属性的对象1. 工厂模式function createPersonFactory(name, age) { var obj = new Object(); obj.name = name; obj.age = age; obj.say = function() { console.log(My name is ${this.name}, i am ${this.age}); }}var father = createPersonFactory(‘David’, 48);var mother = createPersonFactory(‘Kelly’, 46);father.say(); // ‘My name is David, i am 48’mother.say(); // ‘My name is Kelly, i am 46’缺点:无法解决对象识别问题属性值为函数时无法共用,不同实例对象的 say 方法没有共用内存空间obj.say = function(){…} 实例化一个对象时都会开辟新的内存空间,去存储function(){…},造成不必要的内存开销。father.say == mother.say; // false2. 构造函数(new)function Person(name, age) { this.name = name; this.age = age; this.say = function() { console.log(My name is ${this.name}, i am ${this.age}); }}let father = new Person(‘David’, 48);缺点:属性值为引用类型(say方法)时无法共用,不同实例对象的 say 方法没有共用内存空间(与工厂模式一样)。3. 原型模式function Person() {}Person.prototype.name = ‘David’;Person.prototype.age = 48;Person.prototype.say = function() { console.log(My name is ${this.name}, i am ${this.age});};let father = new Person();优点:解决公共方法内存占用问题(所有实例属性的 say 方法共用内存)缺点:属性值为引用类型时,因内存共用,一个对象修改属性会造成其它对象使用属性发生改变。Person.prototype.like = [‘sing’, ‘dance’];let father = new Person();let mother = new Person();father.like.push(’travel’);// 引用类型共用内存,一个对象修改属性,会影响其它对象father.like; // [‘sing’, ‘dance’, ’travel’]mother.like; // [‘sing’, ‘dance’, ’travel’]4. 构造函数 + 原型(经典组合)function Person(name, age) { this.name = name; this.age = age;}Person.prototype.say = function() { console.log(My name is ${this.name}, i am ${this.age});}原理:结合构造函数和原型的优点,“构造函数初始化属性,原型定义公共方法”。5. 动态原型构造函数 + 原型的组合方式,区别于其它 “面向对象” 语言的声明方式。属性方法的定义并没有统一在构造函数中。因此动态原型创建对象的方式,则是在 “构造函数 + 原型组合” 基础上,优化了定义方式(区域)。function Person(name, age) { this.name = name; this.age = age; // 判断原型是否有方法,没有则添加; // 原型上的属性在构造函数内定义,仅执行一次 if (!Person.prototype.say) { Person.prototype.say = function() { console.log(My name is ${this.name}, i am ${this.age}); } }}优点:属性方法统一在构造函数中定义。除了以上介绍的几种对象创建方式,此外还有"寄生构造函数模式"、“稳妥构造函数模式”。日常开发较少使用,感兴趣的伙伴们可自行了解。“类” 的继承传统的面向对象语言中,“类” 继承的原理是 “类” 的复制。但JavaScript模拟 “类” 继承则是通过 “原型关联” 来实现,并不是 “类” 的复制。正如《你不知道的JavaScript》中提出的观点,这种模拟 “类” 继承的方式,更像是 “委托”,而不是 “继承”。以下列举JavaScript中常用的继承方式,预先定义两个类:“Person” 父类(超类)“Student” 子类(用来继承父类)// 父类统一定义function Person(name, age) { // 构造函数定义初始化属性 this.name = name; this.age = age;}// 原型定义公共方法Person.prototype.eat = function() {};Person.prototype.sleep = function() {};原型继承// 原型继承function Student(name, age, grade) { this.grade = grade;};Student.prototype = new Person(); // Student原型指向Person实例对象Student.prototype.constructor = Student; // 原型对象修改,需要修复constructor属性let pupil = new Student(name, age, grade);原理:子类的原型对象为父类的实例对象,因此子类原型对象中拥有父类的所有属性缺点:无法向父类构造函数传参,初始化属性值属性值是引用类型时,存在内存共用的情况无法实现多继承(只能为子类指定一个原型对象)构造函数继承// 构造函数继承function Student(name, age, grade) { Person.call(this, name, age); this.grade = grade;}原理:调用父类构造函数,传入子类的上下文对象,实现子类参数初始化赋值。仅实现部分继承,无法继承父类原型上的属性。可 call 多个父类构造函数,实现多继承。缺点:属性值为引用类型时,需开辟多个内存空间,多个实例对象无法共享公共方法的存储,造成不必要的内存占用。原型 + 构造函数继承(经典)// 原型 + 构造函数继承function Student(name, age, grade) { Person.call(this, name, age); // 第一次调用父类构造函数 this.grade = grade;}Student.prototype = new Person(); // 第二次调用父类构造函数Student.prototype.constructor = Student; // 修复constructor属性原理:结合原型继承 + 构造函数继承两者的优点,“构造函数继承并初始化属性,原型继承公共方法”。缺点:父类构造函数被调用了两次。待优化:父类构造函数第一次调用时,已经完成父类构造函数中 “属性的继承和初始化”,第二次调用时只需要 “继承父类原型属性” 即可,无须再执行父类构造函数。寄生组合式继承(理想)// 寄生组合式继承function Student(name, age, grade) { Person.call(this, name, age); this.grade = grade;}Student.prototype = Object.create(Person.prototype); // Object.create() 会创建一个新对象,该对象的__proto__指向Person.prototypeStudent.prototype.constructor = Student;let pupil = new Student(‘小明’, 10, ‘二年级’);原理:创建一个新对象,将该对象原型关联至父类的原型对象,子类 Student 已使用 call 来调用父类构造函数完成初始化,所以只需再继承父类原型属性即可,避免了经典组合继承调用两次父类构造函数。(较完美的继承方案)ES6的class语法class Person { constructor(name, age) { this.name = name; this.grade = grade; } eat () { //… } sleep () { //… }}class Student extends Person { constructor (name, age, grade) { super(name, age); this.grade = grade; } play () { //… }}优点:ES6提供的 class 语法使得类继承代码语法更加简洁。Object.create(…)Object.create()方法会创建一个新对象,使用现有对象来提供新创建的对象的__proto__Object.create 实现的其实是"对象关联",直接上代码更有助于理解:let person = { eat: function() {}; sleep: function() {};}let father = Object.create(person); // father.proto -> person, 因此father上有eat/sleep/talk等属性father.eat();father.sleep();上述代码中,我们并没有使用构造函数 / 类继承的方式,但 father 却可以使用来自 person 对象的属性方法,底层原理依赖于原型和原型链的魔力。// Object.create实现原理/模拟Object.create = function(o) { function F() {} F.prototype = o; return new F();}Object.create(…) 实现的 “对象关联” 的设计模式与 “面向对象” 模式不同,它并没有父类,子类的概念,甚至没有 “类” 的概念,只有对象。它倡导的是 “委托” 的设计模式,是基于 “面向委托” 的一种编程模式。文章篇幅有限,仅作浅显了解,后续可另开一章讲讲 “面向对象” VS “面向委托”,孰优孰劣,说一道二。对象识别(检查 “类” 关系)instanceofinstanceof 只能处理对象与函数的关系判断。instanceof 左边是对象,右边是函数。判断规则:沿着对象的 proto 进行查找,沿着函数的 prototype 进行查找,如果有关联引用则返回 true,否则返回 false。let pupil = new Student();pupil instanceof Student; // truepupil instanceof Person; // true Student继承了PersonObject.prototype.isPrototypeOf(…)Object.prototype.isPrototyepOf(…) 可以识别对象与对象,也可以是对象与函数。let pupil = new Student();Student.prototype.isPrototypeOf(pupil); // true判断规则:在对象 pupil 原型链上是否出现过 Student.prototype , 如果有则返回 true, 否则返回 falseES6新增修改对象原型的方法: Object.setPrototypeOf(obj, prototype),存在有性能问题,仅作了解,更推荐使用 Object.create(…)。Student.prototype = Object.create(Person.prototype);// setPrototypeOf改写上行代码Object.setPrototypeOf(Student.prototype, Person.prototype);后语"面向对象" 是程序编程的一种设计模式,具备 “封装,继承,多态” 的特点,在ES6的 class 语法未出来之前,原型继承确实是JavaScript入门的一个难点,特别是对新入门的朋友,理解起来并不友好,模拟继承的代码写的冗余又难懂。好在ES6有了 class 语法糖,不必写冗余的类继承代码,代码写少了,眼镜片都亮堂了。老话说的好,“会者不难”。深入理解面向对象,原型,继承,对日后代码能力的提升及编码方式优化都有益处。好的方案不只有一种,明白个中缘由,带你走进新世界大门。参考文档:你不知道的JavaScript(上卷)《JavaScript高级程序设计》JavaScript常见的六种继承方式深入理解javascript原型和闭包本文首发Github,期待Star!https://github.com/ZengLingYong/blog作者:以乐之名本文原创,有不当的地方欢迎指出。转载请指明出处。 ...

February 18, 2019 · 4 min · jiezi

PHP面试常考内容之Memcache和Redis(1)

你好,是我琉忆。继上周(2019.2-11至2-15)发布的“PHP面试常考内容之面向对象”专题后,发布的第二个专题,感谢你的阅读。本周(2019.2-18至2-22)的文章内容点为以下几点,更新时间为每周一三五,可以关注本栏持续关注,感谢你的支持。一、什么是Memcache?二、Memcache有什么特征?三、Memcache的内存管理机制是什么样的?四、Memcache和Memcached有什么区别?五、如何操作Memcache?六、如何使用Memcache做Session共享?七、什么是Redis?八、如何使用Redis?九、使用Redis需要注意哪些问题?十、Memcache和Redis常考的面试题本章节的内容将会被分为三篇文章进行讲解完整块内容,第一篇主要讲解一到六,第二篇主要讲解七到九,第三篇围绕第十点。以下正文的部分内容来自《PHP程序员面试笔试宝典》书籍,如果转载请保留出处:一、什么是Memcache?Memcache是一个开源、免费、高性能的分布式对象缓存系统,它基于一个存储键/值对的hashmap来存储数据到内存中。它的作用是减少对数据库的读取以提高Web应用的性能。虽然它的守护进程(daemon )是用 C语言实现的,但是客户端可以用任何语言来编写,并通过Memcache协议与守护进程通信。需要注意的是,当某个服务器停止运行或崩溃了,所有存放在该服务器上的键/值对都将丢失。Memcache的服务器端没有提供分布式功能,各个Memcache应用不会互相通信以共享信息。想要实现分布式通信,可以搭建几个Memcache应用,通过算法实现此效果。下面介绍Memcache的两个重要概念。slab:为了防止内存碎片化,Memcache服务器端会预先将数据空间划分为一系列slab;举个例子,现在有一个100m3的房间,为了合理规划这个房间放置东西,会在这个房间里放置30个1m3的盒子、20个1.25m3的盒子、15个1.5m3的盒子……这些盒子就是slab。LRU:最近最少使用算法;当同一个slab的格子满了,这时需要新加一个值时,不会考虑将这个新数据放到比当前slat更大的空闲slab,而是使用LRU移除旧数据,放入这个新数据。二、Memcache有什么特征?Memcache的特征如下:1)协议简单。2)基于libevent的事件处理。3)内置内存存储方式。4)Memcached不互相通信的分布式。Memcache的特性如下:(1)单个item 最大的数据为1MB。(2)单进程最大的使用内存为2GB,需要更多内存时可开多个端口。(3)Memcached是多线程,非阻塞io复用的网络模型,Redis是单线程。(4)键长最大为250字节。三、Memcache的内存管理机制是什么样的?Memcache将内存分割成各种尺寸的块(chunk),并把尺寸相同的块分成组(chunk的集合)。page是分配给slab的内存空间,默认是1MB,根据slab大小切分成chunk,chunk是用户缓存记录的内存空间,slab class是特定chunk的组。在存储数据时,Memcache会压缩数据的大小进行存储,一般压缩后的数据为原数据大小的30%左右,从而节省了70%的传输性能消耗。如果存储的数是小于100字节的键值对,那么压缩后可能带来膨胀,但Memcache都是按照固定大小分块存储的,最小也有88B,所以小数据带来的压缩膨胀也并不会有什么影响。具体流程如下图所示。自己整理了一篇“如何解决Memcache的缓存雪崩现象?”的文章,关注公众号:“琉忆编程库”,回复:“me”,我发给你。四、Memcache和Memcached有什么区别?其实 memcache 和 memcached 说的是 PHP 的扩展。Memcache是一个自由和开放源代码、高性能、分配的内存对象缓存系统。用于加速动态web应用程序,减轻数据库负载。它可以应对任意多个连接,使用非阻塞的网络IO。由于它的工作机制是在内存中开辟一块空间,然后建立一个Hash表,Memcached自管理这些Hash表。Memcache是该系统的项目名称,Memcached是该系统的主程序文件(字母d可以理解为daemon),以守护程序方式运行于一个或多个服务器中,随时接受客户端的连接操作,使用共享内存存取数据。Memcached处理的原子是每一个(key,value)对(以下简称kv对),key会通过一个hash算法转化成hash-key,便于查找、对比以及做到尽可能的散列。同时,memcached用的是一个二级散列,通过一张大hash表来维护。Memcached有两个核心组件组成:服务端(Server)和客户端(Client),在一个memcached的查询中,Client先通 过计算key的hash值来确定kv对所处在的Server位置。当Server确定后,客户端就会发送一个查询请求给对应的Server,让它来查找确 切的数据。因为这之间没有交互以及多播协议,所以 memcached交互带给网络的五、如何操作Memcache?Memcached的操作命令可以分为存储命令、查找命令、统计命令等三大类。第一类,Memcached的存储命令:第二类,Memcached的查找命令:第三类,Memcached的统计命令:使用示例代码参考:<?php $m = new Memcached(); $m->addServer(“127.0.0.1”,11211); //连接Memcache服务器 $m->set(“mkey”,“123”,600);//设置一个键名为mkey,值为123,600s过期,0为永久有效 echo $m->get(“mkey”);//输出123,get可以获取键名为mkey的值 $m->delete(“mkey”);//删除键名为mkey的值?>具体全部的使用方法不在此一一罗列,可根据上面的方法表和参考示例进行模仿使用。六、如何使用Memcache做Session共享?默认状态下,PHP使用文件方式做Session存储,磁盘的I/O性能肯定没有内存中存取快,因此改用memcache来存储Session在读写速度上会快很多,而且在多台服务器集群时,使用memcahced能够有效地解决Session共享的问题。我们配置好memcached服务器后,修改php.ini配置文件的代码:session.save_handler = memcachesession.save_path = “tcp://127.0.0.1:11211”也可以使用在网站目录下放置Apache的.htaccess文件:php_value session.save_handler “memcache”php_value session.save_path “tcp://127.0.0.1:11211”执行PHP脚本时写入以下两行:ini_set(“session.save_handler”,”memcache”);ini_set(“session.save_path”,”tcp://127.0.0.1:11211”);可以在使用多个memcached服务器时用都好隔开,还可以额外带参数:persistent、weight、timeout、retry_interval等。例如:$session_save_path = “tcp://<memcache_server1>:11211?persistent=1&weight=1&timeout=1&retry_interval=15,udp://<memcache_server2>:11211”;如果想使用udp支持,需要确保你的memcached服务器启用,也确保Web服务器连接到memcache服务器端口正常。重新启动Apache后,将开始使用的memcache存储PHP的Session。测试案例:<?php Session_start(); $_SESSION[‘test’] = time(); echo $_SESSION[‘test’].”<br>”; echo session_id();?>测试memcache是否已存储成功,通过sessionid去取对应的数据:$m = memcache_connect(‘localhost’,11211);echo $m->get(“13765595df9dsa8f9dsa8fa9sf8saf865”);得到结果:17559898989如果使用memcached作为Session存储,要确保memcached的服务正常工作,否则Session相关功能将不起作用,这样PHP的处理就多了一层外面的依赖。因为memcached是使用内存的,这样当用户量比较大时,就可能由于内存方面原因导致Session时长上的问题,Session的实际失效时长达不到设定的失效时长(由memcached在内存不够时的处理机制决定)。Memcache可扩展考察的内容有很多,由于整理的篇幅内容很多,在此只是罗列了普遍遇到的Memcache相关的知识考点。额外的考点还有:(1)Memcache的工作流程是什么样的?(2)Memcache如何实现分布式?(3)Memcache的分布式算法有哪些?(4)使用Memcache有哪些技术限制?(5)Memcache和Redis有什么相同和区别的地方?更多相关面试知识点可以阅读《PHP程序员面试笔试宝典》。预告:PHP面试常考内容之Memcache和Redis(2)将于本周三(2019.2-20)更新。以上内容摘自《PHP程序员面试笔试宝典》书籍,该书已在天猫、京东、当当等电商平台销售。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,我将不断完善追求极致,感谢你们的支持。

February 18, 2019 · 1 min · jiezi

【剑指offer】让抽象问题具体化

1.包含min函数的栈定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。思路1.定义两个栈,一个栈用于存储数据,另一个栈用于存储每次数据进栈时栈的最小值.2.每次数据进栈时,将此数据和最小值栈的栈顶元素比较,将二者比较的较小值再次存入最小值栈.4.数据栈出栈,最小值栈也出栈。3.这样最小值栈的栈顶永远是当前栈的最小值。代码var dataStack = [];var minStack = []; function push(node){ dataStack.push(node); if(minStack.length === 0 || node < min()){ minStack.push(node); }else{ minStack.push(min()); }}function pop(){ minStack.pop(); return dataStack.pop();}function top(){ var length = dataStack.length; return length>0&&dataStack[length-1]}function min(){ var length = minStack.length; return length>0&&minStack[length-1]}2.栈的压入、弹出序列输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)思路1.借助一个辅助栈来存储数据。2.将pushV中的数据依次入栈。3.出栈有可能在任意一次入栈后进行,当出栈数据不再位于栈顶,继续入栈。4.所以设置一个索引,记录当前出栈的位置,每次出栈索引+1。5.当所有数据入栈完成,如果出栈顺序正确,那么辅助栈应该为空。代码 function IsPopOrder(pushV, popV) { if (!pushV || !popV || pushV.length == 0 || popV.length == 0) { return; } var stack = []; var idx = 0; for (var i = 0; i < pushV.length; i++) { stack.push(pushV[i]); while (stack.length && stack[stack.length - 1] == popV[idx]) { stack.pop(); idx++; } } return stack.length == 0; }3.题二叉树的后续遍历输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。思路1.后序遍历:分成三部分:最后一个节点为跟节点,第二部分为左子树的值比跟节点都小,第三部分为右子树的值比跟节点都大。2.先检测左子树,左侧比跟节点小的值都判定为左子树。3.除最后一个节点外和左子树外的其他值为右子树,右子树有一个比跟节点小,则返回false。4.若存在,左、右子树,递归检测左、右子树是否复合规范。代码 function VerifySquenceOfBST(sequence) { if (sequence && sequence.length > 0) { var root = sequence[sequence.length - 1] for (var i = 0; i < sequence.length - 1; i++) { if (sequence[i] > root) { break; } } for (let j = i; j < sequence.length - 1; j++) { if (sequence[j] < root) { return false; } } var left = true; if (i > 0) { left = VerifySquenceOfBST(sequence.slice(0, i)); } var right = true; if (i < sequence.length - 1) { right = VerifySquenceOfBST(sequence.slice(i, sequence.length - 1)); } return left && right; } }4.二叉树中和为某一值的路径输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)思路1.使用前序遍历2.使用一个辅助栈来存储当前路径里的数据3.记录一个当前路径的和4.遍历到当前节点后,当前值入路径栈,和加当前值5.递归左孩子右孩子节点6.遍历完一个节点,退回到上一个节点,从路径栈中移除当前的值,和减当前值代码 function FindPath(root, expectNumber) { var result = []; if (!root) { return result; } findPath(root, expectNumber, [], 0, result); return result; } function findPath(node, expectNumber, vector, sum, result) { vector.push(node.val); sum += node.val; var isLeaf = !node.left && !node.right; if (isLeaf && sum === expectNumber) { result.push(vector.slice(0)); } if (node.left) { findPath(node.left, expectNumber, vector, sum, result); } if (node.right) { findPath(node.right, expectNumber, vector, sum, result); } vector.pop(); } ...

February 17, 2019 · 2 min · jiezi

「前端面试题系列6」理解函数的柯里化

前言这是前端面试题系列的第 6 篇,你可能错过了前面的篇章,可以在这里找到:ES6 中箭头函数的用法this 的原理以及用法伪类与伪元素的区别及实战如何实现一个圣杯布局?今日头条 面试题和思路解析最近,朋友T 在准备面试,他为一道编程题所困,向我求助。原题如下:// 写一个 sum 方法,当使用下面的语法调用时,能正常工作console.log(sum(2, 3)); // Outputs 5console.log(sum(2)(3)); // Outputs 5这道题要考察的,就是对函数柯里化的理解。让我们先来解析一下题目的要求:如果传递两个参数,我们只需将它们相加并返回。否则,我们假设它是以sum(2)(3)的形式被调用的,所以我们返回一个匿名函数,它将传递给sum()(在本例中为2)的参数和传递给匿名函数的参数(在本例中为3)。所以,sum 函数可以这样写:function sum (x) { if (arguments.length == 2) { return arguments[0] + arguments[1]; } return function(y) { return x + y; }}arguments 的用法挺灵活的,在这里它则用于分割两种不同的情况。当参数只有一个的时候,进行柯里化的处理。那么,到底什么是函数的柯里化呢?接下来,我们将从概念出发,探究函数柯里化的实现与用途。什么是柯里化柯里化,是函数式编程的一个重要概念。它既能减少代码冗余,也能增加可读性。另外,附带着还能用来装逼。先给出柯里化的定义:在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。柯里化的定义,理解起来有点费劲。为了更好地理解,先看下面这个例子:function sum (a, b, c) { console.log(a + b + c);}sum(1, 2, 3); // 6毫无疑问,sum 是个简单的累加函数,接受3个参数,输出累加的结果。假设有这样的需求,sum的前2个参数保持不变,最后一个参数可以随意。那么就会想到,在函数内,是否可以把前2个参数的相加过程,给抽离出来,因为参数都是相同的,没必要每次都做运算。如果先不管函数内的具体实现,调用的写法可以是这样: sum(1, 2)(3); 或这样 sum(1, 2)(10); 。就是,先把前2个参数的运算结果拿到后,再与第3个参数相加。这其实就是函数柯里化的简单应用。柯里化的实现sum(1, 2)(3); 这样的写法,并不常见。拆开来看,sum(1, 2) 返回的应该还是个函数,因为后面还有 (3) 需要执行。那么反过来,从最后一个参数,从右往左看,它的左侧必然是一个函数。以此类推,如果前面有n个(),那就是有n个函数返回了结果,只是返回的结果,还是一个函数。是不是有点递归的意思?网上有一些不同的柯里化的实现方式,以下是个人觉得最容易理解的写法:function curry (fn, currArgs) { return function() { let args = [].slice.call(arguments); // 首次调用时,若未提供最后一个参数currArgs,则不用进行args的拼接 if (currArgs !== undefined) { args = args.concat(currArgs); } // 递归调用 if (args.length < fn.length) { return curry(fn, args); } // 递归出口 return fn.apply(null, args); }}解析一下 curry 函数的写法:首先,它有 2 个参数,fn 指的就是本文一开始的源处理函数 sum。currArgs 是调用 curry 时传入的参数列表,比如 (1, 2)(3) 这样的。再看到 curry 函数内部,它会整个返回一个匿名函数。再接下来的 let args = [].slice.call(arguments);,意思是将 arguments 数组化。arguments 是一个类数组的结构,它并不是一个真的数组,所以没法使用数组的方法。我们用了 call 的方法,就能愉快地对 args 使用数组的原生方法了。在这篇 「干货」细说 call、apply 以及 bind 的区别和用法 中,有关于 call 更详细的用法介绍。currArgs !== undefined 的判断,是为了解决递归调用时的参数拼接。最后,判断 args 的个数,是否与 fn (也就是 sum )的参数个数相等,相等了就可以把参数都传给 fn,进行输出;否则,继续递归调用,直到两者相等。测试一下:function sum(a, b, c) { console.log(a + b + c);}const fn = curry(sum);fn(1, 2, 3); // 6fn(1, 2)(3); // 6fn(1)(2, 3); // 6fn(1)(2)(3); // 6都能输出 6 了,搞定!柯里化的用途理解了柯里化的实现之后,让我们来看一下它的实际应用。柯里化的目的是,减少代码冗余,以及增加代码的可读性。来看下面这个例子:const persons = [ { name: ‘kevin’, age: 4 }, { name: ‘bob’, age: 5 }];// 这里的 curry 函数,之前已实现let getProp = curry(function (key, obj) { return obj[key];});const ages = persons.map(getProp(‘age’)); // [4, 5]const names = persons.map(getProp(’name’)); // [‘kevin’, ‘bob’]在实际的业务中,我们常会遇到类似的列表数据。用 getProp 就可以很方便地,取出列表中某个 key 对应的值。另外,为了便于理解调用的写法,可以扩展一下:const names = persons.map(getProp(’name’));等价于:const names = persons.map(item => { return getProp(’name’, item);});最后,来看一个 Memoization 的例子。它用于优化比较耗时的计算,通过将计算结果缓存到内存中,这样对于同样的输入值,下次只需要中内存中读取结果。function memoizeFunction(func) { const cache = {}; return function() { let key = arguments[0]; if (cache[key]) { return cache[key]; } else { const val = func.apply(null, arguments); cache[key] = val; return val; } };}const fibonacci = memoizeFunction(function(n) { return (n === 0 || n === 1) ? n : fibonacci(n - 1) + fibonacci(n - 2);});console.log(fibonacci(100)); // 输出354224848179262000000console.log(fibonacci(100)); // 输出354224848179262000000代码中,第2次计算 fibonacci(100) 则只需要在内存中直接读取结果。总结函数的柯里化,是 Javascript 中函数式编程的一个重要概念。它返回的,是一个函数的函数。其实现方式,需要依赖参数以及递归,通过拆分参数的方式,来调用一个多参数的函数方法,以达到减少代码冗余,增加可读性的目的。虽然一开始理解起来有点云里雾里的,但一旦理解了其中的含义和具体的使用场景,用起来就会得心应手了。PS:欢迎关注我的公众号 “超哥前端小栈”,交流更多的想法与技术。 ...

February 17, 2019 · 2 min · jiezi

高阶函数的使用

问题字节跳动面试时问题:原函数例如fetchData是一个异步函数,尝试从服务器端获取一些信息并返回一个Promise。写一个新的函数可以自动重试一定次数,并且在使用上和原函数没有区别。思路这个问题其实不是很难,不过可能是太菜了紧张的原因,当时答得不是很好。不过思路还是很明确的,内部通过闭包来计数,一旦成功获得数据就返回,否则就继续尝试,直到重试次数达到上限位置。function retry(fetch, n) { let i = 0 function tryFetch(err) { if (i > n) { return “reach try limit:” + err } else { fetch().then(data => { return data }).catch(err => { i ++ tryFetch(err) }) } }}当时差不多就是那么答的,有几个问题是,函数调用方式与原来不通,按道理应该返回一个Promise,更准确的说是返回一个返回Promise的函数,如果有什么问题也应该在外面能catch住。另一方面,也许fetch函数要接受参数,也应该传递进去才行。解决修改后的函数如下function retry(fetch, n) { return function() { let args = arguments return new Promise((rseolve, reject) => { let i = 0 tryFetch() function tryFetch(err) { console.log(i) if (i > n) { reject(“reach max try” + err) } else { fetch(…args).then(data => { rseolve(data) }).catch(err => { i ++ tryFetch(err) }) } } }) }}最后自己写了个fetch进行测试function fetch() { console.log(arguments) return new Promise((rseolve, reject) => { console.log(’trying…’) setTimeout(function() { if (Math.random() > 0.9) { console.log(‘resolved’) rseolve(‘resolved’) } else { console.log(‘rejected’) reject(‘rejected’) } }, 5000) })}结果符合预期,问题解决。当然也可以返回async function,不过和Promise本质上是一个思路。 ...

February 17, 2019 · 1 min · jiezi

【前端面试】作用域和闭包

题目说一下对变量提升的理解说明this的几种不同使用场景创建10个a标签,点击的时候弹出来相应的序号如何理解作用域实际开发中闭包的应用2. 知识点2.1 执行上下文范围:一段script或者一个函数全局:变量定义、函数声明 script函数:变量定义、函数声明、this、arguments (执行之前)函数声明和函数表达式的区别:a(); //报错 函数表达式 变量声明 会提前。var a = function(){}b(); // 不报错 函数声明function b(){}变量定义时会默认把他的变量声明提升:(仅限于他的执行上下文,比如一段script和一个函数中)console.log(a);var a = 0;实际上是var a;console.log(a);a = 0;2.2 thisthis要在执行时才能确认,定义时无法确认。 var a = { name:‘a’, fn:function(){ console.log(this.name); } } a.fn(); // a a.fn.apply({name:‘b’}); // b a.fn.call({name:‘b’}); var fn1 = a.fn(); fn1(); // undefinedthis的使用场景构造函数中(指向构造的对象) function Fun(name){ this.name = name; } var f = new Fun(‘a’); console.log(f.name);对象属性中(指向该对象)普通函数中(指向window)call apply bind var fun = function (name){ console.log(this); console.log(name); }.bind({a:1}); fun(“name”);arguments中的this:var length = 10;function fn(){ alert(this.length)}var obj = { length: 5, method: function(fn) { arguments0 }}obj.method(fn)//输出1这里没有输出5,也没有输出10,反而输出了1,有趣。这里arguments是javascript的一个内置对象(可以参见mdn:arguments - JavaScript),是一个类数组(就是长的比较像数组,但是欠缺一些数组的方法,可以用slice.call转换,具体参见上面的链接),其存储的是函数的参数。也就是说,这里arguments[0]指代的就是你method函数的第一个参数:fn,所以arguments0的意思就是:fn()。不过这里有个疑问,为何这里没有输出5呢?我method里面用this,不应该指向obj么,至少也会输出10呀,这个1是闹哪样?实际上,这个1就是arguments.length,也就是本函数参数的个数。为啥这里的this指向了arguments呢?因为在Javascript里,数组只不过使用数字做属性名的方法,也就是说:arguments0的意思,和arguments.0()的意思差不多(当然这么写是不允许的),你更可以这么理解:arguments = { 0: fn, //也就是 functon() {alert(this.length)} 1: 第二个参数, //没有 2: 第三个参数, //没有 …, length: 1 //只有一个参数}所以这里alert出来的结果是1。如果要输出5应该咋写呢?直接 method: fn 就行了。2.3 作用域没有块级作用域 if(true){ var name = “test” } console.log(name);尽量不要在块中声明变量。只有函数级作用域2.4 作用域链自由变量 当前作用域没有定义的变量 即为自由变量。自由变量会去其父级作用域找。是定义时的父级作用域,而不是执行。 var a = 100; function f1(){ var b = 200; function f2(){ var c = 300; console.log(a); //自由变量 console.log(b); //自由变量 console.log(c); } f2(); }; f1();2.5 闭包 一个函数中嵌套另外一个函数,并且将这个函数return出去,然后将这个return出来的函数保存到了一个变量中,那么就创建了一个闭包。闭包的两个使用场景1.函数作为返回值 function fun(){ var a = 0; return function(){ console.log(a); //自由变量,去定义时的父级作用域找 } } var f1 = fun(); a = 1000; f1();2.函数作为参数 function fun(){ var a = 0; return function(){ console.log(a); //自由变量,去定义时的父级作用域找 } } function fun2(f2){ a = 10000 f2(); } var f1 = fun(); fun2(f1);具体解释看 高级-闭包中的说明闭包的两个作用:能够读取其他函数内部变量的函数可以让函数内部的变量一直保存在内存中实际应用场景1:闭包可以将一些不希望暴露在全局的变量封装成“私有变量”。假如有一个计算乘积的函数,mult函数接收一些number类型的参数,并返回乘积结果。为了提高函数性能,我们增加缓存机制,将之前计算过的结果缓存起来,下次遇到同样的参数,就可以直接返回结果,而不需要参与运算。这里,存放缓存结果的变量不需要暴露给外界,并且需要在函数运行结束后,仍然保存,所以可以采用闭包。上代码:function calculate(param){ var cache = {}; return function(){ if(!cache.parame){ return cache.param; }else{ //缓存计算…. //cache.param = result //下次访问直接取 } }}实际应用场景2延续局部变量的寿命img 对象经常用于进行数据上报,如下所示:var report = function( src ){ var img = new Image(); img.src = src;};report( ‘http://xxx.com/getUserInfo' );但是通过查询后台的记录我们得知,因为一些低版本浏览器的实现存在 bug,在这些浏览器下使用 report 函数进行数据上报会丢失 30%左右的数据,也就是说, report 函数并不是每一次都成功发起了 HTTP 请求。丢失数据的原因是 img 是 report 函数中的局部变量,当 report 函数的调用结束后, img 局部变量随即被销毁,而此时或许还没来得及发出 HTTP 请求,所以此次请求就会丢失掉。现在我们把 img 变量用闭包封闭起来,便能解决请求丢失的问题:var report = (function(){ var imgs = []; return function( src ){ var img = new Image(); imgs.push( img ); img.src = src; }})();闭包缺点:浪费资源!3. 题目解答3.1 说一下对变量提升的理解变量定义和函数声明注意函数声明和函数表达式的区别变量定义时会默认把他的变量声明提升:(仅限于他的执行上下文,比如一段script和一个函数中)console.log(a);var a = 0;实际上是var a;console.log(a);a = 0;3.2 说明this的几种不同使用场景构造函数中(指向构造的对象)对象属性中(指向该对象)普通函数中(指向window)call apply bind3.3 创建10个a标签,点击的时候弹出来相应的序号实现方法1:用let声明i var body = document.body; console.log(body); for (let i = 0; i < 10; i++) { let obj = document.createElement(‘i’); obj.innerHTML = i + ‘<br>’; body.appendChild(obj); obj.addEventListener(‘click’,function(){ alert(i); }) }实现方法2 包装作用域 var body = document.body; console.log(body); for (var i = 0; i < 10; i++) { (function (i) { var obj = document.createElement(‘i’); obj.innerHTML = i + ‘<br>’; body.appendChild(obj); obj.addEventListener(‘click’, function () { alert(i); }) })(i) }3.4 实际开发中闭包的应用能够读取其他函数内部变量的函数可以让函数内部的变量一直保存在内存中封装变量,权限收敛应用1var report = (function(){ var imgs = []; return function( src ){ var img = new Image(); imgs.push( img ); img.src = src; }})();用于防止变量销毁。应用2 function isFirstLoad() { var arr = []; return function (str) { if (arr.indexOf(str) >= 0) { console.log(false); } else { arr.push(str); console.log(true); } } } var fun = isFirstLoad(); fun(10); fun(10);将arr封装在函数内部,禁止随意修改,防止变量销毁。

February 16, 2019 · 2 min · jiezi

【Leetcode】95~96 不同的二叉搜索树

Leetcode 95不同的二叉搜索树 II输入: 3输出:[ [1,null,3,2], [3,2,null,1], [3,1,null,null,2], [2,1,3], [1,null,2,null,3]]解释:以上的输出对应以下 5 种不同结构的二叉搜索树: 1 3 3 2 1 \ / / / \ \ 3 2 1 1 3 2 / / \ \ 2 1 2 3Leetcode 86不同的二叉搜索树给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?示例:输入: 3输出: 5解释:给定 n = 3, 一共有 5 种不同结构的二叉搜索树: 1 3 3 2 1 \ / / / \ \ 3 2 1 1 3 2 / / \ \ 2 1 2 3题解搜索二叉树(BST)的定义若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。给点一个数,去构造BST.[1, 2, 3]可以把这个数列做左子树和右子树的划分:[1] [2, 3][1, 2] [3][1, 2] [2, 3] 又可以做左子树和右子树的划分.这是一个递归的过程.把递归的结果构造起来,即可成为答案./** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */class Solution { public List<TreeNode> generateTrees(int n) { if (n == 0) return new ArrayList<>(); return generateBST(1, n); } private List<TreeNode> generateBST(int left, int right) { List<TreeNode> res = new LinkedList<>(); if (left > right) { // 划分不到的时候,这时候填null. res.add(null); return res; } for (int i = left; i <= right; i++) { List<TreeNode> leftTrees = generateBST(left, i - 1); List<TreeNode> rightTrees = generateBST(i + 1, right); for (TreeNode leftTree : leftTrees) { for (TreeNode rightTree : rightTrees) { // 注意,每个循环都要构造新的节点,不能在for 循环外面生成. TreeNode root = new TreeNode(i); root.left = leftTree; root.right = rightTree; res.add(root); } } } return res; }}如果只需要数目,不需要生成具体的BST的话,只要能求出左子树有几种构造,右子树有几种构造,就可以最终确定.而确定左子树和右子树的问题的时候,又可以划分为子问题.eg:求 [1,2,3,4] 依赖于:[1,2,3] [2,3,4]又依赖于:[1,2] [2,3] [3,4]的构造有几种.class Solution { public int numTrees(int n) { int[] res = new int[n + 2]; res[0] = 1; res[1] = 1; // 没有左子树和右子树 res[2] = 2; for (int i = 3; i <= n; i++) { // 从3求到n for (int j = 1; j <= i; j++) { // 求解过程中,需要依赖于之前的解,状态转移方程为每一种划分的左子树和右子树的构造方法乘积. res[i] += res[j - 1] * res[i - j]; } } return res[n]; }} ...

February 16, 2019 · 2 min · jiezi

ES6 Proxy的学习与理解

问题前一段时间在字节跳动时聊到了Proxy。起因是问道Vue中数据绑定的实现,回答通过设置setter和getter实现,问这样有什么缺点,答在对对象的属性的监控方面存在瑕疵,例如通过直接设置数组下标进行赋值,或者对对象直接进行修改,是无法观察到的,必须使用Vue.set添加,或者使用Array.prototype.push等方法。面试官介绍说在Vue3中已经通过Proxy解决了这个问题。Proxy是ES6中添加的内置对象,和Reflect配合功能十分强大。正好今天看到一个问题。理解根据MDN的文档,Proxy是对原对象的包装。可以包装的内容包括一系列get、set等,值得注意的是getPrototypeOf同样是一种可以拦截的操作。同时,对于未定义的操作保持原结果。在instanceof的页面,可以看到如下示例function C() {}function D() {}var o = new C();// true, because: Object.getPrototypeOf(o) === C.prototypeo instanceof C;那么,在上面那个问题中,既然未定义proxy的getPrototypeOf,那它就该与原对象保持一致。使用以下代码进行验证:Object.getPrototypeOf(proxy) === Array.prototype //true进一步思考那么,是不是对于一切行为,在不做任何拦截的情况下,就能保证与目标对象的行为完全一致呢?很显然,这是不可能的。例如a = {}b = new Proxy(a, {})console.log(a === b) //false以及this的指向问题(案例来自阮一峰文章)const target = { m: function () { console.log(this === proxy); }};const handler = {};const proxy = new Proxy(target, handler);target.m() // falseproxy.m() // true虽然大部分情况下这应该不会成为大的障碍,但遇到错误的时候可以从这里入手寻找问题。

February 16, 2019 · 1 min · jiezi

刷前端面经笔记(十一)

1.栈的压入和弹出输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列5,4,3,2,1或3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。function IsPopOrder(pushV,popV){ if(pushV.length === 0) return false; var stack = []; // 模拟栈 for(var i = 0, j = 0; i < pushV.length;){ stack.push(pushV[i]); i += 1; // 压入栈的值需要被弹出 while(j < popV.length && stack[stack.length-1] === popV[j]){ stack.pop(); j++; if(stack.length === 0) break; } } return stack.length === 0;}2.利用栈模拟队列思路:对栈A添加数据。如果栈B为空,循环将栈A中内容弹出放入栈B,并弹出栈B最后一项如果栈B不为空,则直接弹出栈B的最后一项var stackA = [];var stackB = [];function push(node){ stackA.push(node);}function pop(){ if(!stackB.length){ while(stackA.length){ stackB.push(stackA.pop()); } } return stackB.pop();}3.连续最长不重复字符串在一个字符串中找出连续的不重复的最大长度的字符串,解决这类问题的思路:利用循环叠加字符串,直到出现重复为止每一次叠加,记录下来最大长度的字符串// 连续最长不重复字符串function getMaxLenStr(str) { var cur = []; var maxLenStr = ‘’; for(var i = 0; i < str.length; i++) { if(!cur.includes(str[i])) { cur.push(str[i]); } else { cur = []; // 置为空 cur.push(str[i]); } // 存储最大长度的字符串 if(maxLenStr.length < cur.length) { maxLenStr = cur.join(’’); } } return maxLenStr;}getMaxLenStr(‘ababcabcde’); // abcde4.求一个数组当中,连续子向量的最大和。function FindGreatestSumOfSubArray(arr) { let sum = arr[0]; let max = arr[0]; for(let i = 1; i < arr.length; i++) { if(sum < 0) { sum = arr[i]; }else{ sum += arr[i]; } // 记录最大值 if(max < sum) { max = sum; } } return max;} 5.给定一个编码字符,按编码规则进行解码,输出字符串编码规则:coount[letter] ,将letter的内容count次输出,count是0或正整数,letter是区分大小写的纯字母。实例:const s= 3[a]2[bc]; decodeString(s); // 返回 ‘aaabcbc’const s= 3[a2[c]]; decodeString(s); // 返回 ‘accaccacc’const s= 2[ab]3[cd]ef; decodeString(s); // 返回 ‘ababcdcdcdef’思路:使用栈这种数据结构,如果push的内容为‘]’,则循环pop字符,直到碰到’[‘,然后将pop出来的字符串按规则整理后,重新push进栈中,最后将栈内的内容拼接成字符串输出即可。function decodeString(str) { let stack = []; // 存储字符串的栈 for (let i = 0; i < str.length; i++) { let cur = str[i]; if (cur !== ‘]’) { stack.push(cur); } else { // 弹出 let count = 0; let loopStr = []; let popStr = ‘’; while ((popStr = stack.pop()) !== ‘[’) { loopStr.unshift(popStr); } count = stack.pop(); // 添加结果 let item = ‘’; for (let i = 0; i < count; i++) { item += loopStr.join(’’); } stack.push(…(item.split(’’))); } } return stack.join(’’);}6.[‘1’, ‘2’, ‘3’].map(parseInt) 的运行结果答案为:[1, NaN, NaN]解析:arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])这个callback一共可以接收三个参数,其中第一个参数代表当前被处理的元素,而第二个参数代表该元素的索引。而parseInt则是用来解析字符串的,使字符串成为指定基数的整数。- parseInt(string, radix)接收两个参数,第一个表示被处理的值(字符串),第二个表示为解析时的基数。parseInt(‘1’, 0) //radix为0时,且string参数不以“0x”和“0”开头时,按照10为基数处理。这个时候返回1parseInt(‘2’, 1) //基数为1(1进制)表示的数中,最大值小于2,所以无法解析,返回NaN- parseInt(‘3’, 2) //基数为2(2进制)表示的数中,最大值小于3,所以无法解析,返回NaNmap函数返回的是一个数组,所以最后结果为[1, NaN, NaN]7.自定义事件var content = document.querySelector(’.content’); // 自定义事件 var evt = new Event(‘custom’); var customEvt = new CustomEvent(‘customEvt’, { // 通过这个属性传递参数 detail: { name: ’tom’, age: 12 } }); content.addEventListener(‘custom’, (e) => { console.log(‘自定义事件被触发,无参数…’); console.log(e); }); content.addEventListener(‘customEvt’, (e) => { console.log(‘自定义事件被触发,有参数…’); console.log(e); console.log(e.detail); }); // 点击时触发这个自定义事件 content.addEventListener(‘click’, (e) => { content.dispatchEvent(evt); content.dispatchEvent(customEvt); }); ...

February 16, 2019 · 2 min · jiezi

刷前端面经笔记(十)

1.数组方法1)join()把数组上午所有元素放入一个字符串。元素通过指定的分隔符进行分隔。该方法只接收一个参数,用作分隔符的字符串,然后返回包含所有数组项的字符串,如果不给join()方法传入任何值,则使用逗号作为分隔符。var a = [1,2,3];console.log(a.join());//‘1,2,3’console.log(a.join(’ ‘));//‘1 2 3’console.log(a.join(’’));//‘123’var b = new Array(10);b.join(’-’);//’———’,9个连字符组成的字符串注意:如果join()方法的参数是undefined,标准浏览器以逗号为分隔符返回字符串,而IE7-浏览器以"undefined"为分隔符返回字符串;如果数组中某一项的值是null或者undefined,则该值在join()方法返回的结果中以空字符串表示。2)push()方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并且返回修改后数组的长度。var a = [];console.log(a,a.push(1));//[1] 1console.log(a,a.push(‘a’));//[1,‘a’] 2console.log(a,a.push(true, {}));//[1,‘a’,true,{}] 4console.log(a,a.push([5,6]));//[1,‘a’,true,{},[5,6]] 53)pop()方法从数组末尾移除最后一项,减少数组的length,然后返回移除的项。var a = [‘a’, ‘b’, ‘c’];console.log(a,a.pop()); // [‘a’, ‘b’] ‘c’注意:给pop参数传其他数字不起作用,也不报错。还是只删除最后一项;对空数组使用pop()方法,不会报错,而是返回undefined4)shift()方法移除数组中的第一个项并返回该项,同时数组的长度减1var a = [‘a’, ‘b’, ‘c’];console.log(a,a.shift());//[‘b’, ‘c’] ‘a’var arr6 = [1];console.log(arr6,arr6.shift()); //[] 1注意:对空数组使用shift()方法,不会报错,而是返回undefined5)unshift()方法在数组前面添加任意个项并返回新数组长度。var a = [‘a’, ‘b’, ‘c’];console.log(a,a.unshift(‘x’)); //[‘x’, ‘a’, ‘b’, ‘c’] 4注意:当传入多个参数时,是一次性插入。最终的数组中插入的元素的顺序和它们在参数列表中的 顺序一致;在IE-7浏览器中,unshift()方法的返回值总是undefined6)reserve()方法用于反转数组的顺序,返回经过排序之后的数组;而原来数组的顺序也发生改变。var array = [1,2,4,3,5];console.log(array,array.reverse());//[5,3,4,2,1] [5,3,4,2,1]var array = [‘str’,true,3];console.log(array,array.reverse());//[3,true,‘str’] [3,true,‘str’]7)sort()按照字符编码的顺序进行排序。sort()方法会调用每个数组项的toString()方法,然后比较得到的字符串排序,返回经过排序之后的数组,而原数组顺序也发生改变。var array = [2,1,4,3,5];console.log(array,array.sort());//[1,2,3,4,5] [1,2,3,4,5]var array = [‘3str’,3,2,‘2’];console.log(array,array.sort());//[2, “2”, 3, “3str”] [2, “2”, 3, “3str”]注意:如果数组包含undefined元素,它们会被排到数组的尾部;arrayObject.sort(sortby) 参数可选。规定排序顺序。必须是函数。比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回 0,如果第一个参数应该位于第二个之后则返回一个正数。 8)concat()方法基于当前数组中的所有项创建一个新的数组,先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。所以concat()不影响原数组。// 如果不给concat()方法传递参数时,它只是复制当前的数组;var arr = [1,2,3];console.log(arr,arr.concat()); //[1,2,3] [1,2,3]// 如果参数是一个或多个数组,则该方法会将这些数组中的每一项都添加到结果数组中;console.log(arr,arr.concat([6,7,8],[77,33,44]));//[1, 2, 3] [1, 2, 3, 6, 7, 8, 77, 33, 44]var arr1 = [4,5,6];console.log(arr,arr.concat(arr1)); //[1,2,3] [1,2,3,4,5,6]// 如果传递的值不是数组,这些值就会被简单地添加到结果数组的末尾。console.log(arr,arr.concat(4,5));//[1,2,3] [1,2,3,4,5]console.log(arr,arr.concat(4,[5,[6,7]]));//[1,2,3] [1, 2, 3, 4, 5, [6,7]]浅拷贝如果不提供参数,concat()方法返回当前数组的一个浅拷贝。// 该方法实际只复制了数组的第一维。// 数组第一维存放的是第二维的引用,而第二维才是实际存放他们的内容var numbers = [1,2];var newNumbers = numbers.concat();console.log(numbers,newNumbers);//[1,2] [1,2]numbers[0] = 0;console.log(numbers,newNumbers);//[0,2] [1,2]var numbers = [[1,2]];var newNumbers = numbers.concat();console.log(numbers,newNumbers);//[[1,2]] [[1,2]]numbers[0][0] = 0;console.log(numbers,newNumbers);//[[0,2]] [[0,2]]9)slice()方法基于当前数组中的一个或多个项创建一个新数组,接受一个或两个参数,最后返回新数组,所以slice()不影响原数组。slice(start,end)方法需要两个参数start和end,返回这个数组从start位置到end位置(不包含)的一个子数组,左闭右开。注意:a.如果end为undefined或不存在,则返回从start位置到数组结尾的所有项;b.如果没有参数,则返回原数组,即返回当前数组的一个浅拷贝;10)splice()方法用于删除原数组的一部分成员,并可以在被删除的位置添加入新的数组成员,该方法会改变原数组。splice()返回一个由删除元素组成的数组,或者如果没有删除元素就返回一个空数组splice(start,number…)的第一个参数start指定了插入或删除的起始位置,第二个参数number指定了应该从数组中删除的元素的个数,如果后面还有更多的参数,则表示这些就是要被插入数组的新元素。11)indexOf(search,start)方法接收search和start两个参数,返回search首次出现的位置,如果没有找到则返回-1,start代表从start位置开始寻找。12)lastIndexOf(search,start)方法从右向左查找。接收search和start两个参数,返回search第一次出现的位置,如果没有找到则返回-113)reduce()方法需要两个参数,第一个是执行化简操作的函数,化简函数的任务就是用某种方法把两个值组合或化简为一个值,并返回化简后的值。var total = [0, 1, 2, 3].reduce(function(sum, value) { return sum + value;}, 0);// total is 6reduceRight()则从右到左执行对应的化简函数14)map()方法对数组中的每一项运行给定的函数,返回每次函数调用的结果组成的数组,map方法还可以接受第二个参数,表示回调函数执行时this所指向的对象。15)forEach()方法对数组中的每一项运行给定的函数,这个方法没有返回值。本质上和for循环迭代数组一样。如果需要有返回值,一般使用map方法。forEach()方法除了接受一个必须的回调函数参数,第二个参数还可以接受一个可选的上下文参数(改变回调函数里面的this指向)array.forEach(callback(currentValue, index, array){ //do something}, this)16)filter()方法对数组中的每一项运行给定的函数,返回该函数会返回true的项组成的数组。该方法常用于查询符合条件的所有数组项。filter()方法还可以接受第二个可选的上下文参数(改变回调函数里面的this指向)var arr= [1,10,20,30]var brr = arr.filter((item)=>{ return item>10;})//[20,30]17)some()方法对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。并且当且仅当数值中的所有元素调用判定函数都返回false,它才返回false注意:在空数组上调用some()方法会返回falseconst isBiggerThan10 = (element, index, array) => { return element > 10;}[2, 5, 8, 1, 4].some(isBiggerThan10); // false[12, 5, 8, 1, 4].some(isBiggerThan10); // true18)every()方法对数组中的每一项运行给定函数,如果函数对每一项都返回true,则返回true;只要有一项返回false,则返回false19)fill()方法,用一个固定值填充一个数组中起始索引到终止索引内的全部元素arr.fill(value, start, end)var numbers = [1, 2, 3]numbers.fill(1);// results in [1, 1, 1]20)find()方法返回数组中满足提供的测试函数的第一个元素的值 function isBigEnough(element) { return element >= 15; } [12, 5, 8, 130, 44].find(isBigEnough); // 13021)findIndex()方法返回数组中满足提供的测试函数的一个元素的索引function isBigEnough(element) { return element >= 15;}[12, 5, 8, 130, 44].findIndex(isBigEnough); //‘3'22)includes()方法用来判断一个数组是否包含一个指定的值,如果是,则返回true,如果没有则返回falselet a = [1, 2, 3];a.includes(2); // true a.includes(4); // false23)toLocaleString()方法返回一个字符串表示数组中的元素。数组中的元素将使用各自的toLocaleString方法转成字符串,这些字符串将使用一个特定语言环境的字符串(例如一个逗号",")隔开var number = 1337;var date = new Date();var myArr = [number, date, “foo”];var str = myArr.toLocaleString(); console.log(str); // 输出 “1,337,2019/2/15 下午8:32:24,foo"24)copyWithin(target,start,end)方法浅复制数组的一部分到同一数组的另一个位置25)Array.isArray()方法用于确定传递的值是否是一个Array Array.isArray([]) => true; Array.isArray({}) => false;26)Array.of()Array.of(7); // [7] Array.of(1, 2, 3); // [1, 2, 3]Array(7); // [ , , , , , , ]Array(1, 2, 3); // [1, 2, 3]27)Array.from()对伪数组或可迭代对象(包括arguments Array,Map,Set,String…)转换成数组对象语法 Array.from(arrayLike, mapFn, thisArg)arrayLike 想要转换成数组的伪数组对象或可迭代对象。mapFn (可选参数) 如果指定了该参数,新数组中的每个元素会执行该回调函数。thisArg (可选参数) 可选参数,执行回调函数 mapFn 时 this 对象。返回值 一个新的数组实例2.数组降维方法一:function flattenDeep(arr) { arr = "” + arr; // 或者arr = arr.toString(); arr = arr.split(","); arr = arr.map(Number) return arr; }flattenDeep([1, [[2],[3, [4]], 5]]);方法二:function flattenDeep(arr) { if(!Array.isArray(arr)) return [arr]; return arr.reduce((prev,cur) => { return […prev, …flattenDeep(cur)]; },[]);}flattenDeep([1, [[2], [3, [4]], 5]]);方法三:function flattenDeep(arr){ while(arr.some(item=>Array.isArray(item)){ arr = [].concat(…arr); } return arr;} ...

February 15, 2019 · 2 min · jiezi

被裁员后,我是如何成功找到了一份数据科学工作?

作者 Kristen Kehrer中文翻译 MikaCDA 数据分析师原创作品,转载需授权本文的目的为了告诉你们我是如何成功找到一份数据科学的工作。从被裁员到成功签约,这两个月对我来说即辛苦又紧张。我拥有统计学硕士学位,并且自2010年以来一直从事高级分析工作。如果你是该领域的新手,那么你的体验可能会有所不同,但希望本文能有所帮助。我们将介绍如何利用LinkedIn,搜寻各种职位申请,如何在同时提高你的技能,以及当收到offer时该如何协商。被裁员的第1天Vistaprint公司决定减少员工人数,很不幸我是其中一员。但我知道如今市场对数据科学家的需求很火,因此从被裁员的第1天起,我就很乐观。我收到了遣散费,这让我能够真正考虑接下来该怎么做。在我把头发染成亮粉色后的第4天,我碰巧被裁员了,这真是很无奈。在被裁员后的第4天,我顶着一头粉色的头发。这是我的儿子哈利,他当时差不多3个月。实际上,之后我的第一场面试就是顶着一头粉色头发,面试官很喜欢。但是,之后我决定把头发染回之前的颜色,以便之后的求职。做的第一件事在LinkedIn上,招聘人员经常会联系我。我一般都会回复。如果你刚进入这个领域,你可能不会在LinkedIn邮件中收到招聘人员的消息,提出这一点的目的在于,你之后在职业生涯中可以关注这方面。如今我正在求职,我所做的第一件事就是浏览这个清单,给每个人留言:“你好,我正在求职中。如果你们有任何空缺的职位那就太好了,我们可以聊聊。“有很多人回复说他们有空缺的职位,但是在与他们交谈之后,我发现这并不适合我。除了去联系来找我的招聘人员之外,我还进行了谷歌搜索(以及LinkedIn搜索),寻找分析领域的相关招聘人员。与职业导师第一次见面在被裁员之后,Vistaprint为我安排了职业导师。她教给我的信息非常有用,这让我在之后的职业生涯都受益匪浅。我的职业导师是来自Transition Solutions的Joan Blake。在我们的第一次会面时,我带来了我的简历,我们谈论了之后我想找哪方面的工作。由于我的简历和LinkedIn在过去帮我找到了工作,她并没有对简历进行太大的修改,但把我的专业技能和经验放在顶部,把学历背景放在底部。并且我们把简历的篇幅尽量控制在一页以内。这是我的简历:我还在简历中附上了求职信。这让我能够有机会明确地表明,我的情况很符合他们的工作描述。我列了一个电子表格,当中包含了我所申请的所有公司。在表格中,包含了以下信息:公司名称;申请的日期;收到回复的日期;招聘经理的姓名等。这帮助我掌握目前求职的情况和进度。求职申请对我申请的每个工作,我都会在LinkedIn进行搜索。查看我的人脉网络中是否有人目前在这家公司。如果有的话,我会让他们知道我在申请,因为如今很多公司都提供推荐奖金。我大概会这么说:你好,Michelle。我正在申请XX公司的数据科学家职位。你愿意帮我引荐一下吗?如果我认识的人中,没有人在这家公司,那么我会试着找到该职位的招聘经理。可能是“数据科学与数据分析”主管(或副总裁)这类头衔。我给招聘经理发的信息如下所示:你好,Sean。我对远程数据科学的职位感兴趣。我有统计学硕士学位,以及7年的建模经验。我很擅长使用SQL,以及用R进行建模,并且对Python有一定的了解。我很期待有机会与你聊聊,我想谈谈我将如何通过统计方法为公司提供有效的分析见解并创造价值。Kristen大多数人都会回复。当我告诉职业导师我在LinkedIn上求职所获得成功后,她为此感到有些惊讶。开始面试和电话面试电话面试基本大同小异,有些电话面试更紧张一些,有些耗时更长,基本都在半小时左右,通常会是HR。既然是与HR进行沟通,那么不用涉及太深的技术问题,你只希望能够通过电话面试,并试着与招聘经理约定时间进行面试。介绍一下自己:这里HR只想大致了解你,以及你的经历。我的介绍如下:我是一名数据科学家,拥有7年的统计和分析经验,可以解决各行业的业务问题。我能够熟练使用SQL,在R中进行模型构建,并且目前正在学习Python。你想要做什么?我会确保我想做的基本与职位描述相符合。我可能会说“ 我期待不断学习新工具和新技术。我希望能够处理有意思的问题,从而带来商业价值“。你的薪资要求是怎样的?如果可以的话,尽量避免这个问题,你会被问到,但是尝试用不同的角度回答。你可以回答:“我过去的薪资比较符合我的期望,我相信(公司名称)会付出相应的薪资,你怎么看这个职位的薪资范围呢?”他们会有该职位的薪资范围,但他们可能会告诉你他们没有。大多数时候我会给出我的薪资期望,但这并不意味着当收到offer时你无法进行协商。收到offer很棒,你马上就要拿到offer了。这时,你可以联系发offer的公司,询问“我收到通知说我能拿到offer,有什么办法可以让流程加快吗?”我向两家公司提过这个问题。其中一个加快了流程,给我发了额外offer。协商你拿到了offer,现在应该进行协商了。只有很少一部分人会就薪资进行协商,女性更少。一定要协商!当你了解了薪资、休假时间、以及福利信息,你可以这么说:非常感谢您的offer,我很感激。我希望能提高一些薪资。然后等待回应,保持积极的心态。他们可能会说,需要把这些信息提给招聘经理。谢谢。我会花一些时间了解福利的内容。期待再和您协商,我感觉我们能达成一致。然后就结束谈话,并且定下下次协商的具体时间,最后让他们知道你很高兴与他们进行交流,保持积极的态度。就这样我成功地获得了心仪的工作,并在一周后开始上班。我感觉特别的欣喜,经过多次面试我终于找到了适合自己的工作,这一切都是值得的。结语有针对性的求职信,并直接在公司网站上申请会大大提高求职的响应率。同时,你可以有效地利用LinkedIn联系招聘人员,直接进行交流。同时在这个过程中,我的表达能力和自信心都得到了很大的提升。最后希望你能成功找到心仪的工作。

February 15, 2019 · 1 min · jiezi

数据库索引融会贯通

索引的各种规则纷繁复杂,不了解索引的组织形式就没办法真正地理解数据库索引。通过本文,你可以深入地理解数据库索引在数据库中究竟是如何组织的,从此以后索引的规则对于你将变得清清楚楚、明明白白,再也不需要死记硬背。顺畅地阅读这篇文章需要了解索引、联合索引、聚集索引分别都是什么,如果你还不了解,可以通过另一篇文章来轻松理解——数据库索引是什么?新华字典来帮你。这篇文章是一系列数据库索引文章中的第二篇,这个系列包括了下面四篇文章:数据库索引是什么?新华字典来帮你 —— 理解数据库索引融会贯通 —— 深入20分钟数据库索引设计实战 —— 实战数据库索引为什么用B+树实现? —— 扩展这一系列涵盖了数据库索引从理论到实践的一系列知识,一站式解决了从理解到融会贯通的全过程,相信每一篇文章都可以给你带来更深入的体验。索引的组织形式通过之前的内容,我们已经对数据库索引有了相当程度的抽象了解,那么在数据库中,索引实际是以什么样的形式进行组织的呢?同一张表上的多个索引又是怎样分工合作的呢?目前绝大多数情况下使用的数据库索引都是使用B+树实现的,下面就以MySQL的InnoDB为例,介绍一下数据库索引的具体实现。聚集索引下面是一个以B+树形式组织的拼音索引,在B+树中,每一个节点里都有N个按顺序排列的值,且每个值的中间和节点的头尾都有指向下一级节点的指针。在查找过程中,按顺序从头到尾遍历一个节点中的值,当发现要找的目标值恰好在一个指针的前一个值之前、后一个值之后时,就通过这个指针进入下一级节点。当最后到达叶子节点,也就是最下层的节点时,就能够找到自己希望查找的数据记录了。在上图中如果希望找到险字,那么我们首先通过拼音首字母在根节点上按顺序查找到了X和Y之间的指针,然后通过这个指针进入了第二级节点···, xia, xian, xiang, ···。之后在该节点上找到了xian和xiang之间的指针,这样就定位到了第519页开始的一个目标数据块,其中就包含了我们想要找到的险字。因为拼音索引是聚集索引,所以我们在叶子节点上直接就找到了我们想找的数据。非聚集索引下面是一个模拟部首索引的组织形式。我们由根节点逐级往下查询,但是在最后的叶子节点上并没有找到我们想找的数据,那么在使用这个索引时我们是如何得到最终的结果的呢?回忆之前字典中“检字表”的内容,我们可以看到,在每个字边上都有一个页码,这就相当于下面这一个索引中叶子节点上险字与院字中间的指针,这个指针会告诉我们真正的数据在什么地方。下图中,我们把非聚集索引(部首索引)和聚集索引(拼音索引)合在一起就能看出非聚集索引最后到底如何查找到实际数据了。非聚集索引叶子节点上的指针会直接指向聚集索引的叶子节点,因为根据聚集索引的定义,所有数据都是按聚集索引组织存储的,所以所有实际数据都保存在聚集索引的叶子节点中。而从非聚集索引的叶子节点链接到聚集索引的叶子节点查询实际数据的过程就叫做——回表。全覆盖索引那么如果我们只是想要验证险字的偏旁是否是双耳旁“阝”呢?这种情况下,我们只要在部首索引中阝下游的叶子节点中找到了险字就足够了。这种在索引中就获取到了SQL语句中需要的所有字段,所以不需要再回表查询的情况中,这个索引就被称为这个SQL语句的全覆盖索引。在实际的数据库中,非聚集索引的叶子节点上保存的“指针”就是聚集索引中所有字段的值,要获取一条实际数据,就需要通过这几个聚集索引字段的值重新在聚集索引上执行一遍查询操作。如果数据量不多,这个开销是非常小的;但如果非聚集索引的查询结果中包含了大量数据,那么就会导致回表的开销非常大,甚至超过不走索引的成本。所以全覆盖索引可以节约回表的开销这一点在一些回表开销很大的情况下就非常重要了。范围查询条件上图是一个联合索引idx_eg(col_a, col_b)的结构,如果我们希望查询一条满足条件col_a = 64 and col_b = 128的记录,那么我们可以一路确定地往下找到唯一的下级节点最终找到实际数据。这种情况下,索引上的col_a和col_b两个字段都能被使用。但是如果我们将查询条件改为范围查询col_a > 63 and col_b = 128,那么我们就会需要查找所有符合条件col_a > 63的下级节点指针,最后不得不遍历非常多的节点及其子节点。这样的话对于索引来说就得不偿失了,所以在这种情况下,数据库会选择直接遍历所有满足条件col_a > 63的记录,而不再使用索引上剩下的col_b字段。数据库会从第一条满足col_a > 63的记录开始,横向遍历之后的所有记录,从里面排除掉所有不满足col_b = 128的记录。这就是范围条件会终止使用联合索引上的后续字段的原因。

February 14, 2019 · 1 min · jiezi

PHP面试常考内容之面向对象(3)

PHP面试专栏正式起更,每周一、三、五更新,提供最好最优质的PHP面试内容。继上一篇“PHP面试常考内容之面向对象(2)”发表后,今天更新面向对象的最后一篇(3)。需要(1),(2)的可以直接点文字跳转获取。PHP面试常考内容之面向对象(1)整个面向对象文章的结构涉及的内容模块有:一、面向对象与面向过程有什么区别?二、面向对象有什么特征?三、什么是构造函数和析构函数?四、面向对象的作用域范围有哪几种?五、PHP 中魔术方法有哪些?六、什么是对象克隆?七、this、self和parent的区别是什么?八、抽象类与接口有什么区别与联系?九、PHP面向对象的常考面试题讲解关于PHP面向对象的内容将会被分为三篇文章进行讲解完整块内容,第一篇主要讲解一到四点内容,第二篇主要讲解五到八的内容,第三篇围绕第九点进行讲解。以下正文的内容都来自《PHP程序员面试笔试真题解析》书籍,如果转载请保留出处:九、PHP面向对象的常考面试题讲解【真题1】 什么是面向对象?其主要特征是什么?答案:面向对象是程序的一种设计方式,它是一种对现实世界的理解和抽象的方法,它可以提高程序的重用性,让程序结构更加清晰。面向对象的主要特征为:封装、继承、多态。【真题2】 可以获得对象的类名的函数是( )。A.get_class_name B.get_class C.class_exists D.get_class_vars答案:B。PHP中获取对象的类名函数是get_class()。所以,选项B正确。对于选项A,不存在该方法。所以,选项A错误。对于选项C,class_exists()函数可以检查类是否存在。所以,选项C错误。对于选项D,get_class_vars()函数可以获取类的默认属性。所以,选项D错误。所以,本题的答案是B。【真题3】 请简单说明PHP的垃圾收集机制。答案:在PHP中,当没有任何变量指向该对象时,该对象变为垃圾将会在内存中被销毁,可以防止内存溢出。内存中对变量有引用计数,当计数到0时变量被销毁。【真题4】多态与方法重写有什么关系?答案:多态是指一个类可以被多个类继承,每个子类都可以对父类方法进行重写,每个类里的同名方法可以实现不同的功能从而表现出多种形态,它增强了软件的灵活性和重用性。重写是子类对父类中的方法进行改写。它们的关系是重写让类具备多态性。【真题5】面向对象几大原则是什么?答案:面向对象存在五大基本原则,分别是:单一职责原则、开放封闭原则、替换原则、依赖原则、接口分离原则等。(1)单一职责原则所谓单一职责原则,即一个类最好只做一件事。为了提高内聚性减少引起变化,单一原则是低耦合、高内聚的面向原则上的引申。(2)开放封闭原则软件的功能应该是可扩展的,尽可能减少修改,因为修改程序,可能会对原来的程序造成影响。虽然建议尽可能不修改程序,但是允许通过添加功能来减少修改。(3)替换原则只有子类能够替换基类,在继承机制的约束规范中,子类替换基类时,可以保证运行期内识别子类,保证继承复用。(4)依赖倒置原则高层模块不依赖底层模块,二者都依赖于抽象,抽象不依赖于实体,而实体依赖于抽象。模块间的依赖是通过抽象方法发生的,实现类中不发生直接的依赖关系,而依赖关系是通过接口或抽象类产生的。即接口或抽象类不依赖于实现类,而实现类依赖于接口和抽象类。这种依赖倒置原则可以有效地减少类之间的耦合性,提高系统的稳定性,减少并发引起的风险,提高代码的可读性和可维护性。(5)接口隔离原则建议开发使用多个小的、专门的接口,避免使用一个大的总接口。即每一个功能有一个专门的功能接口,需要用到才调用,不需要全部功能汇总到一个接口,这样可以提高代码的灵活性,降低类之间的耦合性,提高稳定性。【真题6】以下有关PHP高级特性的说法中,正确的是( )。A.可以定义一个类去实现预定义接口Iterator,然后就能像访问数组一样访问这个类创建的对象B.spla_utoload_register()提供了一种更加灵活的方式来实现类的自动加载,不再建议使用_autoload()函数C.PHP在对象中调用一个不可访问方法时,invoke()方法会被自动调用D.匿名函数也叫闭包函数,常用作回调函数参数的值,但是不能作为变量的值来使用答案:B。对于选项A,只有ArrayAccess能够提供像访问数组一样访问这个对象的接口,不能定义一个类或预定义接口Iterator去实现这个功能。所以,选项A错误。对于选项B,因为可以通过spla_utoload_register()函数创建autoload函数的队列,按定义顺序逐个执行,比_autoload()函数只可以定义一次使用更方便,所以不建议使用_autoload()函数。所以,选项B正确。对于选项C,_call方法是在创建一个类实例化后就可以直接调用对象使用,当调用的方法不可访问或没有权限访问时,会自动调用_call方法。所以,选项C错误。对于选项D,匿名函数是可以赋值给变量的。所以,选项D错误。所以,本题的答案是B。【真题7】__autoload()函数的工作原理是什么?答案:使用这个魔术函数的基本条件是,类文件的文件名要和类的名字保持一致。当程序执行到实例化某个类时,如果在实例化前没有引入这个类文件,那么就自动执行__autoload()函数。这个函数根据实例化的类名去查找这个类的路径,一旦找到这个类后就会通过执行include或require载入该类,从而保证程序能够继续执行。如果没有找到,那么报错。【真题8】以下关于PHP命名空间的说法中,不正确的是( )。A.访问任意全局类、函数或常量,都可以使用完全限定名称,例如strlen()或Exception或INI_ALLB.关键字 namespace可用来显式访问当前命名空间或子命名空间中的元素,它等价于类中的 this 操作符 C.任意合法的PHP代码都可以包含在命名空间中,但只有三种类型的代码受命名空间的影响,它们是类、函数和常量D.常量__NAMESPACE__的值是当前命名空间名称的字符串。如果是在全局中,那么它不包括任何命名空间中的代码,本身是一个空字符串答案:B。namespace关键字是用来声明命名空间用的,它并不能等价于this操作符的功能。所以,选项B说法不对。所以,本题的答案是B。【真题9】以下代码的运行结果是( )。<?php class Person{ var $name; } $a = new Person(); $a->name = “张三”; $b = $a; $b->name = “李四”; echo $a->name;?>A.张三 B.李四 C.Null D.什么都没有答案:B。首先$a实例化Person类,把张三赋值给类内的变量name,把对象张三的值给了$b,通过$b去修改类内name的值为李四,所以最后输出Person类内的name,输出得到结果李四。所以,选项B正确,选项A、选项C、选项D错误。所以,本题的答案是B。【真题10】下面说法错误的是( )。A.如果一个类的成员前面有访问修饰符private,那么这些成员不能被继承,在类的外部不可见。但如果成员被指定为protected和public,那么可以被继承,在类的外部也是可见的B.PHP5中,final关键字可以禁止继承和重载C.PHP5中,析构函数的名称是__destruct(),并且不能有任何参数D.继承接口的类必须实现接口中声明的所有方法,在PHP中,如果继承接口的类没有实现接口中的方法,那么将会产生一个致命错误答案:A。对于选项A,private修饰的成员是不可以被继承的,protected的成员是可以被继承的,但是在外部不可见,选项A说法错误,所以,选项A正确。对于选项B,final关键字的方法是禁止被继承和重载的,选项B说法正确,所以选项B错误。对于选项C,析构函数不能有参数,选项C说法正确,所以,选项C错误。对于选项D,继承接口的类没有实现接口中的方法是会产生错误的,选项D说法正确,所以,选项D错误。所以,本题的答案是A。自己整理了一篇“个人编程6年的心得——如何学好编程?”的文章,关注公众号:“琉忆编程库”,回复:“学好”,我发给你。【真题11】 定义类成员的访问权限控制符有哪些?默认修饰符是什么?答案:类成员的访问修饰符有public、private、protected,主要用来修饰类中的成员属性和方法。public是公共类型,允许在类的内部或子类中使用,也可以在类外部被访问。private是私有类型,只能在类的内部被使用,不能被继承使用。protected是保护类型,只能在类的内部或子类中使用。如果不使用public、private、protected等关键字修饰方法或属性,那么可以使用var关键字,它的功能等同于public,可以在类内或类外被调用,也可以被继承使用。其中,PHP默认的修饰符是public,即公有类型。类前面只能加final、abstract关键字,被final修饰的属性或方法是不能被继承的,只能在当前类中使用,abstract定义的类或方法,叫作抽象类或抽象方法。属性前面:必须有访问修饰符(private,protected,public,var)。【真题12】 PHP的魔术方法包含哪些(越多越好)?在什么情况下被自动调用?答案:PHP可用的魔术方法会在特定情况下被自动调用,但是前提是特定的条件被触发,并且这些魔术方法可以在类中作为方法。PHP的魔术方法有:1)_construct():构造函数,创建对象时自动被调用。2)_destruct():析构函数,对象的所有引用都被删除或者当对象被显式销毁时执行。3)__clone():克隆函数,调用clone方法时自动调用。4)__set():当程序试图写入一个不存在或不可见的成员变量时自动调用。该函数在类中定义时必须有两个参数:变量名和变量值。5)__get():当程序调用一个未定义或不可见的成员变量时自动调用__get()来读取变量值。定义时必有有一个参数:变量名。6)__call():当程序试图调用不存在或不可见的成员方法时,自动调用__call()。__call()方法一般用于监视错误的方法调用。为了避免当调用的方法不存在时产生错误,可以使用__call()方法来避免。该方法包含两个参数:方法名和方法参数。其中,方法参数以数组形式存在。7)__sleep():使用serialize()实现序列化对象时,先调用该方法,可以用来清除对象并返回一个该对象中所有变量的数组。8)__wakeup():使用unserialize()还原一个被序列化的对象时,先执行该方法,恢复在序列化中可能丢失的数据库连接及相关工作。9)__toString():当使用echo或print输出对象时,将对象转化为字符串。10)__autoload():调用未被实例化的类时,自动调用,在指定路径下查找和该类名称相同的文件。【真题13】 $this、self和parent这三个关键词分别代表什么?在哪些场合下使用?答案:$this表示当前对象,在当前类中可以通过->符调用类内的属性和方法。self表示当前类,只能通过self的形式(“self::方法或属性”)调用类内的方法。parent表示当前类的父类,调用父类内的方法只能使用“parent::”形式调用。【真题14】下面关于面向对象的描述中,错误的是( )。A.父类的构造函数与析构函数不会自动被调用B.成员变量需要用public、protected、private修饰,在定义变量时不再需要var关键字C.父类中定义的静态成员,不可以在子类中直接调用D.包含抽象方法的类必须为抽象类,抽象类不能被实例化答案:A。对于选项A,子类继承父类,如果子类没有构造函数和析构函数,那么实例化子类时会自动调用父类的构造函数和析构函数;但如果子类只有构造函数没有析构函数时,那么实例化子类时,自动调用的是子类的构造函数,销毁对象时调用父类的析构函数;如果子类没有构造函数只有析构函数,那么实例化子类时会自动调用父类的构造函数,销毁对象时调用子类的析构函数,选项A说法不完全。所以,选项A正确。对于选项B,成员变量使用了public、protected、private修饰定义变量时是不需要var关键字的,选项B说法正确。所以,选项B错误。对于选项C,父类中的静态成员,子类中是不可以直接访问的,选项B说法正确。所以,选项C错误。对于选项D,一个包含抽象方法的类必须是抽象类,并且抽象类不能被实例化。选项D说法正确。所以,选项D错误。所以,本题的答案是A。【真题15】 在PHP中,如果派生类与父类有相同名字的函数,那么派生类的函数会替换父类的函数,有如下程序代码:<?php class A { function disName(){ echo “Picachu”; } } class B extends A { var $tmp=’’; function disName(){ echo “Doraemon”; } } $cartoon = new B; $cartoon->disName();?>上述代码的运行结果为( )。A.tmp B.Picachu C.disName D.Doraemon E.无输出答案:D。当派生类继承父类时,如果通过实例化一个派生类的对象来访问对象的方法时,派生类不存在父类中的方法,那么执行父类中的方法。如果派生类和父类存在相同名字的方法,那么派生类的方法会覆盖父类方法,执行派生类的方法。所以,本题中可以执行派生类的disName()方法。所以,选项D正确,选项A、选项B、选项C、选项E错误。所以,本题的答案是D。【真题16】什么是抽象类和接口?抽象类和接口有什么不同和相似的地方?答案:被关键字abstract修饰的类叫作抽象类,抽象类是不能被实例化的。被abstract修饰的方法为抽象方法,一个类只要有一个抽象方法,这个类一定是抽象类。接口是通过关键字interface来定义的,可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体实现。PHP类只支持是单重继承的,但通过接口可以实现PHP类的多重继承。抽象类和接口的不同和相似的地方如下所示。1)抽象类是一种不能被实例化的类,只能作为其他类的父类来使用。2)抽象类是通过关键字abstract来声明的。3)抽象类与普通类相似,都包含成员变量和成员方法,两者的区别在于,抽象类中至少要包含一个抽象方法。4)抽象方法没有方法体,该方法就是要被子类重写的。5)抽象方法的格式为:abstract function abstractMethod()。6)因为PHP中只支持单重继承,所以如果想实现多重继承,那么就要使用接口。也就是说,子类可以实现多个接口。7)接口类是通过interface关键字来声明的,接口类中的成员变量和方法都是public的,可以不用显式地使用public来修饰。8)接口中的方法没有方法体。接口中的方法就是要被子类继承实现的。9)子类继承抽象类使用extends关键字,子类实现接口使用implements关键字。【真题17】用类编程实现:Stu类中有两个私有属性name和sex,有两个公有方法,setName()和setSex()参数自定,方法可实现对两个私有属性进行修改。在实例化类时要求对私有属性能初始化。答案:实现代码如下:<?php Class Stu{ private $name; private $sex; public function setName($name){ $this->name = $name; } public function setSex($sex){ $this->sex = $sex; } }?>【真题18】 假如有一个类Person,实例化(new)一个对象$p,那么以下使用对象$p调用Person类中的getInfo方法的写法中,正确的是( )。A.$p=>getInfo(); B.$this->getInfo(); C.$p->getInfo(); D.$p::getInfo();参考答案:C。分析:“::”主要用于访问类中的静态成员,“->”主要用于访问类中的变量和方法,“=>”主要应用在数组中的key和value映射时使用。所以,选项A、选项B、选项D错误,选项C正确。【真题19】php中public、protected、private三种访问控制模式的区别是什么?参考答案:php中public、protected、private三种访问控制模式的区别如下:访 问 模 式 描 述public 共有,任何地方都可以访问protected 继承,只能在本类或子类中访问,在其他地方不能使用private 私有,只能在本类中访问,在其他地方不能使用【真题20】 在PHP面向对象中,下面关于final修饰符的描述中,错误的是( )。A.使用final标识的类不能被继承 B.在类中使用final标识的成员方法,在子类中不能被覆盖C.不能使用final标识成员属性 D.使用final标识的成员属性,不能在子类中再次定义参考答案:D。分析:因为final只能修饰类与方法,不能修饰类的属性。所以,选项D错误。PS:由于真题较多,仅罗列PHP面向对象笔试中经常遇到的20道考题!至此本周(2019-2-11 至 2019-2-15 的面向对象专题已更新完毕,以上的内容只是摘取了PHP面向对象中最常考的内容,个别内容没有罗列可以从原书中获取。)感谢大家的支持!预告:下周(2019-2.18 —— 2.22)更新“PHP面试常考内容之Memcache和Redis缓存的”专题,敬请期待。以上内容摘自《PHP程序员面试笔试真题解析》书籍,该书已在天猫、京东、当当等电商平台销售。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,将不断完善追求极致,感谢你们的支持。 ...

February 14, 2019 · 1 min · jiezi

中高级前端大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)

引言当下,正面临着近几年来的最严重的互联网寒冬,听得最多的一句话便是:相见于江湖~????。缩减HC、裁员不绝于耳,大家都是人心惶惶,年前如此,年后想必肯定又是一场更为惨烈的江湖厮杀。但博主始终相信,寒冬之中,人才更是尤为珍贵。只要有过硬的操作和装备,在逆风局下,同样也能来一波收割翻盘。博主也是年前经历了一番厮杀,最终拿到多家大厂的 offer。在闭关修炼的过程中,自己整理出了一套面试秘籍供自己反复研究,后来给了多位有需要的兄台,均表示相当靠谱,理应在这寒冬之中回报于社会。于是决定花点精力整理成文,让大家能比较系统的反复学习,快速提升自己。面试固然有技巧,但绝不是伪造与吹流弊,通过一段短时间沉下心来闭关修炼,出山收割,步入大厂,薪资翻番,岂不爽哉?????修炼原则想必大家很厌烦笔试和考察知识点。因为其实在平时实战中,讲究的是开发效率,很少会去刻意记下一些细节和深挖知识点,脑海中都是一些分散的知识点,无法系统性地关联成网,一直处于时曾相识的状态。不知道多少人和博主一样,至今每次写阻止冒泡都需要谷歌一番如何拼写。????。以如此的状态,定然是无法在面试的战场上纵横的。其实面试就犹如考试,大家回想下高考之前所做的事,无非就是 理解 和 系统性关联记忆。本秘籍的知识点较多,花点时间一个个理解并记忆后,自然也就融会贯通,无所畏惧。由于本秘籍为了便于记忆,快速达到应试状态,类似于复习知识大纲。知识点会尽量的精简与提炼知识脉络,并不去展开深入细节,面面俱到。有兴趣或者有疑问的童鞋可以自行谷歌下对应知识点的详细内容。????CSS1. 盒模型页面渲染时,dom 元素所采用的 布局模型。可通过box-sizing进行设置。根据计算宽高的区域可分为:content-box (W3C 标准盒模型)border-box (IE 盒模型)padding-boxmargin-box2. BFC块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。IE下为 Layout,可通过 zoom:1 触发触发条件:根元素positon: absolute/fixeddisplay: inline-block / tablefloat 元素ovevflow !== visible规则:属于同一个 BFC 的两个相邻 Box 垂直排列属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠BFC 中子元素不会超出他的包含块BFC 的区域不会与 float 的元素区域重叠计算 BFC 的高度时,浮动子元素也参与计算文字层不会被浮动层覆盖,环绕于周围应用:阻止margin重叠可以包含浮动元素 —— 清除内部浮动(清除浮动的原理是两个div都位于同一个 BFC 区域之中)自适应两栏布局可以阻止元素被浮动元素覆盖3.层叠上下文元素提升为一个比较特殊的图层,在三维空间中 (z轴) 高出普通元素一等。触发条件根层叠上下文(html)positioncss3属性flextransformopacityfilterwill-change-webkit-overflow-scrolling层叠等级:层叠上下文在z轴上的排序在同一层叠上下文中,层叠等级才有意义z-index的优先级最高4. 居中布局水平居中行内元素: text-align: center块级元素: margin: 0 autoabsolute + transformflex + justify-content: center垂直居中line-height: heightabsolute + transformflex + align-items: centertable水平垂直居中absolute + transformflex + justify-content + align-items5. 选择器优先级!important > 行内样式 > #id > .class > tag > * > 继承 > 默认选择器 从右往左 解析6.去除浮动影响,防止父级高度塌陷通过增加尾元素清除浮动:after / <br> : clear: both创建父级 BFC父级设置高度7.link 与 @import 的区别link功能较多,可以定义 RSS,定义 Rel 等作用,而@import只能用于加载 css当解析到link时,页面会同步加载所引的 css,而@import所引用的 css 会等到页面加载完才被加载@import需要 IE5 以上才能使用link可以使用 js 动态引入,@import不行8. CSS预处理器(Sass/Less/Postcss)CSS预处理器的原理: 是将类 CSS 语言通过 Webpack 编译 转成浏览器可读的真正 CSS。在这层编译之上,便可以赋予 CSS 更多更强大的功能,常用功能:嵌套变量循环语句条件语句自动前缀单位转换mixin复用面试中一般不会重点考察该点,一般介绍下自己在实战项目中的经验即可9.CSS动画transition: 过渡动画transition-property: 属性transition-duration: 间隔transition-timing-function: 曲线transition-delay: 延迟常用钩子: transitionendanimation / keyframesanimation-name: 动画名称,对应@keyframesanimation-duration: 间隔animation-timing-function: 曲线animation-delay: 延迟animation-iteration-count: 次数infinite: 循环动画animation-direction: 方向alternate: 反向播放animation-fill-mode: 静止模式forwards: 停止时,保留最后一帧backwards: 停止时,回到第一帧both: 同时运用 forwards / backwards常用钩子: animationend动画属性: 尽量使用动画属性进行动画,能拥有较好的性能表现translatescalerotateskewopacitycolor经验通常,CSS 并不是重点的考察领域,但这其实是由于现在国内业界对 CSS 的专注不够导致的,真正精通并专注于 CSS 的团队和人才并不多。因此如果能在 CSS 领域有自己的见解和经验,反而会为相当的加分和脱颖而出。JavaScript1. 原型 / 构造函数 / 实例原型(prototype): 一个简单的对象,用于实现对象的 属性继承。可以简单的理解成对象的爹。在 Firefox 和 Chrome 中,每个JavaScript对象中都包含一个__proto__ (非标准)的属性指向它爹(该对象的原型),可obj.__proto__进行访问。构造函数: 可以通过new来 新建一个对象 的函数。实例: 通过构造函数和new创建出来的对象,便是实例。 实例通过__proto__指向原型,通过constructor指向构造函数。说了一大堆,大家可能有点懵逼,这里来举个栗子,以Object为例,我们常用的Object便是一个构造函数,因此我们可以通过它构建实例。// 实例const instance = new Object()则此时, 实例为instance, 构造函数为Object,我们知道,构造函数拥有一个prototype的属性指向原型,因此原型为:// 原型const prototype = Object.prototype这里我们可以来看出三者的关系:实例.proto === 原型原型.constructor === 构造函数构造函数.prototype === 原型实例.constructorr === 构造函数放大来看,我画了张图供大家彻底理解:2.原型链:原型链是由原型对象组成,每个对象都有 proto 属性,指向了创建该对象的构造函数的原型,proto 将对象连接起来组成了原型链。是一个用来实现继承和共享属性的有限的对象链。属性查找机制: 当查找对象的属性时,如果实例对象自身不存在该属性,则沿着原型链往上一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型对象Object.prototype,如还是没找到,则输出undefined;属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要修改原型的属性时,则可以用: b.prototype.x = 2;但是这样会造成所有继承于该对象的实例的属性发生改变。3. 执行上下文(EC)执行上下文可以简单理解为一个对象:它包含三个部分:变量对象(VO)作用域链(词法作用域)this指向它的类型:全局执行上下文函数执行上下文eval执行上下文代码执行过程:创建 全局上下文 (global EC)全局执行上下文 (caller) 逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee) 被push到执行栈顶层函数执行上下文被激活,成为 active EC, 开始执行函数中的代码,caller 被挂起函数执行完后,callee 被pop移除出执行栈,控制权交还全局上下文 (caller),继续执行2.变量对象变量对象,是执行上下文中的一部分,可以抽象为一种 数据作用域,其实也可以理解为就是一个简单的对象,它存储着该执行上下文中的所有 变量和函数声明(不包含函数表达式)。活动对象 (AO): 当变量对象所处的上下文为 active EC 时,称为活动对象。3. 作用域执行上下文中还包含作用域链。理解作用域之前,先介绍下作用域。作用域其实可理解为该上下文中声明的 变量和声明的作用范围。可分为 块级作用域 和 函数作用域特性:声明提前: 一个声明在函数体内都是可见的, 函数优先于变量非匿名自执行函数,函数变量为 只读 状态,无法修改const foo = 1(function foo() { foo = 10 // 由于foo在函数中只为可读,因此赋值无效 console.log(foo)}()) // 结果打印: ƒ foo() { foo = 10 ; console.log(foo) }4.作用域链我们知道,我们可以在执行上下文中访问到父级甚至全局的变量,这便是作用域链的功劳。作用域链可以理解为一组对象列表,包含 父级和自身的变量对象,因此我们便能通过作用域链访问到父级里声明的变量或者函数。由两部分组成:[[scope]]属性: 指向父级变量对象和作用域链,也就是包含了父级的[[scope]]和AOAO: 自身活动对象如此 [[scopr]]包含[[scope]],便自上而下形成一条 链式作用域。5. 闭包闭包属于一种特殊的作用域,称为 静态作用域。它的定义可以理解为: 父函数被销毁 的情况下,返回出的子函数的[[scope]]中仍然保留着父级的单变量对象和作用域链,因此可以继续访问到父级的变量对象,这样的函数称为闭包。闭包会产生一个很经典的问题:多个子函数的[[scope]]都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响。解决:变量可以通过 函数参数的形式 传入,避免使用默认的[[scope]]向上查找使用setTimeout包裹,通过第三个参数传入使用 块级作用域,让变量成为自己上下文的属性,避免共享6. script 引入方式:html 静态<script>引入js 动态插入<script><script defer>: 异步加载,元素解析完成后执行<script async>: 异步加载,与元素渲染并行执行7. 对象的拷贝浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响Object.assign展开运算符(…)深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响JSON.parse(JSON.stringify(obj)): 性能最快具有循环引用的对象时,报错当值为函数或undefined时,无法拷贝递归进行逐一赋值8. new运算符的执行过程新生成一个对象链接到原型: obj.proto = Con.prototype绑定this: apply返回新对象9. instanceof原理能在实例的 原型对象链 中找到该构造函数的prototype属性所指向的 原型对象,就返回true。即:// proto: 代表原型对象链instance.[proto…] === instance.constructor.prototype// return true10. 代码的复用当你发现任何代码开始写第二遍时,就要开始考虑如何复用。一般有以下的方式:函数封装继承复制extend混入mixin借用apply/call11. 继承在 JS 中,继承通常指的便是 原型链继承,也就是通过指定原型,并可以通过原型链继承原型上的属性或者方法。最优化: 圣杯模式var inherit = (function(c,p){ var F = function(){}; return function(c,p){ F.prototype = p.prototype; c.prototype = new F(); c.uber = p.prototype; c.prototype.constructor = c; }})();使用 ES6 的语法糖 class / extends12. 类型转换大家都知道 JS 中在使用运算符号或者对比符时,会自带隐式转换,规则如下:-、*、/、% :一律转换成数值后计算+:数字 + 字符串 = 字符串, 运算顺序是从左到右数字 + 对象, 优先调用对象的valueOf -> toString数字 + boolean/null = 数字数字 + undefined == NaN[1].toString() === ‘1’{}.toString() === ‘[object object]‘NaN !== NaN 、+undefined === NaN13. 类型判断判断 Target 的类型,单单用 typeof 并无法完全满足,这其实并不是 bug,本质原因是 JS 的万物皆对象的理论。因此要真正完美判断时,我们需要区分对待:基本类型(null): 使用 String(null)基本类型(string / number / boolean / undefined) + function: 直接使用 typeof即可其余引用类型(Array / Date / RegExp Error): 调用toString后根据[object XXX]进行判断很稳的判断封装:let class2type = {}‘Array Date RegExp Object Error’.split(’ ‘).forEach(e => class2type[ ‘[object ’ + e + ‘]’ ] = e.toLowerCase()) function type(obj) { if (obj == null) return String(obj) return typeof obj === ‘object’ ? class2type[ Object.prototype.toString.call(obj) ] || ‘object’ : typeof obj}14. 模块化模块化开发在现代开发中已是必不可少的一部分,它大大提高了项目的可维护、可拓展和可协作性。通常,我们 在浏览器中使用 ES6 的模块化支持,在 Node 中使用 commonjs 的模块化支持。分类:es6: import / exportscommonjs: require / module.exports / exportsamd: require / definedrequire与import的区别require支持 动态导入,import不支持,正在提案 (babel 下可支持)require是 同步 导入,import属于 异步 导入require是 值拷贝,导出值变化不会影响导入值;import指向 内存地址,导入值会随导出值而变化15. 防抖与节流防抖与节流函数是一种最常用的 高频触发优化方式,能对性能有较大的帮助。防抖 (debounce): 将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。function debounce(fn, wait, immediate) { let timer = null return function() { let args = arguments let context = this if (immediate && !timer) { fn.apply(context, args) } if (timer) clearTimeout(timer) timer = setTimeout(() => { fn.apply(context, args) }, wait) }}节流(throttle): 每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件 或者 resize 事件,通常每隔 100500 ms执行一次即可。function throttle(fn, wait, immediate) { let timer = null let callNow = true return function() { let context = this, args = arguments if (callNow) { fn.apply(context, args) callNow = false } if (!timer) { timer = setTimeout(() => { fn.apply(context, args) timer = null }, wait) } }}16. 函数执行改变this由于 JS 的设计原理: 在函数中,可以引用运行环境中的变量。因此就需要一个机制来让我们可以在函数体内部获取当前的运行环境,这便是this。因此要明白 this 指向,其实就是要搞清楚 函数的运行环境,说人话就是,谁调用了函数。例如:obj.fn(),便是 obj 调用了函数,既函数中的 this === objfn(),这里可以看成 window.fn(),因此 this === window但这种机制并不完全能满足我们的业务需求,因此提供了三种方式可以手动修改 this 的指向:call: fn.call(target, 1, 2)apply: fn.apply(target, [1, 2])bind: fn.bind(target)(1,2)17. ES6/ES7由于 Babel 的强大和普及,现在 ES6/ES7 基本上已经是现代化开发的必备了。通过新的语法糖,能让代码整体更为简洁和易读。声明let / const: 块级作用域、不存在变量提升、暂时性死区、不允许重复声明const: 声明常量,无法修改解构赋值class / extend: 类声明与继承Set / Map: 新的数据结构异步解决方案:Promise的使用与实现generator:- yield: 暂停代码 - next(): 继续执行代码function* helloWorld() { yield ‘hello’; yield ‘world’; return ’ending’;}const generator = helloWorld();generator.next() // { value: ‘hello’, done: false }generator.next() // { value: ‘world’, done: false }generator.next() // { value: ’ending’, done: true }generator.next() // { value: undefined, done: true }- await / async: 是generator的语法糖, babel中是基于promise实现。jsasync function getUserByAsync(){ let user = await fetchUser(); return user;}const user = await getUserByAsync()console.log(user) 18. AST抽象语法树 (Abstract Syntax Tree),是将代码逐字母解析成 树状对象 的形式。这是语言之间的转换、代码语法检查,代码风格检查,代码格式化,代码高亮,代码错误提示,代码自动补全等等的基础。例如:function square(n){ return n * n}通过解析转化成的AST如下图:19. babel编译原理babylon 将 ES6/ES7 代码解析成 ASTbabel-traverse 对 AST 进行遍历转译,得到新的 AST新 AST 通过 babel-generator 转换成 ES520. 函数柯里化在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,称为函数的柯里化。通常可用于在不侵入函数的前提下,为函数 预置通用参数,供多次重复调用。const add = function add(x) { return function (y) { return x + y }}const add1 = add(1)add1(2) === 3add1(20) === 2121. 数组(array)map: 遍历数组,返回回调返回值组成的新数组forEach: 无法break,可以用try/catch中throw new Error来停止filter: 过滤some: 有一项返回true,则整体为trueevery: 有一项返回false,则整体为falsejoin: 通过指定连接符生成字符串push / pop: 末尾推入和弹出,改变原数组, 返回推入/弹出项unshift / shift: 头部推入和弹出,改变原数组,返回操作项sort(fn) / reverse: 排序与反转,改变原数组concat: 连接数组,不影响原数组, 浅拷贝slice(start, end): 返回截断后的新数组,不改变原数组splice(start, number, value…): 返回删除元素组成的数组,value 为插入项,改变原数组indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值(从第二项开始)数组乱序:var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];arr.sort(function () { return Math.random() - 0.5;});数组拆解: flat: [1,[2,3]] –> [1, 2, 3]arr.prototype.flat = function() { this.toString().split(’,’).map(item => +item )}浏览器1. 跨标签页通讯不同标签页间的通讯,本质原理就是去运用一些可以 共享的中间介质,因此比较常用的有以下方法:通过父页面window.open()和子页面postMessage异步下,通过 window.open(‘about: blank’) 和 tab.location.href = ‘*‘设置同域下共享的localStorage与监听window.onstorage重复写入相同的值无法触发会受到浏览器隐身模式等的限制设置共享cookie与不断轮询脏检查(setInterval)借助服务端或者中间层实现2. 浏览器架构用户界面主进程内核渲染引擎JS 引擎执行栈事件触发线程消息队列微任务宏任务网络异步线程定时器线程3. 浏览器下事件循环(Event Loop)事件循环是指: 执行一个宏任务,然后执行清空微任务列表,循环再执行宏任务,再清微任务列表微任务 microtask(jobs): promise / ajax / Object.observe宏任务 macrotask(task): setTimout / script / IO / UI Rendering4. 从输入 url 到展示的过程DNS 解析TCP 三次握手发送请求,分析 url,设置请求报文(头,主体)服务器返回请求的文件 (html)浏览器渲染HTML parser –> DOM Tree标记化算法,进行元素状态的标记dom 树构建CSS parser –> Style Tree解析 css 代码,生成样式树attachment –> Render Tree结合 dom树 与 style树,生成渲染树layout: 布局GPU painting: 像素绘制页面5. 重绘与回流当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:页面初次渲染浏览器窗口大小改变元素尺寸、位置、内容发生改变元素字体大小变化添加或者删除可见的 dom 元素激活 CSS 伪类(例如::hover)查询某些属性或调用某些方法- clientWidth、clientHeight、clientTop、clientLeft- offsetWidth、offsetHeight、offsetTop、offsetLeft- scrollWidth、scrollHeight、scrollTop、scrollLeft- getComputedStyle()- getBoundingClientRect()- scrollTo()回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。最佳实践:css避免使用table布局将动画效果应用到position属性为absolute或fixed的元素上javascript避免频繁操作样式,可汇总后统一 一次修改尽量使用class进行样式修改减少dom的增删次数,可使用 字符串 或者 documentFragment 一次性插入极限优化时,修改样式可将其display: none后修改避免多次触发上面提到的那些会触发回流的方法,可以的话尽量用 变量存住6. 存储我们经常需要对业务中的一些数据进行存储,通常可以分为 短暂性存储 和 持久性储存。短暂性的时候,我们只需要将数据存在内存中,只在运行时可用持久性存储,可以分为 浏览器端 与 服务器端浏览器🍪 通常用于存储用户身份,登录状态等http 中自动携带, 体积上限为 4K, 可自行设置过期时间localStorage / sessionStorage: 长久储存/窗口关闭删除, 体积限制为 4~5MindexDB服务器:分布式缓存 redis数据库7. Web Worker现代浏览器为JavaScript创造的 多线程环境。可以新建并将部分任务分配到worker线程并行运行,两个线程可 独立运行,互不干扰,可通过自带的 消息机制 相互通信。基本用法:// 创建 workerconst worker = new Worker(‘work.js’);// 向主进程推送消息worker.postMessage(‘Hello World’);// 监听主进程来的消息worker.onmessage = function (event) { console.log(‘Received message ’ + event.data);}限制:同源限制无法使用 document / window / alert / confirm无法加载本地资源8. V8垃圾回收机制垃圾回收: 将内存中不再使用的数据进行清理,释放出内存空间。V8 将内存分成 新生代空间 和 老生代空间。新生代空间: 用于存活较短的对象又分成两个空间: from 空间 与 to 空间Scavenge GC算法: 当 from 空间被占满时,启动 GC 算法存活的对象从 from space 转移到 to space清空 from spacefrom space 与 to space 互换完成一次新生代GC老生代空间: 用于存活时间较长的对象从 新生代空间 转移到 老生代空间 的条件经历过一次以上 Scavenge GC 的对象当 to space 体积超过25%标记清除算法: 标记存活的对象,未被标记的则被释放增量标记: 小模块标记,在代码执行间隙执,GC 会影响性能并发标记(最新技术): 不阻塞 js 执行压缩算法: 将内存中清除后导致的碎片化对象往内存堆的一端移动,解决 内存的碎片化9. 内存泄露意外的全局变量: 无法被回收定时器: 未被正确关闭,导致所引用的外部变量无法被释放事件监听: 没有正确销毁 (低版本浏览器可能出现)闭包: 会导致父级中的变量无法被释放dom 引用: dom 元素被删除时,内存中的引用未被正确清空可用 chrome 中的 timeline 进行内存标记,可视化查看内存的变化情况,找出异常点。服务端与网络1. http/https 协议1.0 协议缺陷:无法复用链接,完成即断开,重新慢启动和 TCP 3次握手head of line blocking: 线头阻塞,导致请求之间互相影响1.1 改进:长连接(默认 keep-alive),复用host 字段指定对应的虚拟站点新增功能:断点续传身份认证状态管理cache 缓存Cache-ControlExpiresLast-ModifiedEtag2.0:多路复用二进制分帧层: 应用层和传输层之间首部压缩服务端推送https: 较为安全的网络传输协议证书(公钥)SSL 加密端口 443TCP:三次握手四次挥手滑动窗口: 流量控制拥塞处理慢开始拥塞避免快速重传快速恢复缓存策略: 可分为 强缓存 和 协商缓存Cache-Control/Expires: 浏览器判断缓存是否过期,未过期时,直接使用强缓存,Cache-Control的 max-age 优先级高于 Expires当缓存已经过期时,使用协商缓存唯一标识方案: Etag(response 携带) & If-None-Match(request携带,上一次返回的 Etag): 服务器判断资源是否被修改,最后一次修改时间: Last-Modified(response) & If-Modified-Since (request,上一次返回的Last-Modified)如果一致,则直接返回 304 通知浏览器使用缓存如不一致,则服务端返回新的资源Last-Modified 缺点:周期性修改,但内容未变时,会导致缓存失效最小粒度只到 s, s 以内的改动无法检测到Etag 的优先级高于 Last-Modified2. 常见状态码1xx: 接受,继续处理200: 成功,并返回数据201: 已创建202: 已接受203: 成为,但未授权204: 成功,无内容205: 成功,重置内容206: 成功,部分内容301: 永久移动,重定向302: 临时移动,可使用原有URI304: 资源未修改,可使用缓存305: 需代理访问400: 请求语法错误401: 要求身份认证403: 拒绝请求404: 资源不存在500: 服务器错误3. get / postget: 缓存、请求长度受限、会被历史保存记录无副作用(不修改资源),幂等(请求次数与资源无关)的场景post: 安全、大数据、更多编码类型两者详细对比如下图:4. WebsocketWebsocket 是一个 持久化的协议, 基于 http , 服务端可以 主动 push兼容:FLASH Socket长轮询: 定时发送 ajaxlong poll: 发送 –> 有消息时再 responsenew WebSocket(url)ws.onerror = fnws.onclose = fnws.onopen = fnws.onmessage = fnws.send()5. TCP三次握手建立连接前,客户端和服务端需要通过握手来确认对方:客户端发送 syn(同步序列编号) 请求,进入 syn_send 状态,等待确认服务端接收并确认 syn 包后发送 syn+ack 包,进入 syn_recv 状态客户端接收 syn+ack 包后,发送 ack 包,双方进入 established 状态6. TCP四次挥手客户端 – FIN –> 服务端, FIN—WAIT服务端 – ACK –> 客户端, CLOSE-WAIT服务端 – ACK,FIN –> 客户端, LAST-ACK客户端 – ACK –> 服务端,CLOSED7. Node 的 Event Loop: 6个阶段timer 阶段: 执行到期的setTimeout / setInterval队列回调I/O 阶段: 执行上轮循环残流的callbackidle, preparepoll: 等待回调执行回调执行定时器如有到期的setTimeout / setInterval, 则返回 timer 阶段如有setImmediate,则前往 check 阶段check执行setImmediateclose callbacks跨域JSONP: 利用<script>标签不受跨域限制的特点,缺点是只能支持 get 请求function jsonp(url, jsonpCallback, success) { const script = document.createElement(‘script’) script.src = url script.async = true script.type = ’text/javascript’ window[jsonpCallback] = function(data) { success && success(data) } document.body.appendChild(script)}设置 CORS: Access-Control-Allow-Origin:postMessage安全XSS攻击: 注入恶意代码cookie 设置 httpOnly转义页面上的输入内容和输出内容CSPF: 跨站请求伪造,防护:get 不修改数据不被第三方网站访问到用户的 cookie设置白名单,不被第三方网站请求请求校验框架:Vue1. nextTick在下次dom更新循环结束之后执行延迟回调,可用于获取更新后的dom状态新版本中默认是mincrotasks, v-on中会使用macrotasksmacrotasks任务的实现:setImmediate / MessageChannel / setTimeout2. 生命周期_init_initLifecycle/Event,往vm上挂载各种属性callHook: beforeCreated: 实例刚创建initInjection/initState: 初始化注入和 data 响应性created: 创建完成,属性已经绑定, 但还未生成真实dom进行元素的挂载: $el / vm.$mount()是否有template: 解析成render function.vue文件: vue-loader会将<template>编译成render functionbeforeMount: 模板编译/挂载之前执行render function,生成真实的dom,并替换到dom tree中mounted: 组件已挂载update:执行diff算法,比对改变是否需要触发UI更新flushScheduleQueuewatcher.before: 触发beforeUpdate钩子 - watcher.run(): 执行watcher中的 notify,通知所有依赖项更新UI触发updated钩子: 组件已更新actived / deactivated(keep-alive): 不销毁,缓存,组件激活与失活destroy:beforeDestroy: 销毁开始销毁自身且递归销毁子组件以及事件监听remove(): 删除节点watcher.teardown(): 清空依赖vm.$off(): 解绑监听destroyed: 完成后触发钩子上面是vue的声明周期的简单梳理,接下来我们直接以代码的形式来完成vue的初始化new Vue({})// 初始化Vue实例function _init() { // 挂载属性 initLifeCycle(vm) // 初始化事件系统,钩子函数等 initEvent(vm) // 编译slot、vnode initRender(vm) // 触发钩子 callHook(vm, ‘beforeCreate’) // 添加inject功能 initInjection(vm) // 完成数据响应性 props/data/watch/computed/methods initState(vm) // 添加 provide 功能 initProvide(vm) // 触发钩子 callHook(vm, ‘created’) // 挂载节点 if (vm.$options.el) { vm.$mount(vm.$options.el) }}// 挂载节点实现function mountComponent(vm) { // 获取 render function if (!this.options.render) { // template to render // Vue.compile = compileToFunctions let { render } = compileToFunctions() this.options.render = render } // 触发钩子 callHook(‘beforeMounte’) // 初始化观察者 // render 渲染 vdom, vdom = vm.render() // update: 根据 diff 出的 patchs 挂载成真实的 dom vm._update(vdom) // 触发钩子 callHook(vm, ‘mounted’)}// 更新节点实现funtion queueWatcher(watcher) { nextTick(flushScheduleQueue)}// 清空队列function flushScheduleQueue() { // 遍历队列中所有修改 for(){ // beforeUpdate watcher.before() // 依赖局部更新节点 watcher.update() callHook(‘updated’) }}// 销毁实例实现Vue.prototype.$destory = function() { // 触发钩子 callHook(vm, ‘beforeDestory’) // 自身及子节点 remove() // 删除依赖 watcher.teardown() // 删除监听 vm.$off() // 触发钩子 callHook(vm, ‘destoryed’)}3. 数据响应(数据劫持)看完生命周期后,里面的watcher等内容其实是数据响应中的一部分。数据响应的实现由两部分构成: 观察者( watcher ) 和 依赖收集器( Dep ),其核心是 defineProperty这个方法,它可以 重写属性的 get 与 set 方法,从而完成监听数据的改变。Observe (观察者)观察 props 与 state遍历 props 与 state,对每个属性创建独立的监听器( watcher )使用 defineProperty 重写每个属性的 get/set(defineReactive)get: 收集依赖Dep.depend()watcher.addDep()set: 派发更新Dep.notify()watcher.update()queenWatcher()nextTickflushScheduleQueuewatcher.run()updateComponent()大家可以先看下面的数据相应的代码实现后,理解后就比较容易看懂上面的简单脉络了。let data = {a: 1}// 数据响应性observe(data)// 初始化观察者new Watcher(data, ’name’, updateComponent)data.a = 2// 简单表示用于数据更新后的操作function updateComponent() { vm._update() // patchs}// 监视对象function observe(obj) { // 遍历对象,使用 get/set 重新定义对象的每个属性值 Object.keys(obj).map(key => { defineReactive(obj, key, obj[key]) })}function defineReactive(obj, k, v) { // 递归子属性 if (type(v) == ‘object’) observe(v) // 新建依赖收集器 let dep = new Dep() // 定义get/set Object.defineProperty(obj, k, { enumerable: true, configurable: true, get: function reactiveGetter() { // 当有获取该属性时,证明依赖于该对象,因此被添加进收集器中 if (Dep.target) { dep.addSub(Dep.target) } return v }, // 重新设置值时,触发收集器的通知机制 set: function reactiveSetter(nV) { v = nV dep.nofify() }, })}// 依赖收集器class Dep { constructor() { this.subs = [] } addSub(sub) { this.subs.push(sub) } notify() { this.subs.map(sub => { sub.update() }) }}Dep.target = null// 观察者class Watcher { constructor(obj, key, cb) { Dep.target = this this.cb = cb this.obj = obj this.key = key this.value = obj[key] Dep.target = null } addDep(Dep) { Dep.addSub(this) } update() { this.value = this.obj[this.key] this.cb(this.value) } before() { callHook(‘beforeUpdate’) }}4. virtual dom 原理实现创建 dom 树树的diff,同层对比,输出patchs(listDiff/diffChildren/diffProps)没有新的节点,返回新的节点tagName与key不变, 对比props,继续递归遍历子树对比属性(对比新旧属性列表):旧属性是否存在与新属性列表中都存在的是否有变化是否出现旧列表中没有的新属性tagName和key值变化了,则直接替换成新节点渲染差异遍历patchs, 把需要更改的节点取出来局部更新dom// diff算法的实现function diff(oldTree, newTree) { // 差异收集 let pathchs = {} dfs(oldTree, newTree, 0, pathchs) return pathchs}function dfs(oldNode, newNode, index, pathchs) { let curPathchs = [] if (newNode) { // 当新旧节点的 tagName 和 key 值完全一致时 if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) { // 继续比对属性差异 let props = diffProps(oldNode.props, newNode.props) curPathchs.push({ type: ‘changeProps’, props }) // 递归进入下一层级的比较 diffChildrens(oldNode.children, newNode.children, index, pathchs) } else { // 当 tagName 或者 key 修改了后,表示已经是全新节点,无需再比 curPathchs.push({ type: ‘replaceNode’, node: newNode }) } } // 构建出整颗差异树 if (curPathchs.length) { if(pathchs[index]){ pathchs[index] = pathchs[index].concat(curPathchs) } else { pathchs[index] = curPathchs } }}// 属性对比实现function diffProps(oldProps, newProps) { let propsPathchs = [] // 遍历新旧属性列表 // 查找删除项 // 查找修改项 // 查找新增项 forin(olaProps, (k, v) => { if (!newProps.hasOwnProperty(k)) { propsPathchs.push({ type: ‘remove’, prop: k }) } else { if (v !== newProps[k]) { propsPathchs.push({ type: ‘change’, prop: k , value: newProps[k] }) } } }) forin(newProps, (k, v) => { if (!oldProps.hasOwnProperty(k)) { propsPathchs.push({ type: ‘add’, prop: k, value: v }) } }) return propsPathchs}// 对比子级差异function diffChildrens(oldChild, newChild, index, pathchs) { // 标记子级的删除/新增/移动 let { change, list } = diffList(oldChild, newChild, index, pathchs) if (change.length) { if (pathchs[index]) { pathchs[index] = pathchs[index].concat(change) } else { pathchs[index] = change } } // 根据 key 获取原本匹配的节点,进一步递归从头开始对比 oldChild.map((item, i) => { let keyIndex = list.indexOf(item.key) if (keyIndex) { let node = newChild[keyIndex] // 进一步递归对比 dfs(item, node, index, pathchs) } })}// 列表对比,主要也是根据 key 值查找匹配项// 对比出新旧列表的新增/删除/移动function diffList(oldList, newList, index, pathchs) { let change = [] let list = [] const newKeys = getKey(newList) oldList.map(v => { if (newKeys.indexOf(v.key) > -1) { list.push(v.key) } else { list.push(null) } }) // 标记删除 for (let i = list.length - 1; i>= 0; i–) { if (!list[i]) { list.splice(i, 1) change.push({ type: ‘remove’, index: i }) } } // 标记新增和移动 newList.map((item, i) => { const key = item.key const index = list.indexOf(key) if (index === -1 || key == null) { // 新增 change.push({ type: ‘add’, node: item, index: i }) list.splice(i, 0, key) } else { // 移动 if (index !== i) { change.push({ type: ‘move’, form: index, to: i, }) move(list, index, i) } } }) return { change, list }}5. Proxy 相比于 defineProperty 的优势数组变化也能监听到不需要深度遍历监听let data = { a: 1 }let reactiveData = new Proxy(data, { get: function(target, name){ // … }, // …})6. vue-routermodehashhistory跳转this.$router.push()<router-link to=""></router-link>占位<router-view></router-view>7. vuexstate: 状态中心mutations: 更改状态actions: 异步更改状态getters: 获取状态modules: 将state分成多个modules,便于管理算法其实算法方面在前端的实际项目中涉及得并不多,但还是需要精通一些基础性的算法,一些公司还是会有这方面的需求和考核,建议大家还是需要稍微准备下,这属于加分题。1. 五大算法贪心算法: 局部最优解法分治算法: 分成多个小模块,与原问题性质相同动态规划: 每个状态都是过去历史的一个总结回溯法: 发现原先选择不优时,退回重新选择分支限界法2. 基础排序算法冒泡排序: 两两比较 function bubleSort(arr) { var len = arr.length; for (let outer = len ; outer >= 2; outer–) { for(let inner = 0; inner <=outer - 1; inner++) { if(arr[inner] > arr[inner + 1]) { [arr[inner],arr[inner+1]] = [arr[inner+1],arr[inner]] } } } return arr; }选择排序: 遍历自身以后的元素,最小的元素跟自己调换位置function selectSort(arr) { var len = arr.length; for(let i = 0 ;i < len - 1; i++) { for(let j = i ; j<len; j++) { if(arr[j] < arr[i]) { [arr[i],arr[j]] = [arr[j],arr[i]]; } } } return arr}插入排序: 即将元素插入到已排序好的数组中function insertSort(arr) { for(let i = 1; i < arr.length; i++) { //外循环从1开始,默认arr[0]是有序段 for(let j = i; j > 0; j–) { //j = i,将arr[j]依次插入有序段中 if(arr[j] < arr[j-1]) { [arr[j],arr[j-1]] = [arr[j-1],arr[j]]; } else { break; } } } return arr;}3. 高级排序算法快速排序选择基准值(base),原数组长度减一(基准值),使用 splice循环原数组,小的放左边(left数组),大的放右边(right数组);concat(left, base, right)递归继续排序 left 与 rightfunction quickSort(arr) { if(arr.length <= 1) { return arr; //递归出口 } var left = [], right = [], current = arr.splice(0,1); for(let i = 0; i < arr.length; i++) { if(arr[i] < current) { left.push(arr[i]) //放在左边 } else { right.push(arr[i]) //放在右边 } } return quickSort(left).concat(current,quickSort(right));}希尔排序:不定步数的插入排序,插入排序口诀: 插冒归基稳定,快选堆希不稳定稳定性: 同大小情况下是否可能会被交换位置, 虚拟dom的diff,不稳定性会导致重新渲染;4. 递归运用(斐波那契数列): 爬楼梯问题初始在第一级,到第一级有1种方法(s(1) = 1),到第二级也只有一种方法(s(2) = 1), 第三级(s(3) = s(1) + s(2))function cStairs(n) { if(n === 1 || n === 2) { return 1; } else { return cStairs(n-1) + cStairs(n-2) }}5. 数据树二叉树: 最多只有两个子节点完全二叉树满二叉树深度为 h, 有 n 个节点,且满足 n = 2^h - 1二叉查找树: 是一种特殊的二叉树,能有效地提高查找效率小值在左,大值在右节点 n 的所有左子树值小于 n,所有右子树值大于 n遍历节点前序遍历根节点访问左子节点,回到 1访问右子节点,回到 1中序遍历先访问到最左的子节点访问该节点的父节点访问该父节点的右子节点, 回到 1后序遍历先访问到最左的子节点访问相邻的右节点访问父节点, 回到 1插入与删除节点6. 天平找次品有n个硬币,其中1个为假币,假币重量较轻,你有一把天平,请问,至少需要称多少次能保证一定找到假币?三等分算法:将硬币分成3组,随便取其中两组天平称量平衡,假币在未上称的一组,取其回到 1 继续循环不平衡,假币在天平上较轻的一组, 取其回到 1 继续循环结语由于精力时间及篇幅有限,这篇就先写到这。大家慢慢来不急。。????。下篇打算准备以下内容,我也得补补课先:Webpack相关原理LoaderPlugin项目性能优化首屏渲染优化用户体验优化webpack 性能优化Hybrid 与 Webviewwebview 加载过程bridge 原理hybrid app 经验框架: React在面试中,很多领域并没有真正的答案,能回答到什么样的深度,还是得靠自己真正的去使用和研究。知识面的广度与深度应该并行,尽量的拓张自己的领域,至少都有些基础性的了解,在被问到的时候可以同面试官唠嗑两句,然后在自己喜欢的领域,又有着足够深入的研究,让面试官觉得你是这方面的专家。知识大纲还在不断的完善和修正,由于也是精力时间有限,我会慢慢补充后面列出来的部分。当然,我也是在整理中不断的学习,也希望大家能一起参与进来,要补充或修正的地方麻烦赶紧提出。另外,刚新建了个公众号,想作为大家交流和分享的地方,有兴趣想法的童鞋联系我哈~~????Tips: 头条招前端,内推的童鞋赶紧死命找我! 博主写得很辛苦,感恩 github。???? ...

February 14, 2019 · 11 min · jiezi

数据库索引是什么?新华字典来帮你

学过服务器端开发的朋友一定知道,程序没有数据库索引也可以运行。但是所有学习数据库的资料、教程,一定会有大量的篇幅在介绍数据库索引,各种后端开发工作的面试也一定绕不开索引,甚至可以说数据库索引是从后端初级开发跨越到高级开发的屠龙宝刀,那么索引到底在服务端程序中起着怎样的作用呢?这篇文章是一系列数据库索引文章中的第一篇,这个系列包括了下面四篇文章:数据库索引是什么?新华字典来帮你 —— 理解数据库索引融会贯通 —— 深入20分钟数据库索引设计实战 —— 实战数据库索引为什么用B+树实现? —— 扩展这一系列涵盖了数据库索引从理论到实践的一系列知识,一站式解决了从理解到融会贯通的全过程,相信每一篇文章都可以给你带来更深入的体验。什么是数据库索引?用一句话来描述:数据库索引就是一种加快海量数据查询的关键技术。现在还不理解这句话?不要紧,往下看,20分钟以后你就能自己做出这样的总结来了。首先给大家看一张图片这本书大家一定都很熟悉,小学入门第一课一定就是教小朋友们学习如何使用这本书。那这和我们的数据库索引有啥关系呢?别着急,我们翻开第一页看看。请大家注意右上角的那一排文字,原来目录就是传说中的索引呀!从前面的“一句话描述”我们可以知道,索引的目的就是为了加快数据查询。那么我们查字典时翻的第一个地方是哪里呢,我相信大部分人都会先翻到拼音目录,毕竟现在很多人都是提笔忘字了????。数据库索引的作用和拼音目录是一样的,就是最快速的锁定目标数据所在的位置范围。比如我们在这里要查险这个字,那么我们找到了Xx部分之后就能按顺序找到xian这个拼音所在的页码,根据前后的页码我们可以知道这个字一定是在519页到523页之间的,范围一下子就缩小到只有4页了。这相比我们从头翻到尾可是快多了,这时候就出现了第一个专业术语——全表扫描,也就是我们说的从头找到尾了。果然,我们在第521页找到了我们要找的“险”字。那么现在我们就知道数据库索引大概是一个什么东西了:数据库索引是一个类似于目录这样的用来加快数据查询的技术。什么是联合索引?相信大家都见过一些包含多个字段的数据库索引,比如INDEX idx_test(col_a, col_b)。这种包含多个字段的索引就被称为“联合索引”。那么在多个字段上建索引能起到什么样的作用呢?下面还是以新华字典为例,来看看到底什么是联合索引。新华字典里还有一种目录被称为“部首目录”,下面可以看到,要使用这个目录我们首先会根据部首的笔画数找到对应该能的部分,然后可以在里面找到我们想找的部首。比如如果我们还是要找险字所在的位置:找到部首后,右边的页码还不是险字真正的页码,我们还需要根据右边的页码找到对应部首在检字表中的位置。找到第93页的检字表后我们就可以根据险字余下的笔画数(7画)在“6-8画”这一部分里找到险字真正的页码了。在这个过程中,我们按顺序使用了“两个目录”,一个叫做“部首目录”,一个叫做“检字表”。并且我们可以看到上图中检字表的内容都是按部首分门别类组织的。这两个部分合在一起就是我们在本节讨论的主题——联合索引。即通过第一个字段的值(部首)在第一级索引中找到对应的第二级索引位置(检字表页码),然后在第二级索引中根据第二个字段的值(笔画)找到符合条件的数据所在的位置(险字的真正页码)。最左前缀匹配从前面使用部首目录的例子中可以看出,如果我们不知道一个字的部首是什么的话,那基本是没办法使用这个目录的。这说明仅仅通过笔画数(第二个字段)是没办法使用部首目录的。这就引申出了联合索引的一个规则:联合索引中的字段,只有某个字段(笔画)左边的所有字段(部首)都被使用了,才能使用该字段上的索引。例如,有索引INDEX idx_i1(col_a, col_b),如果查询条件为where col_b = 1,则无法使用索引idx_i1。但是如果我们知道部首但是不知道笔画数,比如不知道“横折竖弯勾”是算一笔还是两笔,那我们仍然可以使用“部首目录”部分的内容,只是要把“检字表”对应部首里的所有字都看一遍就能找到我们要找的字了。这就引申出了联合索引的另一个规则:联合索引中的字段,即使某个字段(部首)右边的其他字段(笔画)没有被使用,该字段之前(含)的所有字段仍然可以正常使用索引。例如,有索引INDEX idx_i2(col_a, col_b, col_c),则查询条件where col_a = 1 and col_b = 2在字段col_a和col_b上仍然可以走索引。但是,如果我们在确定部首后,不知道一个字到底是两画还是三画,这种情况下我们只需要在对应部首的两画和三画部分中找就可以了,也就是说我们仍然使用了检字表中的内容。所以,使用范围条件查询时也是可以使用索引的。最后,我们可以完整地表述一下最左前缀匹配原则的含义:对于一个联合索引,如果有一个SQL查询语句需要执行,则只有从索引最左边的第一个字段开始到SQL语句查询条件中不包含的字段(不含)或范围条件字段(含)为止的部分才会使用索引进行加速。这里出现了一个之前没有提到的点,就是范围条件字段也会结束对索引上后续字段的使用,这是为什么呢?具体原因的解释涉及到了更深层次的知识,在接下来的第二篇文章的最后就可以找到答案。什么是聚集索引?从上文的部首目录和拼音目录同时存在但是实际的字典内容只有一份这一点上可以看出,在数据库中一张表上是可以有多个索引的。那么不同的索引之间有什么区别呢?我们在新华字典的侧面可以看到一个V字形的一个个黑色小方块,有很多人都会在侧面写上A, B, C, D这样对应的拼音字母。因为字典中所有的字都是按照拼音顺序排列的,有时候直接使用首字母翻开对应的部分查也很快。像拼音目录这样的索引,数据会根据索引中的顺序进行排列和组织的,这样的索引就被称为聚集索引,而非聚集索引就是其他的一般索引。因为数据只能按照一种规则排序,所以一张表至多有一个聚集索引,但可以有多个非聚集索引。在MySQL数据库的InnoDB存储引擎中,主键索引就是聚集索引,所有数据都会按照主键索引进行组织;而在MyISAM存储引擎中,就没有聚集索引了,因为MyISAM存储引擎中的数据不是按索引顺序进行存储的。

February 14, 2019 · 1 min · jiezi

程序员精美简历Top榜—面试必备

听说你最近打算换工作?听说你和好工作之间,只差一个漂亮的简历模板?人们常说“金三银四”,一年之际在于春。不管你是主动离职,还是“被离职”(稳住,我们能赢!),趁着大好时光和对新年的憧憬,再找一个更好的工作吧。凡事预则立,不预则废。面试也是一样,除了腻害的编程技术以外,要怎么在茫茫的竞争者中脱颖而出呢?或许你需要一个漂亮的简历,让面试官眼前一亮的Surprise,第一印象往往是成功开端,所以下来我们一起来看看这些漂亮的简历模板吧。模板1:模板2:模板3:模板4:模板5:模板6:免费领取: 搜索“王磊的博客”或扫描下方二维码关注微信号,发送“简历”两个字直接领取。

February 14, 2019 · 1 min · jiezi

Java面试 | 001优点有啥?

本博客 猫叔的博客,转载请申明出处前言本系列为猫叔综合整理的Java面试题系列,如有雷同不胜荣幸。001、请你说说Java这门语言的优点?PS:这是看你对Java的理解程序。1、Java是一门面向对象的编程语言,使用它编写出来的程序易读且更为容易。2、“一次编译,到处运行”,由于Java为解释型语言,编译器转换java代码后再由Java虚拟机解释执行,所以java语言可以很好的跨平台执行,具备可移植性。3、java提供了各种内置库,如多线程Thread、网络通信NIO、垃圾回收器(帮助程序员摆脱内存管理)。4、对Web应用程序支持,可以开发分布式类应用。5、自带了很好的安全机制,如:强类型转换、垃圾回收器、异常处理、安全检查等等。6、java是由C++改进重新设计而来的,且去除了C++中部分复杂的习惯。公众号:Java猫说现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。

February 14, 2019 · 1 min · jiezi

PHP面试常考内容之面向对象(2)

PHP面试专栏正式起更,每周一、三、五更新,提供最好最优质的PHP面试内容。继上一篇“PHP面试常考内容之面向对象(1)”发表后,今天更新(2),需要(1)的可以直接点击文字进行跳转获取。整个面向对象文章的结构涉及的内容模块有:一、面向对象与面向过程有什么区别?二、面向对象有什么特征?三、什么是构造函数和析构函数?四、面向对象的作用域范围有哪几种?五、PHP 中魔术方法有哪些?六、什么是对象克隆?七、this、self和parent的区别是什么?八、抽象类与接口有什么区别与联系?九、PHP面向对象的常考面试题讲解关于PHP面向对象的内容将会被分为三篇文章进行讲解完整块内容,第一篇主要讲解一到四点内容,第二篇主要讲解五到八的内容,第三篇围绕第九点进行讲解。以下正文的内容都来自《PHP程序员面试笔试宝典》书籍,如果转载请保留出处:五、PHP种魔术方法有哪些?在PHP中,把所有以__(两个下画线)开头的类方法保留为魔术方法。所以在定义类方法时,不建议使用 __ 作为方法的前缀。下面分别介绍每个魔术方法的作用。1.__get、__set、__isset、__unset这四个方法是为在类和它们的父类中没有声明的属性而设计的。1)在访问类属性的时候,若属性可以访问,则直接返回;若不可以被访问,则调用__get 函数。方法签名为:public mixed __get ( string $name )2)在设置一个对象的属性时,若属性可以访问,则直接赋值;若不可以被访问,则调用__set 函数。方法签名为:public void __set ( string $name , mixed $value )3)当对不可访问的属性调用 isset() 或 empty() 时,__isset() 会被调用。方法签名为:public bool __isset ( string $name )4)当对不可访问属性调用 unset() 时,__unset() 会被调用。方法签名为:public bool _unset ( string $name )需要注意的是,以上存在的不可访问包括属性没有定义,或者属性的访问控制为proteced或private(没有访问权限的属性)。下面通过一个例子把对象变量保存在另外一个数组中。<?php class Test { /* 保存未定义的对象变量 */ private $data = array(); public function __set($name, $value){ $this->data[$name] = $value; } public function __get($name){ if(array_key_exists($name, $this->data)) return $this->data[$name]; return NULL; } public function __isset($name){ return isset($this->data[$name]); } public function __unset($name){ unset($this->data[$name]); } } $obj = new Test; $obj->a = 1; echo $obj->a . “\n”;?>程序的运行结果为12.__construct、__destruct1)__construct 构造函数,实例化对象时被调用。2)__destruct 析构函数,当对象被销毁时调用。通常情况下,PHP只会释放对象所占有的内存和相关的资源,对于程序员自己申请的资源,需要显式地去释放。通常可以把需要释放资源的操作放在析构方法中,这样可以保证在对象被释放的时候,程序员自己申请的资源也能被释放。例如,可以在构造函数中打开一个文件,然后在析构函数中关闭文件。<?php class Test { protected $file = NULL; function __construct(){ $this->file = fopen(“test”,“r”); } function __destruct(){ fclose($this->file); } }?>3.__call()和__callStatic()1)__call( $method, $arg_array ):当调用一个不可访问的方法时会调用这个方法。2)__callStatic的工作方式与 __call() 类似,当调用的静态方法不存在或权限不足时,会自动调用__callStatic()。使用示例如下: <?php class Test { public function __call ($name, $arguments) { echo “调用对象方法 ‘$name’ “. implode(’, ‘, $arguments). “\n”; } public static function __callStatic ($name, $arguments) { echo “调用静态方法 ‘$name’ “. implode(’, ‘, $arguments). “\n”; } } $obj = new Test; $obj->method1(‘参数1’); Test::method2(‘参数2’); ?>程序的运行结果为调用对象方法 ‘method1’ 参数1 调用静态方法 ‘method2’ 参数24.__sleep()和__wakeup()1)__sleep 串行化的时候调用。2)__wakeup 反串行化的时候调用。也就是说,在执行serialize()和unserialize()时,会先调用这两个函数。例如,在序列化一个对象时,如果这个对象有一个数据库连接,想要在反序列化中恢复这个连接的状态,那么就可以通过重载这两个方法来实现。示例代码如下:<?php class Test { public $conn; private $server, $user, $pwd, $db; public function __construct($server, $user, $pwd, $db) { $this->server = $server; $this->user = $user; $this->pwd = $pwd; $this->db = $db; $this->connect(); } private function connect() { $this->conn = mysql_connect($this->server, $this->user, $this->pwd); mysql_select_db($this->db, $this->conn); } public function __sleep() { return array(‘server’, ‘user’, ‘pwd’, ‘db’); } public function __wakeup() { $this->connect(); } public function __destruct(){ mysql_close($conn); } }?>5.__toString()__toString 在打印一个对象时被调用,可以在这个方法中实现想要打印的对象的信息,使用示例如下:<?php class Test { public $age; public function __toString() { return “age:$this->age”; } } $obj = new Test(); $obj->age=20; echo $obj;?>程序的运行结果为age:206.__invoke()在引入这个魔术方法后,可以把对象名当作方法直接调用,它会间接调用这个方法,使用示例如下:<?php class Test { public function __invoke() { print “hello world”; } } $obj = new Test; $obj();?>程序的运行结果为hello world7.__set_state()调用 var_export 时被调用,用__set_state的返回值作为var_export 的返回值。使用示例如下:<?php class People { public $name; public $age; public static function __set_state ($arr) { $obj = new People; $obj->name = $arr[’name’]; $obj->age = $arr[‘aage’]; return $obj; } } $p = new People; $p->age = 20; $p->name = ‘James’; var_dump(var_export($p));?>程序的运行结果为People::__set_state(array( ’name’ => ‘James’, ‘age’ => 20,)) NULL8.__clone()这个方法在对象克隆的时候被调用,php提供的__clone()方法对一个对象实例进行浅拷贝,也就是说,对对象内的基本数值类型通过值传递完成拷贝,当对象内部有对象成员变量的时候,最好重写__clone方法来实现对这个对象变量的深拷贝。使用示例如下:<?php class People { public $age; public function __toString() { return “age:$this->age \n”; } } class MyCloneable { public $people; function __clone() { $this->people = clone $this->people; //实现对象的深拷贝 } } $obj1 = new MyCloneable(); $obj1->people = new People(); $obj1->people->age=20; $obj2 = clone $obj1; $obj2->people->age=30; echo $obj1->people; echo $obj2->people;?>程序的运行结果为age:20 age:30由此可见,通过对象拷贝后,对其中一个对象值的修改不影响另外一个对象。9.__autoload()当实例化一个对象时,如果对应的类不存在,则该方法被调用。这个方法经常的使用方法为:在方法体中根据类名,找出类文件,然后require_one 导入这个文件。由此,就可以成功地创建对象了,使用示例如下:Test.php:<?php class Test { function hello() { echo ‘Hello world’; } }?>index.php:<?php function __autoload( $class ) { $file = $class . ‘.php’; if ( is_file($file) ) { require_once($file); //导入文件 } } $obj = new Test(); $obj->hello();?>程序的运行结果为Hello world在index.php中,由于没有包含Test.php,在实例化Test对象的时候会自动调用__autoload方法,参数$class的值即为类名Test,这个函数中会把Test.php引进来,由此Test对象可以被正确地实例化。这种方法的缺点是需要在代码中文件路径做硬编码,当修改文件结构的时候,代码也要跟着修改。另一方面,当多个项目之间需要相互引用代码的时候,每个项目中可能都有自己的__autoload,这样会导致两个__autoload冲突。当然可以把__autoload修改成一个。这会导致代码的可扩展性和可维护性降低。由此从PHP5.1开始引入了spl_autoload,可以通过spl_autoload_register注册多个自定义的autoload方法,使用示例如下:index.php<?php function loadprint( $class ) { $file = $class . ‘.php’; if (is_file($file)) { require_once($file); } } spl_autoload_register( ’loadprint’ ); //注册自定义的autoload方法从而避免冲突 $obj = new Test(); $obj->hello();?>spl_autoload是_autoload()的默认实现,它会去include_path中寻找$class_name(.php/.inc) 。除了常用的spl_autoload_register外,还有如下几个方法:1)spl_autoload:_autoload()的默认实现。2)spl_autoload_call:这个方法会尝试调用所有已经注册的__autoload方法来加载请求的类。3)spl_autoload_functions:获取所有被注册的__autoload方法。4)spl_autoload_register:注册__autoload方法。5)spl_autoload_unregister:注销已经注册的__autoload方法。6)spl_autoload_extensions:注册并且返回spl_autoload方法使用的默认文件的扩展名。引申:PHP有哪些魔术常量?除了魔术变量外,PHP还定义了如下几个常用的魔术常量。1)LINE:返回文件中当前的行号。2)FILE:返回当前文件的完整路径。3)FUNCTION:返回所在函数名字。4)CLASS:返回所在类的名字。5)METHOD:返回所在类方法的名称。与__FUNCTION__不同的是,__METHOD__返回的是“class::function”的形式,而__FUNCTION__返回“function”的形式。6)DIR:返回文件所在的目录。如果用在被包括文件中,则返回被包括的文件所在的目录(PHP 5.3.0中新增)。7)NAMESPACE:返回当前命名空间的名称(区分大小写)。此常量是在编译时定义的(PHP 5.3.0 新增)。8)TRAIT:返回 Trait 被定义时的名字。Trait 名包括其被声明的作用区域(PHP 5.4.0 新增)。六、什么是对象克隆?对于对象而言,PHP用的是引用传递,也就是说,对象间的赋值操作只是赋值了一个引用的值,而不是整个对象的内容,下面通过一个例子来说明引用传递存在的问题:<?php class My_Class { public $color; } $obj1 = new My_Class (); $obj1->color = “Red”; $obj2 = $obj1; $obj2->color =“Blue”; //$obj1->color的值也会变成"Blue”?>因为PHP使用的是引用传递,所以在执行$obj2 = $obj1后,$obj1和$obj2都是指向同一个内存区(它们在内存中的关系如下图所示),任何一个对象属性的修改对另外一个对象也是可见的。在很多情况下,希望通过一个对象复制出一个一样的但是独立的对象。PHP提供了clone关键字来实现对象的复制。如下例所示:<?php class My_Class { public $color; } $obj1 = new My_Class (); $obj1->color = “Red”; $obj2 = clone $obj1; $obj2->color =“Blue”; //此时$obj1->color的值仍然为"Red”?>$obj2 = clone $obj1把obj1的整个内存空间复制了一份存放到新的内存空间,并且让obj2指向这个新的内存空间,通过clone克隆后,它们在内存中的关系如下图所示。此时对obj2的修改对obj1是不可见的,因为它们是两个独立的对象。在学习C++的时候有深拷贝和浅拷贝的概念,显然PHP也存在相同的问题,通过clone关键字克隆出来的对象只是对象的一个浅拷贝,当对象中没有引用变量的时候这种方法是可以正常工作的,但是当对象中也存在引用变量的时候,这种拷贝方式就会有问题,下面通过一个例子来进行说明:<?php class My_Class { public $color; } $c =“Red”; $obj1 = new My_Class (); $obj1->color =&$c; //这里用的是引用传递 $obj2 = clone $obj1; //克隆一个新的对象 $obj2->color=“Blue”; //这时,$obj1->color的值也变成了"Blue”?>在这种情况下,这两个对象在内存中的关系如下图所示。从上图中可以看出,虽然obj1与obj2指向的对象占用了独立的内存空间,但是对象的属性color仍然指向一个相同的存储空间,因此当修改了obj2->color的值后,意味着c的值被修改,显然这个修改对obj1也是可见的。这就是一个非常典型的浅拷贝的例子。为了使两个对象完全独立,就需要对对象进行深拷贝。那么如何实现呢,PHP提供了类似于__clone方法(类似于C++的拷贝构造函数)。把需要深拷贝的属性,在这个方法中进行拷贝:使用示例如下:<?php class My_Class { public $color; public function __clone() { $this->color = clone $this->color; } } $c =“Red”; $obj1 = new My_Class (); $obj1->color =&$c; $obj2 = clone $obj1; $obj2->color=“Blue”; //这时,$obj1->color的值仍然为"Red”?>通过深拷贝后,它们在内存中的关系如图1-4所示。通过在__clone方法中对对象的引用变量color进行拷贝,使obj1与obj2完全占用两块独立的存储空间,对obj2的修改对obj1也不可见。自己整理了一篇“如果遇到代码怎么改都没效果,怎么办?”的文章,关注公众号:“琉忆编程库”,回复:“问题”,我发给你。七、this、self和parent的区别是什么?this、self、parent三个关键字从字面上比较好理解,分别是指这、自己、父亲。其中,this指的是指向当前对象的指针(暂用C语言里面的指针来描述),self指的是指向当前类的指针,parent指的是指向父类的指针。以下将具体对这三个关键字进行分析。##1.this关键字## 1 <?php 2 class UserName { 3 private $name; // 定义成员属性 4 function __construct($name) { 5 $this->name = $name; // 这里已经使用了this指针 6 } 7 // 析构函数 8 function __destruct() { 9 } 10 // 打印用户名成员函数 11 function printName() { 12 print ($this->name."\n") ; // 又使用了this指针 13 } 14 } 15 // 实例化对象 16 $nameObject = new UserName ( “heiyeluren” ); 17 // 执行打印 18 $nameObject->printName (); // 输出: heiyeluren 19 // 第二次实例化对象 20 $nameObject2 = new UserName ( “PHP5” ); 21 // 执行打印 22 $nameObject2->printName (); // 输出:PHP5 23 ?>上例中,分别在5行和12行使用了this指针,那么this到底是指向谁呢?其实,this是在实例化的时候来确定指向谁,例如,第一次实例化对象的时候(16行),当时this就是指向$nameObject 对象,那么执行第12行打印的时候就把print($this->name)变成了print ($nameObject->name),输出"heiyeluren"。对于第二个实例化对象,print( $this- >name )变成了print( $nameObject2->name ),于是就输出了"PHP5"。所以,this就是指向当前对象实例的指针,不指向任何其他对象或类。2.self关键字先要明确一点,self是指向类本身,也就是self是不指向任何已经实例化的对象,一般self用来访问类中的静态变量。 1 <?php 2 class Counter { 3 // 定义属性,包括一个静态变量 4 private static $firstCount = 0; 5 private $lastCount; 6 // 构造函数 7 function __construct() { 8 // 使用self来调用静态变量,使用self调用必须使用::(域运算符号) 9 $this->lastCount = ++ selft::$firstCount; 10 } 11 // 打印lastCount数值 12 function printLastCount() { 13 print ($this->lastCount) ; 14 } 15 } 16 // 实例化对象 17 $countObject = new Counter (); 18 $countObject->printLastCount (); // 输出 1 19 ?>上述示例中,在第4行定义了一个静态变量$firstCount,并且初始值为0,那么在第9行的时候调用了这个值,使用的是self来调用,中间使用域运算符“::”来连接,这时候调用的就是类自己定义的静态变量$firstCount,它与下面对象的实例无关,只是与类有关,无法使用this来引用,只能使用 self来引用,因为self是指向类本身,与任何对象实例无关。3.parent关键字parent是指向父类的指针,一般使用parent来调用父类的构造函数。 1 <?php 2 // 基类 3 class Animal { 4 // 基类的属性 5 public $name; // 名字 6 // 基类的构造函数 7 public function __construct($name) { 8 $this->name = $name; 9 } 10 } 11 // 派生类 12 class Person extends Animal // Person类继承了Animal类 13 { 14 public $personSex; // 性别 15 public $personAge; // 年龄 16 // 继承类的构造函数 17 function __construct($personSex, $personAge) { 18 parent::__construct ( “heiyeluren” ); // 使用parent调用了父类的构造函数 19 $this->personSex = $personSex; 20 $this->personAge = $personAge; 21 } 22 function printPerson() { 23 print ($this->name . " is " . $this->personSex . “,this year " . $this->personAge) ; 24 } 25 } 26 // 实例化Person对象 27 $personObject = new Person ( “male”, “21” ); 28 // 执行打印 29 $personObject->printPerson (); // 输出:heiyeluren is male,this year 21 30 ?>上例中,成员属性都是public的,特别是父类的,是为了供继承类通过this来访问。第18行: parent::__construct( “heiyeluren” ),使用了parent来调用父类的构造函数进行对父类的初始化,因为父类的成员都是public的,于是就能够在继承类中直接使用 this来访问从父类继承的属性。八、抽象类与接口有什么区别与联系?抽象类应用的定义如下:abstract class ClassName{}抽象类具有以下特点:1)定义一些方法,子类必须实现父类所有的抽象方法,只有这样,子类才能被实例化,否则子类还是一个抽象类。2)抽象类不能被实例化,它的意义在于被扩展。3)抽象方法不必实现具体的功能,由子类来完成。4)当子类实现抽象类的方法时,这些方法的访问控制可以和父类中的一样,也可以有更高的可见性,但是不能有更低的可见性。例如,某个抽象方法被声明为protected的,那么子类中实现的方法就应该声明为protected或者public的,而不能声明为private。5)如果抽象方法有参数,那么子类的实现也必须有相同的参数个数,必须匹配。但有一个例外:子类可以定义一个可选参数(这个可选参数必须要有默认值),即使父类抽象方法的声明里没有这个参数,两者的声明也无冲突。下面通过一个例子来加深理解:<?php abstract class A{ abstract protected function greet($name); } class B extends A { public function greet($name, $how=“Hello “) { echo $how.$name."\n”; } } $b = new B; $b->greet(“James”); $b->greet(“James”,“Good morning “);?>程序的运行结果为Hello JamesGood morning James定义抽象类时,通常需要遵循以下规则:1)一个类只要含有至少一个抽象方法,就必须声明为抽象类。2)抽象方法不能够含有方法体。接口可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。在PHP中,接口是通过interface关键字来实现的,与定义一个类类似,唯一不同的是接口中定义的方法都是公有的而且方法都没有方法体。接口中所有的方法都是公有的,此外接口中还可以定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口所覆盖。要实现一个接口,可以通过关键字implements来完成。实现接口的类中必须实现接口中定义的所有方法。虽然PHP不支持多重继承,但是一个类可以实现多个接口,用逗号来分隔多个接口的名称。下面给出一个接口使用的示例:<?php interface Fruit { const MAX_WEIGHT = 3; //静态常量 function setName($name); function getName(); } class Banana implements Fruit { private $name; function getName() { return $this->name; } function setName($_name) { $this->name = $_name; } } $b = new Banana(); //创建对象 $b->setName(“香蕉”); echo $b->getName(); echo “<br />”; echo Banana::MAX_WEIGHT; //静态常量?>程序的运行结果为香蕉 3接口和抽象类主要有以下区别:抽象类:PHP5支持抽象类和抽象方法。被定义为抽象的类不能被实例化。任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。被定义为抽象的方法只是声明了其调用方法和参数,不能定义其具体的功能实现。抽象类通过关键字abstract来声明。接口:可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。在这种情况下,可以通过interface关键字来定义一个接口,在接口中声明的方法都不能有方法体。二者虽然都是定义了抽象的方法,但是事实上两者区别还是很大的,主要区别如下:1)对接口的实现是通过关键字implements来实现的,而抽象类继承则是使用类继承的关键字extends实现的。2)接口没有数据成员(可以有常量),但是抽象类有数据成员(各种类型的成员变量),抽象类可以实现数据的封装。3)接口没有构造函数,抽象类可以有构造函数。4)接口中的方法都是public类型,而抽象类中的方法可以使用private、protected或public来修饰。5)一个类可以同时实现多个接口,但是只能实现一个抽象类。预告:PHP面试常考内容之面向对象(3)将于本周五(2019.2-15)更新。以上内容摘自《PHP程序员面试笔试宝典》书籍,该书已在天猫、京东、当当等电商平台销售。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,我将不断完善追求极致,感谢你们的支持。 ...

February 13, 2019 · 5 min · jiezi

【剑指offer】13.包含min函数的栈

题目定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。思路1.定义两个栈,一个栈用于存储数据,另一个栈用于存储每次数据进栈时栈的最小值.2.每次数据进栈时,将此数据和最小值栈的栈顶元素比较,将二者比较的较小值再次存入最小值栈.4.数据栈出栈,最小值栈也出栈。3.这样最小值栈的栈顶永远是当前栈的最小值。代码var dataStack = [];var minStack = []; function push(node){ dataStack.push(node); if(minStack.length === 0 || node < min()){ minStack.push(node); }else{ minStack.push(min()); }}function pop(){ minStack.pop(); return dataStack.pop();}function top(){ var length = dataStack.length; return length>0&&dataStack[length-1]}function min(){ var length = minStack.length; return length>0&&minStack[length-1]}

February 13, 2019 · 1 min · jiezi

刷前端面经笔记(九)

1.JavaScript实现二分法查找?二分法查找,也称折半查找,是一种在有序数组中查找特定元素的搜索算法。查找过程可以分为以下步骤:(1)首先,从有序数组的中间的元素开始搜索,如果该元素正好是目标元素(即要查找的元素),则搜索过程结束,否则进行下一步。(2)如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作。(3)如果某一步数组为空,则表示找不到目标元素。代码: // 非递归算法 function binary_search(arr, key) { var low = 0, high = arr.length - 1; while(low <= high){ var mid = parseInt((high + low) / 2); if(key == arr[mid]){ return mid; }else if(key > arr[mid]){ low = mid + 1; }else if(key < arr[mid]){ high = mid -1; }else{ return -1; } }}; var arr = [1,2,3,4,5,6,7,8,9,10,11,23,44,86]; var result = binary_search(arr,10); alert(result); // 9 返回目标元素的索引值 // 递归算法 function binary_search(arr,low, high, key) { if (low > high){ return -1; } var mid = parseInt((high + low) / 2); if(arr[mid] == key){ return mid; }else if (arr[mid] > key){ high = mid - 1; return binary_search(arr, low, high, key); }else if (arr[mid] < key){ low = mid + 1; return binary_search(arr, low, high, key); }}; var arr = [1,2,3,4,5,6,7,8,9,10,11,23,44,86]; var result = binary_search(arr, 0, 13, 10); alert(result); // 9 返回目标元素的索引值2.有一楼梯共M级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第M级,共有多少种走法?这个问题要倒过来看,要到达n级楼梯,只有两种方式,从(n-1)级 或 (n-2)级到达的。所以可以用递推的思想去想这题,假设有一个数组s[n], 那么s[1] = 1(由于一开始就在第一级,只有一种方法),s[2] = 1(只能从s[1]上去 没有其他方法)。那么就可以推出s[3] ~ s[n]了。下面继续模拟一下, s[3] = s[1] + s[2],因为只能从第一级跨两步, 或者第二级跨一步。function cStairs(n) { if(n === 1 || n === 2) { return 1; } else { return cStairs(n-1) + cStairs(n-2) }}3.递归设计。 实现一个函数,给该函数一个DOM节点,函数访问其所有子元素(所有子元素,不仅仅是直接子元素),每次访问子元素的时候,并为其传一个callback?//访问一个DOM tree,是一个经典的深度优先搜索的算法function Traverse(DOM,callback) { callback(DOM); var list = DOM.children; Array.prototype.forEach.apply(list,(item)=>{ Traverse(item,callback); //递归 })}4.介绍一下对webpack的认识?WebPack 是一个模块打包工具,可以使用WebPack管理模块依赖,并编绎输出模块们所需的静态文件。它能够很好地管理、打包Web开发中所用到的HTML、javaScript、CSS 以及各种静态文件(图片、字体等),让开发过程更加高效。对于不同类型的资源,webpack 有对应的模块加载器。webpack 模块打包器会分析模块间的依赖关系,最后 生成了优化且合并后的静态资源。webpack的两大特色:1)code splitting(可以自动完成)2)loader 可以处理各种类型的静态文件,并且支持串联操作webpack 是以commonJS的形式来书写脚本,但对 AMD/CMD 的支持也很全面,方便旧项目进行代码迁移。webpack具有requireJs和browserify的功能,但仍有很多自己的新特性:1) 对 CommonJS 、 AMD 、ES6的语法做了兼容2) 对js、css、图片等资源文件都支持打包3) 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对CoffeeScript、ES6的支持4) 有独立的配置文件webpack.config.js5) 可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间6) 支持 SourceUrls 和 SourceMaps,易于调试7) 具有强大的Plugin接口,大多是内部插件,使用起来比较灵活8)webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快5.关于HTTP2.0的认识HTTP/2引入了“服务端推(server push)”的概念,它允许服务端在客户端需要数据之前就主动地将数据发送到客户端缓存中,从而提高性能。HTTP/2提供更多的加密支持,HTTP/2使用多路技术,允许多个消息在一个连接上同时交差。它增加了头压缩(header compression),因此即使非常小的请求,其请求和响应的header都只会占用很小比例的带宽。6.对AMD和Commonjs的理解?CommonJS是服务器端模块的规范,nodejs采用了这个规范。CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。AMD推荐的风格通过返回一个对象做为模块对象,CommonJS的风格通过对module.exports或exports的属性赋值来达到暴露模块对象的目的。7.mongoDB和MySQL的区别?MySQL是传统的关系型数据库,MongoDB则是非关系型数据库mongodb以JSON结构(二进制)进行存储,对海量数据存储有着很明显的优势。对比传统关系型数据库,NoSQL有着非常显著的性能和扩展性优势,与关系型数据库相比,MongoDB的优点有: ①弱一致性(最终一致),更能保证用户的访问速度: ②文档结构的存储方式,能够更便捷的获取数据。8.讲讲304缓存的原理?服务器首先产生ETag,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器,要求服务器验证其(客户端)缓存。304是HTTP状态码,服务器用来标识这个文件没修改,不返回内容,浏览器在接收到个状态码后,会使用浏览器已缓存的文件。客户端请求一个页面A。 服务器返回页面A,并在给A加上一个ETag。 客户端展现该页面,并将页面连同ETag一起缓存。 客户再次请求页面A,并将上次请求时服务器返回的ETag一起传递给服务器。 服务器检查该ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304(未修改——Not Modified)和一个空的响应体。9.用node模拟客户端发起请求?var http = require(“http”);var request = http.request({ host:“localhost”, port:“8080”, path:"/request", method:“post”},function(res){ res.on(“data”,function(chunk){ console.log(chunk.toString()); });});request.write(“user=zhang&pass=111”);request.end(“请求结束”);//结束本次请求10.CommonJS 中的 require/exports 和 ES6 中的 import/export 区别?CommonJS 模块的重要特性是加载时执行,即脚本代码在 require 的时候,就会全部执行。一旦出现某个模块被”循环加载”,就只输出已经执行的部分,还未执行的部分不会输出。ES6 模块是动态引用,如果使用 import 从一个模块加载变量,那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。import/export 最终都是编译为 require/exports 来执行的。CommonJS 规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports )是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性。export 命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。 ...

February 12, 2019 · 2 min · jiezi

大厂2020届实习生笔试题

编程题:1.输入某年某月某日,判断这一天是这一年的第几天? // 判断是否为闰年 function isRun(a) { return a % 4 == 0 && a % 100 != 0 || a % 400 == 0 } // 判断是这一年的第几天 function daysNum(year,month,day) { var months = [31,28,31,30,31,30,31,31,30,31,30,31]; if(month ==1) { return day; } for(var i = 0; i < month-1; i++) { day += months[i] } if(isRun(year) && month >2) { day ++ } return day }2.寻找多数元素设计一个方法,在一个数组中寻找占大多数的元素(如果存在的话),如果这样的元素不存在,就输出“没有元素占大多数”。占大多数的元素的定义为:如果一个数组A的长度为n,某一个元素在数组中的数量大于n/2,这个元素即为占大多数的元素。(简单推理可知,一个数组最多有一个元素为占大多数的元素)附加要求:时间复杂度越小越好例子:输入:[3,3,4,2,4,4,2,4,4]输出:4 // 一共5个4,超过数组长度的一半Input: [3,3,4,2,4,4,2,4,]Output:没有元素占大多数 function SelectNum(arr) { let len = arr.length; var json = {}; for (let i = 0; i < len; i++) { if (json[arr[i]] == undefined) { json[arr[i]] = 1; } else { json[arr[i]] += 1 } } for (let a in json) { if (json[a] > len / 2) { return a } } return ‘没有元素占大多数’ }3.请有缘人指点。。 ...

February 12, 2019 · 1 min · jiezi

刷前端面经笔记(八)

1.apply,call,bind有什么区别?三者都可以把一个函数应用到其他对象上,apply,call是直接执行函数调用,bind是绑定,执行需要再次调用。 apply和call的区别是apply接受数组作为参数,而call是接受逗号分隔的无限多个参数列表。代码如下:function Person() { } Person.prototype.sayName() { alert(this.name); } var obj = {name: ‘michaelqin’}; // 注意这是一个普通对象,它不是Person的实例 // 1) apply Person.prototype.sayName.apply(obj, [param1, param2, param3]); // 2) call Person.prototype.sayName.call(obj, param1, param2, param3); // 3) bind var liaoke = Person.prototype.sayName.bind(obj); liaoke ([param1, param2, param3]); // bind需要先绑定,再执行 liaoke (param1, param2, param3); // bind需要先绑定,再执行2.介绍一下defineProperty,hasOwnProperty,isEnumerableObject.defineProperty(obj,prop,descriptor)用来给对象定义属性,有value,writeable,enumerable,set/get,configurable,hasOwnProperty用于检查某一属性是不是存在于对象本身,isEnumerable用来检测某一属性是否可遍历,也就是能不能用for…in循环来取到。3.JS常用设计模式的实现思路(单例、工厂、代理、装饰、观察者模式等)// 1) 单例: 任意对象都是单例,无须特别处理 var obj = {name: ‘michaelqin’, age: 30}; // 2) 工厂: 就是同样形式参数返回不同的实例 function Person() { this.name = ‘Person1’; } function Animal() { this.name = ‘Animal1’; } function Factory() {} Factory.prototype.getInstance = function(className) { return eval(’new ’ + className + ‘()’); } var factory = new Factory(); var obj1 = factory.getInstance(‘Person’); var obj2 = factory.getInstance(‘Animal’); console.log(obj1.name); // Person1 console.log(obj2.name); // Animal1 // 3) 代理: 就是新建个类调用老类的接口,包一下 function Person() { } Person.prototype.sayName = function() { console.log(‘michaelqin’); } Person.prototype.sayAge = function() { console.log(30); } function PersonProxy() { this.person = new Person(); var that = this; this.callMethod = function(functionName) { console.log(‘before proxy:’, functionName); that.personfunctionName; // 代理 console.log(‘after proxy:’, functionName); } } var pp = new PersonProxy(); pp.callMethod(‘sayName’); // 代理调用Person的方法sayName() pp.callMethod(‘sayAge’); // 代理调用Person的方法sayAge() // 4) 观察者: 就是事件模式,比如按钮的onclick这样的应用. function Publisher() { this.listeners = []; } Publisher.prototype = { ‘addListener’: function(listener) { this.listeners.push(listener); }, ‘removeListener’: function(listener) { delete this.listeners[listener]; }, ’notify’: function(obj) { for(var i = 0; i < this.listeners.length; i++) { var listener = this.listeners[i]; if (typeof listener !== ‘undefined’) { listener.process(obj); } } } }; // 发布者 function Subscriber() { } Subscriber.prototype = { ‘process’: function(obj) { console.log(obj); } }; // 订阅者 var publisher = new Publisher(); publisher.addListener(new Subscriber()); publisher.addListener(new Subscriber()); publisher.notify({name: ‘michaelqin’, ageo: 30}); // 发布一个对象到所有订阅者 publisher.notify(‘2 subscribers will both perform process’); // 发布一个字符串到所有订阅者3.处理字符串常用的十个函数charAt() // 返回在指定位置的字符。concat() // 连接字符串。fromCharCode() // 从字符编码创建一个字符串。indexOf() // 检索字符串。match() // 找到一个或多个正则表达式的匹配。replace() // 替换与正则表达式匹配的子串。search() // 检索与正则表达式相匹配的值。slice() // 提取字符串的片断,并在新的字符串中返回被提取的部分。split() // 把字符串分割为字符串数组。substr() // 从起始索引号提取字符串中指定数目的字符。substring() // 提取字符串中两个指定的索引号之间的字符。toLocaleLowerCase() // 把字符串转换为小写。toLocaleUpperCase() // 把字符串转换为大写。toLowerCase() // 把字符串转换为小写。toUpperCase() // 把字符串转换为大写。toString() // 返回字符串。valueOf() // 返回某个字符串对象的原始值。4.如何判断一个变量是对象还是数组function isObjArr(variable){ if (Object.prototype.toString.call(value) === “[object Array]”) { console.log(‘value是数组’); }else if(Object.prototype.toString.call(value)===’[object Object]’){//这个方法兼容性好一点 console.log(‘value是对象’); }else{ console.log(‘value不是数组也不是对象’) }}// 注意:千万不能使用typeof来判断对象和数组,因为这两种类型都会返回"object"。5.ES5的继承和ES6的继承有什么区别?ES5的继承是通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。ES6的继承机制实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。具体为ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其调用。如果不调用super方法,子类得不到this对象。注意:super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可使用this关键字,否则报错。6.下面的ul,如何点击每一列的时候alert其index?(闭包) <ul id=“test”> <li>这是第一条</li> <li>这是第二条</li> <li>这是第三条</li> </ul>// 方法一:var lis=document.getElementById(’test’).getElementsByTagName(’li’);for(var i=0;i<3;i++){lis[i].index=i;lis[i].onclick=function(){alert(this.index);};}//方法二:var lis=document.getElementById(’test’).getElementsByTagName(’li’);for(var i=0;i<3;i++){lis[i].index=i;lis[i].onclick=(function(a){return function() {alert(a);}})(i);}7.对于MVVM的理解MVVM 是 Model-View-ViewModel 的缩写。Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。View 代表UI 组件,它负责将数据模型转化成UI 展现出来。ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的,因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。8.解释Vue的生命周期Vue实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom->渲染、更新->渲染、销毁等一系列过程,称之为Vue的生命周期。Vue的生命周期包括:beforeCreate(创建前)在数据观测和初始化事件还未开始,created(创建后)完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来;beforeMount(载入前)在挂载开始之前被调用,相关的render函数首次被调用,实例已完成以下的配置:编译模板,把data里面的数据和模板生成html,注意此时还没有挂载html到页面上;mounted(载入后)在el被新创建的vm.$el替换,并挂载到实例上去之后调用,实例已完成以下配置:用上面编译好的html内容替换el属性指向的DOM对象,完成模板中的html渲染到html页面中,此过程中进行ajax交互。beforeUpdate(更新前)在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前,可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。updated(更新后)在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环,该钩子在服务器渲染期间不被调用。beforeDestroy(销毁前)在实例销毁之前调用,实例仍然完全可用。destroyed(销毁后)在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。9.为什么使用Node.js,它有哪些优缺点?优点:事件驱动,通过闭包很容易实现客户端的生命活期。不用担心多线程,锁,并行计算的问题V8引擎速度非常快对于游戏来说,写一遍游戏逻辑代码,前端后端通用缺点:node.js更新很快,可能会出现版本兼容node.js还不算成熟,还没有大制作node.js不像其他的服务器,对于不同的链接,不支持进程和线程操作10.什么是错误优先的回调函数?错误优先(Error-first)的回调函数(Error-First Callback)用于同时返回错误和数据。第一个参数返回错误,并且验证它是否出错;其他参数返回数据。fs.readFile(filePath, function(err, data){ if (err) { // 处理错误 return console.log(err); } console.log(data);});11.使用npm有哪些好处?通过npm,你可以安装和管理项目的依赖,并且能够指明依赖项的具体版本号。对于Node应用开发而言,可以通过package.json文件来管理项目信息,配置脚本,以及指明依赖的具体版本。12.在JavaScript源文件的开头包含 use strict 有什么意义和好处?use strict 是一种在JavaScript代码运行时自动实行更严格解析和错误处理的方法。(严格模式)将值分配给一个未声明的变量会自动创建该名称的全局变量。这是JavaScript中最常见的错误之一。在严格模式下,这样做的话会抛出错误。消除 this 强制。当检测到对象(例如,var object = {foo: “bar”, foo: “baz”};)中重复命名的属性,或检测到函数中(例如,function foo(val1, val2, val1){})重复命名的参数时,严格模式会抛出错误,因此捕捉几乎可以肯定是代码中的bug可以避免浪费大量的跟踪时间。比eval() 更安全。13.vuejs与angularjs以及react的区别?与AngularJS的区别相同点:都支持指令:内置指令和自定义指令。都支持过滤器:内置过滤器和自定义过滤器。都支持双向数据绑定。都不支持低端浏览器。不同点:1.AngularJS的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观。2.在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢。Vue.js使用基于依赖追踪的观察并且使用异步队列更新。所有的数据都是独立触发的。对于庞大的应用来说,这个优化差异还是比较明显的。与React的区别相同点:React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用。中心思想相同:一切都是组件,组件实例之间可以嵌套。都提供合理的钩子函数,可以让开发者定制化地去处理需求。都不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载。在组件开发中都支持mixins的特性。不同点:React依赖Virtual DOM,而Vue.js使用的是DOM模板。React采用的Virtual DOM会对渲染出来的结果做脏检查。Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM。14.标签keep-alive的作用是什么?<keep-alive></keep-alive> 包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染。15.WeakMap 和 Map 的区别?WeakMap 结构与 Map 结构基本类似,唯一的区别是它只接受对象作为键名( null 除外),不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。WeakMap 最大的好处是可以避免内存泄漏。一个仅被 WeakMap 作为 key 而引用的对象,会被垃圾回收器回收掉。WeakMap 拥有和 Map 类似的 set(key, value) 、get(key)、has(key)、delete(key) 和 clear() 方法, 没有任何与迭代有关的属性和方法。16.http和https的基本概念?http: 超文本传输协议,是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。https: 是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。https协议的主要作用是:建立一个信息安全通道,来确保数组的传输,确保网站的真实性。17.git fetch和git pull的区别?git pull:相当于是从远程获取最新版本并merge到本地git fetch:相当于是从远程获取最新版本到本地,不会自动merge18.介绍一下对浏览器内核的理解?主要分成两部分:渲染引擎(layout engineer或Rendering Engine)和JS引擎。渲染引擎:负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等),以及计算网页的显示方式,然后会输出至显示器或打印机。浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核。JS引擎:解析和执行javascript来实现网页的动态效果。最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎。19.什么是微格式微格式(Microformats)是一种让机器可读的语义化XHTML词汇的集合,是结构化数据的开放标准。是为特殊应用而制定的特殊格式优点:将智能数据添加到网页上,让网站内容在搜索引擎结果界面可以显示额外的提示。20.数据绑定基本的实现 // 实现一个方法,可以给 obj 所有的属性添加动态绑定事件,当属性值发生变化时会触发事件let obj = { key_1: 1, key_2: 2}function func(key) { console.log(key + ’ 的值发生改变:’ + this[key]);}bindData(obj, func);obj.key_1 = 2; // 此时自动输出 “key_1 的值发生改变:2"obj.key_2 = 1; // 此时自动输出 “key_2 的值发生改变:1"答案:function bindData(obj, fn) { for (let key in obj) { Object.defineProperty(obj, key, { set(newVal) { if (this.value !== newVal) { this.value = newVal; fn.call(obj, key); } }, get() { return this.value; } }) }}20.数据结构处理// 有一个祖先树状 json 对象,当一个人有一个儿子的时候,其 child 为其儿子对象,如果有多个儿子,child 为儿子对象的数组。请实现一个函数,找出这个家族中所有有多个儿子的人的名字(name),输出一个数组。列子:// 样例数据let data = { name: ‘jack’, child: [ { name: ‘jack1’ }, { name: ‘jack2’, child: [{ name: ‘jack2-1’, child: { name: ‘jack2-1-1’ } }, { name: ‘jack2-2’ }] }, { name: ‘jack3’, child: { name: ‘jack3-1’ } } ]}// 答案:// 用递归function findMultiChildPerson(data) { let nameList = []; function tmp(data) { if (data.hasOwnProperty(‘child’)) { if (Array.isArray(data.child)) { nameList.push(data.name); data.child.forEach(child => tmp(child)); } else { tmp(data.child); } } } tmp(data); return nameList;}// 不用递归function findMultiChildPerson(data) { let list = [data]; let nameList = []; while (list.length > 0) { const obj = list.shift(); if (obj.hasOwnProperty(‘child’)) { if (Array.isArray(obj.child)) { nameList.push(obj.name); list = list.concat(obj.child); } else { list.push(obj.child); } } } return nameList;} ...

February 12, 2019 · 3 min · jiezi

PHP面试之面向对象(1)

PHP面试专栏正式起更,每周一、三、五更新,提供最好最优质的PHP面试内容。PHP中面向对象常考的知识点有以下7点,我将会从以下几点进行详细介绍说明,帮助你更好的应对PHP面试常考的面向对象相关的知识点和考题。整个面向对象文章的结构涉及的内容模块有:一、面向对象与面向过程有什么区别?二、面向对象有什么特征?三、什么是构造函数和析构函数?四、面向对象的作用域范围有哪几种?五、PHP 中魔术方法有哪些?六、什么是对象克隆?七、this、self和parent的区别是什么?八、抽象类与接口有什么区别与联系?九、PHP面向对象的常考面试题讲解关于PHP面向对象的内容将会被分为三篇文章进行讲解完整块内容,第一篇主要讲解一到四点内容,第二篇主要讲解五到八的内容,第三篇围绕第九点进行讲解。以下正文的内容都来自《PHP程序员面试笔试宝典》书籍,如果转载请保留出处:一、面向对象与面向过程有什么区别?面向对象是当今软件开发方法的主流方法之一,它是把数据及对数据的操作方法放在一起,作为一个相互依存的整体,即对象。对同类对象抽象出其共性,即类,类中的大多数数据,只能被本类的方法进行处理。类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。程序流程由用户在使用中决定。例如,站在抽象的角度,人类具有身高、体重、年龄、血型等一些特称,人类会劳动、会直立行走、会吃饭、会用自己的头脑去创造工具等这些方法,人类仅仅只是一个抽象的概念,它是不存在的实体,但是所有具备人类这个群体的属性与方法的对象都称为人,这个对象人是实际存在的实体,每个人都是人这个群体的一个对象。而面向过程是一种以事件为中心的开发方法,就是自顶向下顺序执行,逐步求精,其程序结构是按功能划分为若干个基本模块,这些模块形成一个树状结构,各模块之间的关系也比较简单,在功能上相对独立,每一模块内部一般都是由顺序、选择和循环三种基本结构组成,其模块化实现的具体方法是使用子程序,而程序流程在写程序时就已经决定。例如五子棋,面向过程的设计思路就是首先分析问题的步骤:第一步,开始游戏;第二步,黑子先走;第三步,绘制画面;第四步,判断输赢;第五步,轮到白子;第六步,绘制画面;第七步,判断输赢;第八步,返回步骤二;第九步,输出最后结果。把上面每个步骤用分别的函数来实现,就是一个面向过程的开发方法。具体而言,二者主要有以下几个方面的不同之处。1)出发点不同。面向对象是用符合常规思维方式来处理客观世界的问题,强调把问题域的要领直接映射到对象及对象之间的接口上。而面向过程方法则不然,它强调的是过程的抽象化与模块化,它是以过程为中心构造或处理客观世界问题的。2)层次逻辑关系不同。面向对象方法则是用计算机逻辑来模拟客观世界中的物理存在,以对象的集合类作为处理问题的基本单位,尽可能地使计算机世界向客观世界靠拢,以使问题的处理更清晰直接,面向对象方法是用类的层次结构来体现类之间的继承和发展。面向过程方法处理问题的基本单位是能清晰准确地表达过程的模块,用模块的层次结构概括模块或模块间的关系与功能,把客观世界的问题抽象成计算机可以处理的过程。3)数据处理方式与控制程序方式不同。面向对象方法将数据与对应的代码封装成一个整体,原则上其他对象不能直接修改其数据,即对象的修改只能由自身的成员函数完成,控制程序方式上是通过“事件驱动”来激活和运行程序。而面向过程方法是直接通过程序来处理数据,处理完毕后即可显示处理结果,在控制程序方式上是按照设计调用或返回程序,不能自由导航,各模块之间存在着控制与被控制、调用与被调用。4)分析设计与编码转换方式不同。面向对象方法贯穿软件生命周期的分析、设计及编码之间是一种平滑过程,从分析到设计再到编码是采用一致性的模型表示,即实现的是一种无缝连接。而面向过程方法强调分析、设计及编码之间按规则进行转换,贯穿软件生命周期的分析、设计及编码之间,实现的是一种有缝的连接。二、面向对象有什么特征?面向对象的主要特征有抽象、继承、封装和多态。1)抽象。抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。2)继承。继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且子类可以修改或增加新的方法使之更适合特殊的需要。3)封装。封装是指将客观事物抽象成类,每个类对自身的数据和方法实行保护。类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的信息进行隐藏。4)多态。多态是指允许不同类的对象对同一消息做出响应。多态包括参数化多态和包含多态。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好地解决了应用程序函数同名问题。自己整理了一篇“PHP和MySQL面试中爱考的10道题”的文章,关注公众号:“琉忆编程库”,回复:“10”,我发给你。三、什么是构造函数和析构函数?1.构造函数在PHP5之前的版本,构造函数的名字必须与类的名字相同,而从PHP5开始,开发者可以定义一个名为__construct的方法作为构造函数。构造函数的作用就是当类被实例化的时候会被自动调用,因此构造函数主要用于做一些初始化的工作。使用__construct作为构造函数名字的一个好处是,当类名修改的时候,不需要修改构造函数的名字。它的声明形式为void __construct ([ mixed $args [, $… ]] )在C++语言中,子类的构造函数会隐式地调用父类的无参数的构造函数。但是在PHP中,子类的构造函数不会隐式地去调用父类的构造函数,需要开发者通过parent::__construct()来显式地去调用父类的构造函数。当子类没有定义构造函数的时候,它会继承父类的构造函数,但前提是父类的构造函数不能被定义为private。使用示例如下:<?php class BaseClass { function __construct() { print “Base constructor\n”; } } class SubClass extends BaseClass { function __construct() { parent::__construct(); print “Sub constructor\n”; } } // 会调用父类构造函数 $obj = new BaseClass(); //调用子类构造函数,子类构造函数会去调用父类构造函数 $obj = new SubClass();?>程序的运行结果为Base constructor Base constructorSub constructor从上面的讲解中可以发现,从PHP5开始多了一种构造函数定义的方法。为了实现不同版本PHP代码的兼容,在PHP5的类中找不到 __construct() 函数并且也没有从父类继承一个的话,那么它就会尝试寻找旧式的构造函数(与类同名的函数)。这种兼容的方法存在一个风险:在PHP5之前的版本中开发的类中已有一个名为 __construct() 的方法却被用于其他用途时,PHP5的类会认为这是一个构造函数,从而当类实例化时自动执行这个方法。从 PHP 5.3.3 开始,在命名空间中,与类名同名的方法不再作为构造函数。这一改变不影响不在命名空间中的类。2.析构函数析构函数是在PHP5引入的,它的作用与调用时机和构造函数刚好相反,它在对象被销毁时自动执行。析构函数__destruct()结构形式如下:function __destruct(){ /* 类的初始化代码*/} 需要注意的是,析构函数是由系统自动调用的,因此,它不需要参数。默认情况下,系统仅释放对象属性所占用的内存,并不销毁在对象内部申请的资源(例如,打开文件、创建数据库的连接等),而利用析构函数在使用一个对象之后执行代码来清除这些在对象内部申请的资源(关闭文件、断开与数据库的连接)。与构造函数类似,如果想在子类中调用父类的析构函数,那么需要显式地调用:parent::__destruct()。如果子类没有定义析构函数,那么它会继承父类的析构函数。当对象不再被引用时,将调用析构函数。如果要明确地销毁一个对象,那么可以给指向对象的变量不分配任何值,通常将变量赋值为NULL或者用unset()函数。示例代码如下:<?php class des{ function __destruct(){ echo “对象被销毁,执行析构函数<br>”; } } $p=new des(); /* 实例化类 / echo “程序开始<br>”; unset($p); / 销毁变量$p */ echo “程序结束”;?>四、面向对象的作用域范围有哪几种?在PHP5中,类的属性或者方法主要有public、protected和private三种类作用域,它们的区别如下:1)public(公有类型)表示全局,类内部、外部和子类都可以访问。默认的访问权限为public,也就是说,如果一个方法没有被public、protected或private修饰,那么它默认的作用域为public。2)protected(受保护类型)表示受保护的,只有本类或子类可以访问。在子类中,可以通过self::var或self::method访问,也可以通过parent::method来调用父类中的方法。在类的实例化对象中,不能通过$obj->var来访问protected类型的方法或属性。3)private(私有类型)表示私有的,只有本类内部可以使用。该类型的属性或方法只能在该类中使用,在该类的实例、子类、子类的实例中都不能调用私有类型的属性和方法。预告:PHP面试常考内容之面向对象(2)将于本周三(2019.2-13)更新。以上内容摘自《PHP程序员面试笔试宝典》书籍,该书已在天猫、京东、当当等电商平台销售。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,将不断完善追求极致,感谢你们的支持。 ...

February 11, 2019 · 1 min · jiezi

刷前端面经笔记(七)

1.描述一下渐进增强和优雅降级优雅降级(graceful degradation):一开始就构建站点的完整功能,然后针对浏览器测试和修复。渐进增强(progressive enhancement):一开始只构建站点的最少特性,然后不断针对各浏览器追加功能。2.为什么利用多个域名来请求网络资源会更有效?动静分离请求,使用不同的服务器处理请求,提高效率;突破浏览器的并发限制,同一时间针对同一域名下的请求有一定的数量限制。节约主域名的连接数,从而提高客户端网络带宽的利用率,优化页面响应。3.HTML5有哪些新特性、移除了哪些元素?1)绘画标签canvas;2)用于媒介回放的video和audio元素;3)本地离线存储localStorage长期存储数据,浏览器关闭后数据不丢失;4)sessionStorage的数据在浏览器关闭后自动删除;5)语义化更好的内容元素,比如article、footer、header、nav、section;6)表单控件,calendar、data、time、email、url、search;7)webworker、websocket、Geolocation;移除的元素:1)纯表现的元素:basefont、big、center、font、s、strike、tt2)对可用性产生负面影响的元素:frame、frameset、noframes4.display:none;与visibility:hidden;的区别?相同点:它们都能让元素不可见‘不同点:display:none;会让元素完全从渲染树中消失,渲染的时候不占据任何空间;visibility:hidden;不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见;display:none;是非继承属性,子孙节点的消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示;visibility:hodden;是继承属性,子孙节点的消失由于继承了hidden,通过设置visibility:visible;可以让子孙节点显示;修改常规流中元素的display通常会造成文档重排。修改visibility属性只会造成本元素的重绘;读屏器不会读取display:none;元素内容;会读取visibility:hidden;元素内容;5.CSS去掉inline-block元素间隙的几种方法?间隙是怎么来的:间隙是由换行或者回车导致的;只要把标签写成一行或者标签没有空格,就不会出现间隙;去除方法:方法一:元素间的间隙出现的原因,是元素标签之间的空格,把空格去掉间隙就会消失<div class=“itlike”> <span>lhh</span><span>lhh</span></div>方法二:利用HTML注释标签<div class=“demo”> <span>lhh</span><!– –><span>lhh</span></div>方法三:取消标签闭合<div class=“demo”> <span>lhh <span>lhh <span>lhh <span>lhh</div>方法四:在父容器上使用font-size:0;可以消除间隙<div class=“demo”> <span>lhh</span> <span>lhh</span> <span>lhh</span> <span>lhh</span></div>.demo {font-size: 0;}6.input标签的type种类button、checkbox、file、hidden、image、password、radio、reset、submit、text

February 11, 2019 · 1 min · jiezi

如何得到面试官的准确评估

引言2019 年 1 月底,出去面试,因为有了半年经验,且对自己的技术有较为准确的认知,所以时常会去思考一个问题:面试官要怎样才能够看出来我有多少水平呢?断断续续的想过,也面试过,直到最近,才有了大致的思路,所以写了这篇文章。提示:笔者并不是 HR,也没有去查看相应的文献,所做的思考只是自己的推测,如有不当之处,欢迎指教。本文试图回答一个问题:作为求职者,如何确保自己的能力,得到了面试官的准确评估?本文给出的方案大概是(如果你没有看到流程图,说明此平台 markdown 不支持,请直接看正文):start=>start: 面试开始selfIntro=>operation: 自我介绍beAsked=>operation: 被提问understoodQuestion=>condition: 理解问题?transformQuestion=>operation: 尝试/请求转换提问方式answerQuestion=>operation: 回答问题isCurInterviewGoingOver=>condition: 本轮面试就要结束了?askForAbilityLevel=>operation: 询问面试官眼中自己的能力等级hasGap=>condition: 调整自己的评估后,仍与面试官的评估有差距?willingToCorrect=>condition: 愿意纠正面试官?tryCorrect=>operation: 尝试纠正面试官的评估end=>end: 面试结束start->selfIntro->beAsked->understoodQuestionunderstoodQuestion(yes)->answerQuestion->isCurInterviewGoingOverunderstoodQuestion(no)->transformQuestion->beAskedisCurInterviewGoingOver(yes)->askForAbilityLevel->hasGapisCurInterviewGoingOver(no)->beAskedhasGap(yes)->willingToCorrecthasGap(no)->endwillingToCorrect(yes)->tryCorrect->beAskedwillingToCorrect(no)->end正文开始:抽象面试我认为面试包含几个部分:薪资假设:公司有一个能力等级体系(A,B,C ,D,E,F,,,),直接与薪资挂钩。如果要得出求职者的薪资,就要得到他的能力等级,最终就需要在 n 个维度上考察求职者的知识广度和深度能力检测:求职者和面试官,需要竭尽所能的,让面试官看到,求职者在 n 个维度上的知识广度和深度,以得出求职者最接近的能力等级其他检测:参考能力检测之外的东西以决定是否签订合同,譬如,是否认同公司文化等(这一步可能在 能力检测 前,也可能在 能力检测 后)提炼一下,面试就做了一件事检测求职者在 n 个维度上的知识广度和深度,以得出求职者的能力等级不过,这个需要求职者和面试官的共同努力。为什么需要求职者的努力?因为对于求职者来说,通常我们的面试官都不够完美(反之亦如此)。比如说,很多技术岗都是技术部门的同事或者大佬在面试,他们可能都没有接受过专业的 HR 培训有的面试官,可能面试前都没有一份列表,记录着自己要考察的维度、广度和深度有的面试官,可能喜欢按照自己的方式去提问,又不愿意去引导求职者……遇到这种情况时,我建议求职者在适当的时候,去引导、提示面试官,以尽可能的去实现我们的目标,而不是被动接受,甚至回头去社交媒体上吐槽、开骂。求职者需要做什么?适时引导面试官我这里说的,引导,并不是指,把面试官往自己擅长的地方带。面试是一场沟通,如果双方提问回答的方式不对路,那么就很难让面试官准确认识到求职者的能力等级。以我个人为例,我不太能接受一些很宽泛很广的问题,譬如:谈谈你对……的理解?你对……有多少了解?口述有点复杂的代码,然后问你结果反之,我更喜欢:你用过……么?它的原理是什么?…… 有什么缺(优)点?你看这段代码的结果是什么?如果遇到自己不对路的问题,求职者可能要尝试跟面试官沟通,如何去转化这个问题,举几个不太恰当的例子:你是不是想问….?你看能换个方式问么……?这个代码能不能写在纸上,口述的话,容易弄晕?当然,运气好,遇到比较好的面试官,也许会主动去引导求职者,把问题拆开,看看求职者到底知道哪些部分?甚至会去主动询问这个求职者在哪些维度比较擅长,哪些维度比较薄弱?然后再看强的地方有多强,弱的地方有多弱。不过对于求职者而言,这要看运气了,在没有运气时,尽力去尝试引导面试官,求一个自己和面试官的交集。放弃询问市场价以我的经验,二面或总监面的时候,即使求职者询问自己的市场价,面试官通常也会含糊其辞,直到最近我才想通了为什么。因为:总监很有可能并不知道薪酬部门会给你多少薪资,无论说高了还是说低了,都有可能给自己带来不必要的麻烦总监更有可能知道的是,求职者的能力等级。因为总监给求职者定级的一大方式就是,从公司的同事里找一个和求职者水平最接近的人,然后以该同事的水平作为参考,给求职者定级。因为定级是总监的任务,薪酬往往不是HR 或者薪酬部门会根据总监的定级给求职者定价,这个定价一般都是参考公司内部的定价系统来说的所以,对于求职者而言,没有必要问自己的市场价,更应该关注的是,面试官对自己的能力等级的认定,是否准确。如果面试官对自己的能力等级认定的没问题,那么最后 HR 给出的薪资,就可以作为自己市场价的一个参考。那么求职者如何确定面试官对自己的能力等级认定的有没有问题呢?求职者需要主动询问自己的能力等级。询问和纠正自己的能力等级我建议求职者在被问期望薪资之前或者每一轮面试结束之前,最好都问面试官要一份,对自己能力等级的总结,举个例子:你看你能不能对我的能力做个总结?(譬如,哪些维度你还不太满意?哪些维度你觉得还不错?)如果面试官在考察时,对于维度、广度和深度,把握的不太好,那么他就可能会意识到自己的问题,比如:我好像把 C 维度给漏了我好像在 A 维度问的有点深了,实际上没有必要,D 维度应该问的深些…如果面试官没有意识到这个问题,或者没觉得自己把握的有误,没关系。作为求职者,仔细听面试官对自己各维度的评估,如果跟自己的评估有偏差,可以抓住这个机会,指出这个偏差,并希望得到正确的评估。这样求职者就有机会与面试官再次进入能力检测的环节,直到双方就能力检测达成一致为止。这里的评估有偏差,包括又不限于下列情况:我认为某个比较重要的维度,面试官没有考到?我认为我某个维度被低估了?我认为我某个维度被高估了?(纠正这个,需要承担降级的风险)?我认为面试官对于重要的维度考的太少,不重要的维度考的太多?如果求职者的认知正确,那么利用这个问题,就可以帮助面试官更准确的认识求职者的能力等级(通常是提升定级)。如果求职者的认知错误,一般也不会降低自己的定级。总结回到本文试图回答的问题,作为求职者,如何确保自己的能力得到了面试官的准确评估?我给出的方案就是:在被提问时,适时引导面试官放弃询问自己的市场价询问和尝试纠正面试官眼中自己的能力等级,直到和面试官达成一致,或者放弃纠正也可以再次参考前面的流程图,比较直观。留言向以往被我问过自己市场价的各位大佬道歉,,,流程图是用 markdown 写的,具体语法请自行搜索

January 31, 2019 · 1 min · jiezi

前端面试系列之目录

JS前端面试总结vue系列之面试总结angularjs面试总结

January 31, 2019 · 1 min · jiezi

前端进击的巨人(六):知否知否,须知this

常见this的误解指向函数自身(源于this英文意思的误解)指向函数的词法作用域(部分情况)this的应用环境全局环境无论是否在严格模式下,全局执行环境中(任何函数体外部)this都指向全局对象var name = ‘以乐之名’;this.name; // 以乐之名函数(运行内)环境函数内部,this的值取决于函数被调用的方式(被谁调用)var name = ‘无名氏’;function getName() { console.log(this.name);}getName(); // 无名氏 调用者是全局对象var myInfo = { name: ‘以乐之名’, getName: getName};myInfo.getName(); // 以乐之名 调用者是myInfo对象this的正解"this的指向是在运行时进行绑定的,而不是代码书写(函数声明)时确定!!!““看谁用”,this的指向取决于调用者,这也是很多文章提到过的观点。“谁调用,this指向谁”,只是这句话稍有偏颇,某些情况不见得都适用。生活栗子:你的钱并不一定是你的钱,只有当你使用消费了才是你的钱 。(“看谁用”),借出去的钱就不是你的了。。。回到征文,我们先通过栈,来理解什么是调用位置:JavaScript中函数的调用是以栈的方式来存储,栈顶是正在运行的函数,函数调用时入栈,执行完成后出栈。function foo() { // 此时的栈:全局 -> foo,调用位置在foo bar();}function bar() { // 此时的栈:全局 -> foo -> bar,调用位置在bar baz();}function baz() { // 此时的栈:全局 -> foo -> bar -> baz,调用位置在baz // …}foo();代码中虽然函数存在多次嵌套使用,但处于栈顶的只有正在执行的函数,也即调用者只有顶层的那一个(或最后一个)。理清调用位置(调用者)有助于我们理解this。this的绑定规则默认绑定(函数单独调用)隐式绑定(作为对象的属性方法调用,带有执行上下文)显示绑定(call/apply/bind)new绑定(new创建实例)箭头函数绑定(ES6新增,基于词法作用域)默认绑定下(函数单独调用)区分严格模式非严格模式,this会指向全局对象(浏览器全局对象是window,NodeJS全局对象是global);严格模式,this指向undefined// 非严格模式function getName() { console.log(this.name); // this指向全局对象}getName(); // “",并不会报错,如果外部有全局变量name,则会输出对应值// 严格模式function getName() { “use strict” console.log(this.name); // this指向undefined}getName(); // TypeError: Cannot read property ’name’ of undefinedTIPS: 严格模式中,对函数中this的影响,只在函数内声明了严格模式才会存在,如果是调用时声明严格模式则不会影响。function getName() { console.log(this.name);}// 调用时声明严格模式"use strict”;getName(); // ““隐式绑定隐式绑定中,函数一般作为对象的属性调用,带有调用者的执行上下文。因此this值取决于调用者的上下文环境。如果存在多层级属性引用,只有对象属性引用链中最顶层(最后一层)会影响调用位置,而this的值取决于调用位置。文章开头以栈来理解调用者的例子。function getName() { return this.name;}var myInfo = { name: ‘以乐之名’, getName: getName};var leader = { name: ‘大神组长’ man: myInfo};leader.man.getName(); // ‘以乐之名’// man 指向 myInfo,最顶层(最后一层)对象为 myInfoapply/call的区别apply/call方法两者类似,都可以显示绑定this,两者的区别是参数传递的方式不同。apply/call第一个参数都为要指定this的对象,不同的是apply第二个参数接受的是一个参数数组,而call从第二个参数开始接受的是参数列表。apply语法:func.apply(thisArg, [argsArray])call语法:func.apply(thisArg, arg1, arg2, …)var numbers = [5, 6, 2, 3, 7];// 求numbers的最大值// applyvar max = Math.max.apply(null, numbers);// callvar max = Math.max.apply(null, …numbers); // …展开运算符TIPS: 如果thisArg为原始值(数字,字符串,布尔值),this会指向该原始值的自动包装对象,如Number, String, Boolean等func.apply(1);// func中的this -> Number对象;bind的特别(柯里化的应用)bind是ES5新增的方法,跟apply/call功能一样,可以显示绑定this。bind语法:function.bind(thisArg[, arg1[, arg2[, …]]])bind()方法创建一个新的函数,在调用时设置this关键字为提供的值,并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。– 《Function.prototype.bind() | MDN》“bind与apply/call的区别:apply/call传入this并立即执行函数,而bind传入this则返回一个函数,并不会立即执行,只有调用返回的函数才会执行原始函数”。bind方法是函数柯里化的一种应用,看过上篇《前端进击的巨人(五):学会函数柯里化(curry) 》的小伙伴,应该还记得"函数柯里化的特点:延迟执行,部分传参,返回一个可处理剩余参数的函数”。bind相较apply/call的优点,可以通过部分传参提前对this进行一次"永久绑定”,也就是说this只需绑定一次,省却每次执行都要进行this绑定的操作。function getName() { return this.name;}var myInfo = { name: ‘以乐之名’, job: ‘前端工程师’};var getName = getName.bind(myInfo);getName(); // ‘以乐之名’;getName(); // ‘以乐之名’;// 一次性绑定,之后调用无需再修改thisTIPS: 函数柯里化可以用于参数预设,像一次性操作(判断/绑定)等。有关函数柯里化的详解,请回阅:《前端进击的巨人(五):学会函数柯里化(curry) 》。构造函数中的this通过new操作符可以实现对函数的构造调用。JavaScript中本身并没有"构造函数”,一个函数如果没有使用new操作符调用,那么它就是个普通函数,new Func()实际上是对函数Func的"构造调用"。在了解构造函数中的this前,有必要先了解下new实例化对象的过程。new实例过程创建(构造)一个全新的空对象这个新对象会被执行"原型"链接(新对象的__proto__会指向函数的prototype)构造函数的this会指向这个新对象,并对this属性进行赋值如果函数没有返回其他对象,则返回这个新对象(注意构造函数的return,一般不会有return)// 正常不带return的构造函数function People(name, sex) { this.name = name; this.sex = sex;}var man = new People(‘亚当’, ‘男’);var woman = new People(‘夏娃’, ‘女’);// 实例化对象成功// 构造函数带了returnfunction People(name, sex) { return 1; // 返回的是Number对象}function People(name, sex) { return ‘hello world’; // 返回的是String对象}function People(name, sex) { return function() {}}function People(name, sex) { return {};}// 以上并未正确实例化对象构造函数自定义return,会造成new无法完成正确的实例化操作。如果返回值为基本类型,则返回其包装对象Number/String/Bollean。TIPS: 原型链中的this指向其实例化的对象People.prototype.say = function() { console.log(我的名字:${this.name});};var man = new People(‘亚当’, ‘男’);man.say(); // 我的名字:亚当this绑定规则的优先级显示绑定 / new绑定 > 隐式绑定 > 默认绑定TIPS: new无法跟apply/call同时使用this判定步骤函数被new操作符使用(new绑定)? YES –> this绑定的是new创建的新对象函数通过call/apply/bind(显示绑定)? YES –> this绑定的是指定的对象函数在某个上下文对象中调用(隐式绑定)? YES –> this绑定的是那个上下文对象默认绑定,严格模式指向undefined,否则指向全局对象ES6的箭头函数(词法作用域的this机制,规则之外)箭头函数的this机制不同于传统的this机制,它采取的是另外一种机制,词法作用域的this判定规则。// 例子一var name = ‘无名氏’;var myInfo = { name: ‘以乐之名’, getName: () => { console.log(this.name); }};var getName = myInfo.getName;window.getName(); // 无名氏myInfo.getName(); // 无名氏// myInfo是在全局环境定义的,因此根据词法作用域,this指向全局对象// 例子二var name = ‘无名氏’;var myInfo = { name: ‘以乐之名’, say: () => { setTimeout(() => { console.log(this.name); }) }};myInfo.say(); // 无名氏// 箭头函数通过作用域链来逐层查找this,最终找到全局变量myInfo,this指向全局对象// 例子三var name = ‘无名氏’;var myInfo = { name: ‘以乐之名’, say: function() => { setTimeout(() => { console.log(this.name); }) }};myInfo.say(); // 以乐之名// 箭头函数找到say: function(){},因此this的作用域来自myInfoTIPS: setTimeout/setInterval/alert的调用者都是全局对象"箭头函数的this始终指向函数定义时的this,而非执行(调用)时的this。箭头函数中的this必须通过作用域链一层一层向外查找,来确定this指向。“扩展:箭头函数的书写规则箭头函数只能用函数表达式,不能用函数声明式写法(不包括匿名函数)// 函数表达式const getName = (name) => { return ‘myName: ’ + name };// 匿名函数setTimeout((name) => { console.log(name);}, 1000)如果参数只有一个,可不加括号();如果没有参数或多个参数需加括号()// 只有一个参数const getName = name => { return myName: ${name};}// 无参数const getName = () => { return ‘myName: ‘以乐之名‘;}// 多参数const getName = (firstName, lastName) => { return myName: ${firstName} ${lastName};}函数体只有一个可不加花括号{}const getName = name => return myName: ${name};函数体没有花括号{},可不写return,会自动返回const getName = name => myName: ${name};参考文档:你不知道的JavaScript(上卷)彻底理解js中this的指向,不必硬背。this|MDN本文首发Github,期待Star!https://github.com/ZengLingYong/blog作者:以乐之名本文原创,有不当的地方欢迎指出。转载请指明出处。 ...

January 29, 2019 · 2 min · jiezi

《剑指offer》11.链表中倒数第k个节点

题目输入一个链表,输出该链表中倒数第k个结点。思路简单思路: 循环到链表末尾找到 length 在找到length-k节点 需要循环两次。优化:设定两个节点,间距相差k个节点,当前面的节点到达终点,取后面的节点。前面的节点到达k后,后面的节点才出发。本题目着重考察代码鲁棒性、容错率: 需要考虑head为null,k为0,k大于链表长度的情况代码 function FindKthToTail(head, k) { if (!head || !k) return null; let front = head; let behind = head; let index = 1; while (front.next) { index++; front = front.next; if (index > k) { behind = behind.next; } } return (k <= index) && behind; }

January 27, 2019 · 1 min · jiezi

金s办公软件web前端笔试题

var arr = []; arr[‘a’] = 1; console.log(arr.length); // A arr[‘4’] = 2; console.log(arr.length); // B arr.length = 0; console.log(arr) // CA、B、C分别输出什么?运行结果如下: var arr = []; arr[‘a’] = 1; console.log(arr); // [a: 1] console.log(arr.length); // 0 arr[‘4’] = 2; console.log(arr) // (5) [empty × 4, 2, a: 1] console.log(arr.length); // 5 arr.length = 0; console.log(arr) // [a: 1] console.log(arr.length); // 0所以A为0,B为5,C为[a:1]2.for(var i=0; i < 5; i ++) { // 在此处编写代码 // 每隔一秒按顺序输出i值}解法: for (var i = 0; i < 5; i++) { // 在此处编写代码 // 每隔一秒按顺序输出i值 (function(i) { setTimeout(() => { console.log(i) }, 1000 * i) })(i) }这道题如果没有限定给出给定的代码,还可以根据ES6块级作用域的知识把for循环中的var改成let,或者用Promise var arr = [] var output = (i) => new Promise(resolve => { setTimeout(() => { console.log(i); resolve() }, 1000 * i) }); for (var i = 0; i < 5; i++) { arr.push(output(i)) };3.有如下代码: var f = function g() { return 23; }; typeof g()运行结果是:报错(扩展:如果题目中typeof f === ‘function’, typeof f() === ’number’)4.有如下代码: function showCase(value) { switch (value) { case ‘A’: console.log(1); break; case ‘string’: console.log(2); break; case undefined: console.log(3); break; case ‘undefined’: console.log(4); break; default: console.log(5) } } showCase(new String(‘A’))运行结果是:5(扩展:console.log(new String(‘A’)) => String {“A”})5.请用JavaScript实现map的数据结构,要求数据只能通过map提供的接口进行访问。解析:map的数据结构方法有size属性 size属性返回 Map 结构的成员总数。set(key, value) set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。set方法返回的是当前的Map对象,因此可以采用链式写法。get(key) get方法读取key对应的键值,如果找不到key,返回undefined。has(key) has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。delete(key) delete方法删除某个键,返回true。如果删除失败,返回false。clear() clear方法清除所有成员,没有返回值。参考: function MyMap() { this.map = new Object(); this.length = 0; this.size = function() { return this.length; } this.set = function(key, value) { if (!this.map[key]) { ++this.length; } this.map[key] = value; } this.get = function(key) { return this.map[key] ? this.map[key] : undefined; } this.has = function(key) { return this.map[key] ? true : false; } this.delete = function(key) { if (this.map[key]) { –this.length; delete this.map[key]; return true; } else { return false; } } this.clear = function() { this.map = new Object(); this.length = 0; } }6.给定一个排好序的整数数组,判断其中是否存在两个数之和等于指定的值,时间复杂度最好能达到O(n)。(例如:[1,2,3,4,5,9],指定值为12,结果为true) var twoSum = function(nums, target) { var arr = {}; for (var i = 0; i < nums.length; i++) { if (typeof(arr[nums[i]] !== “undefined”)) { return true } arr[target - nums[i]] = i } }

January 26, 2019 · 2 min · jiezi

头条日常实习生面经2018.11.28

第一次大公司面试的面经此次面试说来也有点匆忙,本没想过自己会那么快就想去面试大公司,并且把自己第一次面大公司的各种不足展现得一览无余。当时11月20号左右身边一位朋友给了一位人超nice的师兄的内推,并且鼓励我去投简历。最终战胜了自己的胆怯,投了一份简历给内推的师兄。一两天后hr就打电话安排面试了。没想到面试是要连续着面的,当时面花了一个多小时过了一面,再花一个小时面二面,最终也止于二面。接下来就是本文的主题了。我把当时面试官通过牛课网在线面试平台中写给我做的题记录了下来,其实从面试前就打算把面试学到的东西积累下来。接下来就进入本文的主题了,我也尽量把每一道题都写上我的解题思路,希望能得到大家更多更好的意见。一面一、请问运行这段代码会输出什么。 let obj = { name: ‘bytedance’, getName() { return this.name } } let fb = obj.getName; fb();A:我当时好像是答undefined。但我知道这不会是输出”bytedance“,因为当obj.getName赋给fb的时候它的this也改变了,具体可以看我之前总结了一篇JavaScript中的this的文章。然后我后来我在浏览器中运行了一下代码发现是输出"" 。。。二、设计一个简单的任务队列,要求分别在1,3,4秒后打印出”1“,”2“,”3“ new Quene() .task(1000, () => { console.log(1) }) .task(2000, () => { console.log(2) }) .task(1000, () => { console.log(3) }) .start() function Quene() { … }A:讲真,看到这道题的时候我第一时间感到自己完了,虽然也只写了一点,没有全部做出来,然后就跳过这道题了。面试完就努力着把这道题写出来 function Quene() { this.task = (time, callback) => { setTimeout(callback, time); // console.log(this) return this; }; this.start = () => { return this; }; }虽然这样即使最后面不用写.start()也能打印出来。。。希望能得到指点Q_Q三、给定一个升序整数数组[0,1,2,4,5,7,13,15,16],找出其中连续出现的数字区间如下:[“0->2”,”4->5“,“7”,“13”,“15->16”]A: function Arr(arr) { var len = arr.length, j, newArr = [], str = ‘’; for (var i = 0; i < len; i++) { j = i; if (arr[i] + 1 === arr[j + 1]) { while (arr[j] + 1 === arr[j + 1]) { str = ‘->’ + arr[j + 1]; j++; } str = arr[i] + str; newArr.push(str) i = j } else { newArr.push(arr[i].toString()) } } return newArr; }还算比较简单的算法题吧,还好当时做出来了,不然可能就止步于此。。也希望大家能谈点自己对这道题的解法四、TCP协议建立连接的过程、进程间通信的方式有哪些TCP建立连接的过程即为三次握手,三次握手可以参考我之前发的文章,网上也有很多资料,这里就不细讲。至于进程间的通信方式,当时没能打出来(这就涉及到我的知识盲区了【哭丧脸】),后来网上查了一下,有:管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。这里有个题外话,如果面试官问浏览器窗口间的通信,那么有以下几种:1.localStorelocalStorage.setItem(“name”, name); 2.cookie + setInterval在页面A设置一个使用 setInterval 定时器不断刷新,检查 cookie 的值是否发生变化,如果变化就进行刷新的操作。由于 cookie 是在同域可读的,所以在页面 B 改变 cookie 的值,页面 A 是可以拿到的。五、用纯CSS创建一个三角形的原理是什么?如何实现?A:用CSS创建一个三角形的原理是分别设置上下左右的border属性,中间内容为0面积。实现:#box { width: 0; height: 0; border-left: 50px transparent solid; border-right: 50px transparent solid; border-top: 50px transparent solid; border-bottom: 50px black solid;}六、0.1 + 0.2 > 0.3 返回什么?A:true。分别转成2进制。七、类数组对象是什么?刚开始还对这个类数组感到懵逼,后来面试官一提醒函数的参数马上领悟到就是伪数组。A:只包含使用从零开始,且自然递增的整数做键名,并且定义了length表示元素个数的对象。function内部的arguments对象就是一个类数组对象DOM方法document.getElementsByTagName()…也是返回一个类数组对象八、什么是同源策略,为什么会有这种策略源包括三个部分:协议、域名、端口(HTTP协议的默认端口是80)。如果其中有任何一个部分不同,则源不同。即为跨域。限制一个源加载的文档或脚本与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。(来自MDN的解释)九、什么CORSA:受同源策略的限制,支持跨域;一种新的通信协议标准。可以理解成同时支持同源和跨域的Ajax。MDN解释:跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。十、什么是OPTIONS请求A:OPTIONS请求是HTTP请求的一种方法,返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性然后一面就这样结束了,面试官也直接跟我说我通过了一面,我也问了一些问题后就结束面试了。我赶紧把平台上的面试记录记下来,过了几分钟,当我还沉浸在通过一面的愉悦和全身心已经放松了的情况下,发现手机有几个未接电话,然后又再打进了一个,接通电话后对方是头条hr小姐姐,问我说现在可以二面了,二面的面试官已经在平台上等着了。。。好吧,这太突然了,我马上赶赴战场。<hr/>二面一、fetchA:Fetch API 提供了一个获取资源的接口(包括跨域请求)。无论请求成功与否,它都返回一个 Promise 对象;二、用Promise实现延迟3秒后输出 delay(3000).then(f,e)A: function delay(timer) { return new Promise(function(resolve, reject) { setTimeout(function() { resolve(); }, timer) }) }三、XSS/CSRFXSS:跨站脚本(Cross-site scripting)通过提交或其他方式例如发布评论,其中含有HTML或JavaScript的代码,如果服务器没有过滤掉这些脚本,这些脚本在一些情况下就可能会运行。避免XSS的方法之一就是过滤用户提供的内容,如<,>,script;cookie设置HttpOnly属性CSRF:跨站请求伪造(Cross-site request forgery)是一种劫持受信任用户向服务器发送非预期请求的攻击方式,即在用户登陆某个平台化拿到用户的登陆凭证后发送伪造请求防范CSRF的方法之一就是通过验证码Referer Check,根据 HTTP 协议,在HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。通过 Referer Check,可以检查请求是否来自合法的”源”。添加token验证,可以在 HTTP 请求中以参数的形式加入一个随机产生的token,该token不存在与cookie中,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。四、图片懒加载原理 (lazy image)A:给每张图片添加一个data-xxx的属性用于存放图片的src,检测到图片进入视野中的时候把data-xxx的属性赋给src如何检测图片进入视野:a.document.documentElement.clientHeight获取屏幕可视窗口高度b.element.offsetTop获取元素相对于文档顶部的距离c.document.documentElement.scrollTop获取滚动被卷去的高度如果b-c<a成立则元素进入可视区域这里我还提到一个函数节流提高性能:var canRun = true;document.getElementById(“throttle”).onscroll = function(){ if(!canRun){ // 判断是否已空闲,如果在执行中,则直接return return; } canRun = false; setTimeout(function(){ // 这里加载图片 console.log(“函数节流”); canRun = true; }, 500);};五、上传图片表单上传(1). 提供form表单,method必须是post。(2). form表单的enctype必须是multipart/form-data。ajax上传ajax和FormData可实现页面无刷新的文件上传效果六、将一些ES6的新特性可以参考之前写的文章,不过当时到了后面真是精疲力竭,连let和const都没有说出来。。。七、jsbridge问到这个概念的时候我不清楚,记得当前前一两天掘金刚发一篇相关的文章给我,然而当时没去看。。JSBridge 简单来讲,主要是 给 JavaScript 提供调用 Native 功能的接口,让混合开发中的“前端部分”可以方便地使用地址位置、摄像头甚至支付等Native 功能。是 Native 和非 Native 之间的桥梁,它的核心是构建 Native 和非 Native 间消息通信的通道,而且是双向通信的通道。二面其中也包含一些IQ题,还有后面问我如果实习能实习多久我说3个月。之后第二天就收到把我简历”丢进“公司人才库的邮件了。当然这只是其中一部分题目,一些面试官口头问的我当时面试完真是超级累也忘了记录下来。总之,感觉头条还是会比较重视算法和新技术。希望这一两个月能够好好把握,春招再战!! ...

January 25, 2019 · 2 min · jiezi

解锁跨域的九种姿势

解锁跨域的九种姿势作者: Pawn 时间: 2019.01.24本文首发: Pawn博客github: https://github.com/LiChangyi描述:分析跨域,解锁跨域的九种姿势。写在前面针对本文的九种方法我均写的有相应的demo演示(对应的前端文件,后端文件和配置文件),强烈建议不熟悉的朋友都去尝试一下。 本文github地址,fontService是前端地址文件,service是后端文件。网络上存在很多不同的跨域文章,我在学习的时候基本上也是去看他们的文章,但是有些地方的确理解起来有点困难,所以本文就这样产生了,希望能写一点和现在网络上文章中都不一样的东西。同时也把我自己的看法写进去,和大家相互交流自己的看法。跨域在以前一直折磨着每个前端开发者。但是现在,三大框架的普及,我们在开发的过程中,只修改小小的配置,便没有这些顾虑。但实质上还是webpack-dev-server已经帮我们处理了这一个问题,把当前请求,代理到目标服务器上面,然后把获取到的数据返回。所以,现在很多前端开发者,包括我在写这篇文章之前,对跨域都不是很了解,只有一个个模模糊糊的概念,只会用jsonp去获取,或者直接用jq的jsonp方法去解决跨域,都不会去了解为什么这样就能解决跨域?甚至,很多人对跨域都已经放弃了,因为三大框架的学习,完善的脚手架功能,简化了我们,项目部署也有后端的同学帮我们解决。但是个人认为跨域是每一个前端开发者需要必备的能力,不论是跨域获取数据,还是后面进行ssr服务端渲染的配置都需要了解一点跨域,了解一点请求的代理。为什么存在跨域我们在解决的一个问题的同时我们应该先去了解这个问题是如何产生的。之所以要使用跨域,最终的罪魁祸首都是浏览器的同源策略,浏览器的同源策略限制我们只能在相同的协议、IP地址、端口号相同,如果有任何一个不通,都不能相互的获取数据。这里注意一下,http和https之间也存在跨域,因为https一般采用的是443端口,http采用的是80端口或者其他。这里也存在端口号的不同。想要详细了解请看 => MDN对浏览器的同源策略的说明虽然同源策略的确很可恶,但是如果没有同源策略用户将会陷入很境界。比如,你正在吃着火锅哼着歌,逛着淘宝买东西,但是这时你的同学给你发了一个网址,然后你直接打开来看,假如没有同源策略,他在该网站中用一个iframe的标签然后把src指向淘宝网,这时没有同源策略,他便可以直接通过js操作iframe的页面,比如说获取cookie或者用js模拟点击这些操作(因为你已经登录过了,可以不用再次登录就点击了),危害是很大的。大家可以先去做一个小测验,你在本地创建一个a.html和b.html2个文件,然后在a.html中用iframe中插入b.html,你会发现当前2个页面是一个域的,你可以在a中通过js控制b,和在b中直接用js操作没有区别。但是如果你插入的不是同一个域下面的页面,比如你插入的是淘宝,你会发现你不能通过js操作,你console.log(iframe.contentWindow)你会发现只有少数的几项。大家可以去看看大佬的文章:浅谈CSRF攻击方式,虽然没有永远牢靠的盾,但是有同源策略的存在,会让攻击的成本变得高一点。虽然同源策略危害很大,但是我们还是在一定的场景下面需要进行跨域处理,比如说百度全家桶,你在百度地图和百度搜索2者之间肯定是放在2个域下面的(我没有具体的去了解,但是我猜想肯定是这样的)。在开发地图的时候假如需要应用搜索的时候就不得不用跨域了。比如:百度搜索,输入文字出现内容提示,如果我没有判断错误就是采用的jsonp来做得跨域。大家在学习了jsonp跨域的时候,可以去尝试去获取一下。进入正题1.JSONP说起如何去解决跨域,我相信每个人脑袋中跳出来的第一个词就是jsonp。因为浏览器的同源策略不限制script、link、img三个标签,比如我们经常用这三个标签来加载其他域的资源,我个人的看法这就已经算是跨域了。jsonp的跨域就是应用script标签可以进行获取远程的js脚本文件。// 比如1.js 脚本文件say(‘haha’);我在html里面引入1.js文件,那么他讲会执行say函数,我们需要传入的数据就是haha。所以jsonp的方法就是动态的创建一个script标签,然后设置src为我们需要进行跨域的地址。当然这个方法需要后台的设置。大家可以看我写的代码,前端文件在fontEndService/www/demo1/index,htmlbtn.onclick = () => { jsonp(‘http://127.0.0.1:8888/api/getdata?jsonp=displayData’);}function jsonp(url) { let script = document.createElement(‘script’); script.setAttribute(‘src’, url); document.getElementsByTagName(‘head’)[0].appendChild(script);}function displayData(data) { msg.innerText = JSON.stringify(data);}然后后端代码是用koa写的一个简约的接口,文件在service/app.js// 简约的后端代码,我们直接调用前端传过来的需要执行的函数router.get(’/api/getdata’, ctx => { const params = get_params(ctx.request.url) const data = { title: ‘数据获取’, list: [0, 1, 2] } ctx.body = ${params.jsonp || 'callback'}(${JSON.stringify(data)});})前端通过script标签给后台发送一个get请求,在jsonp=displayData(一个我们接受到数据然后执行的方法,该方法是前端的),当我后台接受到请求后,就返回一个,执行displayData这个方法的脚本。然后把我们需要传递的数据放在形参里面。这样就相当于我们在前端里面执行displayData这个方法。用这个方法来实现跨域资源的共享。此方法是比较常用的一个方法,简单粗暴。但是此方法有一个致命的缺点就是只支持GET请求。所以说如果前端页面仅仅是作为页面的展示,就只获取数据的话,只用此方法就没有任何问题。2.iframe+document.domain这个方法,个人感觉不是特别的常用,因为这个跨域方法要求2个域之间的主域名相同,子域不同,比如a.xxx.com和b.xxx.com。如果不同的话是不行的。此方法的思想就是设置页面的document.domain把他们设置成相同的域名,比如都设置成xxx.com。这样来绕过同源策略。很简单的一个方法,具体的代码文件请看github。代码里面的测试案列是,前端文件在7777端口,后台文件在8888端口,前端如果需要请求后端的数据就存在跨域,所以我们在后端8888端口写一个提供数据的中转html,然后通过ajax或者其他的方法请求到数据,然后把数据往外暴露。此方法需要2个html都需要设置相同的主域。3.iframe+location.hash这种方法是一个很奇妙的方法,虽然我感觉很鸡肋,但是它的实现方法很巧妙,我在学习的时候都感觉到不可思议,还能这么玩?首先我们需要了解hash是什么?比如有一个这样的url:http://www.xxx.com#abc=123,那么我们通过执行location.hash就可以得到这样的一个字符串#abc=123,同时改变hash页面是不会刷新的。这一点,相信很多学习三大框架的朋友应该都见识过hash路由。所以说我们可以根据这一点来在#后面加上我们需要传递的数据。加入现在我们有A页面在7777端口(前端显示的文件),B页面在8888端口,后台运行在8888端口。我们在A页面中通过iframe嵌套B页面。从A页面要传数据到B页面我们在A页面中通过,修改iframe的src的方法来修改hash的内容。然后在B页面中添加setInterval事件来监听我们的hash是否改变,如果改变那么就执行相应的操作。比如像后台服务器提交数据或者上传图片这些。从B页面传递数据到A页面经过上面的方法,那么肯定有聪明的朋友就在想那么,从B页面向A页面发送数据就是修改A页面的hash值了。对没错方法就是这样,但是我在执行的时候会出现一些问题。我们在B页面中直接:parent.location.hash = “#xxxx"这样是不行的,因为前面提到过的同源策略不能直接修改父级的hash值,所以这里采用了一个我认为很巧妙的方法。部分代码:try { parent.location.hash = message=${JSON.stringify(data)};} catch (e) { // ie、chrome 的安全机制无法修改parent.location.hash, // 利用一个中间html 的代理修改location.hash // 如A => B => C 其中,当前页面是B,AC在相同的一个域下。B 不能直接修改A 的 hash值故修改 C,让C 修改A // 文件地址: fontEndService/www/demo3/proxy.html if (!ifrProxy) { ifrProxy = document.createElement(‘iframe’); ifrProxy.style.display = ’none’; document.body.appendChild(ifrProxy); } ifrProxy.src = http://127.0.0.1:7777/demo3/proxy.html#message=${JSON.stringify(data)};}如果可以直接修改我们就直接修改,如果不能直接修改,那么我们在B页面中再添加一个iframe然后指向C页面(我们暂时叫他代理页面,此页面和A页面是在相同的一个域下面),我们可以用同样的方法在url后面我们需要传递的信息。在代理页面中:parent.parent.location.hash = self.location.hash.substring(1);只需要写这样的一段js代码就完成了修改A页面的hash值,同样在A页面中也添加一个setInterval事件来监听hash值的改变。我们现在再来理一下思路。我们现在有三个页面,A,B,C。A页面是我们前端显示的页面;B页面我们可以把他当做A页面也后端数据交互的一个中间页面,完成接受A的数据和向后台发送请求。但是由于同源策略的限制我们不能在B页面中直接修改A的hash值,所以我们需要借助一个与A页面在同一个域名下的C页面。C页面我们把他当中一个代理页面,我们因为他和A页面在一个域下,所以可以修改A的hash值。所以B页面修改C页面的hash值,然后C页面修改A页面的hash值。(C就是一个打工的)此方法虽然我个人感觉实现的思路很巧妙但是,使用价值似乎不高,因为他实现的核心思路就是通过修改URL的hash值,然后用定时器来监听值的改变来修改。所以说最大的问题就是,我们传递的数据会直接在URL里面显示出来,不是很安全,同时URL的长度是一定的所以传输的数据也是有限的。4.iframe+window.name相比于前面2种iframe的方法,这种方法的使用人数要多一点。因为他有效的解决了前面2种方法很大的缺点。这种方法的原理就是window.name属性在于加载不同的页面(包括域名不同的情况下),如果name值没有修改,那么它将不会变化。并且这个值可以非常的长(2MB)方法原理:A页面通过iframe加载B页面。B页面获取完数据后,把数据赋值给window.name。然后在A页面中修改iframe使他指向本域的一个页面。这样在A页面中就可以直接通过iframe.contentWindow.name获取到B页面中获取到的数据。A页面中部分代码:let mark = false;let ifr = document.createElement(‘iframe’);ifr.src = “http://127.0.0.1:8888/demo4”;ifr.style.display = ’none’;document.body.appendChild(ifr);ifr.onload = () => { // iframe 中数据加载完成,触发onload事件 if (mark) { msg.innerText = ifr.contentWindow.name;// 这就是数据 document.body.removeChild(ifr); mark = false; } else { mark = true; // 修改src指向本域的一个页面(这个页面什么都没有) ifr.contentWindow.location = “http://127.0.0.1:7777/demo4/proxy.html”; }}5.postMessagepostMessage是HTML5引入的API。他可以解决多个窗口之间的通信(包括域名的不同)。我个人认为他算是一种消息的推送,可以给每个窗口推送。然后在目标窗口添加message的监听事件。从而获取推送过来的数据。A页面中部分代码:<iframe id=“iframe” src=“http://127.0.0.1:8888/demo5” frameborder=“0”></iframe>iframe.contentWindow.postMessage(‘getData’, ‘http://127.0.0.1:8888’);// 监听其他页面给我发送的数据window.addEventListener(‘message’, e => { if (e.origin !== ‘http://127.0.0.1:8888’) return; msg.innerText = e.data;})这里我们给目标窗口127.0.0.1:8888推送了getData的数据。然后在B页面中添加事件的监听。B页面中部分代码:window.addEventListener(‘message’, e => { // 判断来源是不是我们信任的站点,防止被攻击 if (e.origin !== ‘http://127.0.0.1:7777’) return; const data = e.data; if (data === ‘getData’) { // 根据接受到的数据,来进行下一步的操作 myAjax(’/api/getdata’, notice); }})function notice(data) { // 向后台请求到数据以后,在向父级推送消息 top.postMessage(JSON.stringify(data), ‘http://127.0.0.1:7777’)}我个人认为这种方式就是一个事件的发布与监听,至少说可以无视同源策略。6.cors其实对于跨域资源的请求,浏览器已经把我们的请求发放给了服务器,浏览器也接受到了服务器的响应,只是浏览器一看我们2个的域不一样就把消息给拦截了,不给我们显示。所以我们如果我们在服务器就告诉浏览器我这个数据是每个源都可以获取就可以了。这就是CORS跨域资源共享。在后台代码中我以KOA列子const Koa = require(‘koa’);const router = require(‘koa-router’)();// 引入中间件const cors = require(‘koa2-cors’);const app = new Koa();// 根据后台服务器的类型,打开跨域设置// cors安全风险很高,所以,实际线上的配置肯定要比这个更加复杂,需要根据自己的需求来做app.use(cors());router.get(’/api/getdata’, ctx => { ctx.body = { code: 200, msg: ‘我是配置有cors的服务器传输的数据’ }})app.use(router.routes(), router.allowedMethods());console.log(‘配置有cors的服务器地址在:http://127.0.0.1:8889’);app.listen(8889);这样的话,任何源都可以通过AJAX发起请求来获取我们提供的数据。针对不同语言的服务器后端有不一样的处理方法,但是实质是一样的。配置了CORS的浏览器请求响应信息跨域请求响应信息7.NGINX采用nginx做代理应该是目前跨域解决方案最好的一种。现在强调前后端分离,前端根据后端提供的接口进行数据的交互然后渲染页面。在前端三大框架的同时,开发过程中不需要我们针对跨域配置很多。在网页上线以后。我们经常采用nginx来加载静态的资源,我们把我们前端打包好的文件放到nginx的目录下面,让nginx来处理客服端的静态资源的请求。然后后端部署到另外一个端口号上面,当我们需要进行数据的交互的时候,通过nginx代理把后端数据代理到前端页面。这样的步骤是相较于传统的跨域是最简单也是最有效的一种方法,因为nginx又没有同源策略。不用考虑什么兼容性也不用考虑数据大小。我们在服务器(或者测试代码的时候在本地)安装nginx服务,然后找到我们nginx的配置文件,添加以下配置文件:server { # 把页面部署的端口 listen 8080; # 静态页面存放的目录 root /var/www/html; index index.html index.htm index.php; # 只代理 /api 开头的接口,其他接口不代理 location /api/ { # 需要代理的地址, 输入我们的后台api地址 proxy_pass http://127.0.0.1:8888; }}这样,我们可以代理不同url开头的请求到不同的后端进行处理,对以后服务器做负载均衡和反向代理也很简单。8.nodejs其实这种办法和上一种用nginx的方法是差不多的。都是你把请求发给一个中间人,由于中间人没有同源策略,他可以直接代理或者通过爬虫或者其他的手段得到想到的数据,然后返回(是不是和VPN的原理有点类似)。当然现在常见的就是用nodejs作为数据的中间件,同样,不同的语言有不同的方法,但是本质是一样的。我上次自己同构自己的博客页面,用react服务器端渲染,因为浏览器的同源策略,请求不到数据,然后就用了nodejs作为中间件来代理请求数据。部分代码:const Koa = require(‘koa’);// 代理const Proxy = require(‘koa-proxy’);// 对以前的异步函数进行转换const Convert = require(‘koa-convert’);const app = new Koa();const server = require(‘koa-static’);app.use(server(__dirname+"/www/”,{ extensions: [‘html’]}));app.use(Convert(Proxy({ // 需要代理的接口地址 host: ‘http://127.0.0.1:8888’, // 只代理/api/开头的url match: /^/api//})));console.log(‘服务运行在:http://127.0.0.1:7777’);app.listen(7777);是不是和nginx很类似呀!!9.webSocketwebSocket大家应该都有所耳闻,主要是为了客服端和服务端进行全双工的通信。但是这种通信是可以进行跨端口的。所以说我们可以用这个漏洞来进行跨域数据的交互。我个人认为,这种方法实质上和postMessage差不多,但是不是特别的常用吧!(仅仅个人看法)所以说我们可以很轻松的构建基于webSocket构建一个客服端和服务端。代码在github建议大家都多多去运行一下,了解清楚。这里就不贴了。最后哇!长长短短的写了5000多字终于写到最后了!!写总结真的比写代码还困难。个人觉得,第1种方法和第6种方法是以前常用的一种方法,毕竟以前基本上都是刀耕火种的前端时代。然后2,3,4种方法基本上现在很少有人会用,包括我没去详细了解之前也不会,但是里面有很多思想却值得我们去思考,比如第3种方法,反正我个人觉得很巧妙。第5,9种个人认为,这2种方法虽然可以解决跨域,但是把他们用在跨域有点大材小用了解就好。第7,8种方法,觉得应该是现在每个前端er都应该掌握的方法,应该以后解决跨域的主要方法。所有的代码都在github上面,地址在文章开头,我强烈建议都clone下来跑一边代码,最好是结合自己的理解把9种方法都去实现一下。由于我也是才了解跨域不久,本文有很多地方仅仅是我个人看法,欢迎大佬补充、勘误! ...

January 25, 2019 · 2 min · jiezi

让前端面试不再难(二)

昨天聊了一个算法题,今天接着聊!多聊几个。1、拍平数组(多维数组变成一维数组) let arr = [1,[2,3,[4],[5,6,[7]]],8]//[1,2,3,4,5,6,7,8] //这个有很多方法,我们一一说来 //第一种遍历数组,遍历过程遇到数组递归。 function flatten(arr, newArr) { //省去全局变量,还避开了函数嵌套闭包的形成。 newArr = newArr || [] for (let i = 0; i < arr.length; i++) { //如果是arr[i]是数组那么递归执行,并把当前arr[i]和已有newArr传进去继续push。 //如果不是直接push到newArr typeof arr[i] === ‘object’ ? flatten(arr[i], newArr) : newArr.push(arr[i]) } return newArr } console.log(flatten(arr)) //第二种,逻辑一样只不过遍历换成了reduce,如果读的比较困难请移步:https://segmentfault.com/a/1190000017510301 了解reduce function flatten1(arr) { return arr.reduce((newArr, item) => { return typeof item === ‘object’ ? newArr.concat(flatten1(item, newArr)) : (newArr.push(item), newArr) }, []) } console.log(flatten1(arr)) //第三种比较简单 function flatten2(arr) { //join会默认过滤数组内部[],算是一个奇淫技巧。 return arr.join(’,’).split(’,’) } console.log(flatten2(arr)) //第三种稍有点问题,如果数组内是number类型会拍平后会变成字符串。2、写一个方法判断字符串内()是否成对出现,是返回true不是返回false let str = ‘(()()())’ let str1 = ‘(())()())’ //1、先用栈的思路解决 function isTure(str, result = []) { let arr = str.split(’’) for (let i = 0; i < arr.length; i++) { const item = arr[i]; // 如果是左括号直接压栈 if (item === ‘(’) { // 压栈 result.push(item); // 如果是右括号且当前arr不为空弹出栈顶 } else if (item === ‘)’ && result.length != 0) { // 弹出栈顶 result.pop() } else { //如果是右括号且当前result为空,则直接判定为不合法 return false } } return result ? true : false } console.log(isTure(str)) //true console.log(isTure(str1)) //false 2、用计数方式其实和栈原理类似 function isTure1(str, count = 0) { let arr = str.split(’’) for (let i = 0; i < arr.length; i++) { const item = arr[i]; if (item === ‘(’) { count++ } else if (item === ‘)’ && count != 0) { count– } else { return false } } return !count ? true : false } console.log(isTure1(str))//true console.log(isTure1(str1))//falseok 今天分享就到这,明天继续! ...

January 25, 2019 · 2 min · jiezi

【面试】第3篇:基础集合系列一

Java集合HashMap实现原理分析HashtableLinkedHashMapTreeMapCurrentHashMap实现原理分析

January 24, 2019 · 1 min · jiezi

寒冬储粮

个人博客原文:寒冬储粮上次发了这篇文章 跳槽 & 思维导图,里面只是贴了一个图,也不是很清晰,主要是想给大家看一下思维导图记录知识点,供大家参考一下,后面有小伙伴在咨询能否把思维导图原件分享出来,分享出来肯定是可以的,这些都不是我的知识,都是网上的资料,我只是把它们记录在思维导图。今天把这些资料整理了,有的其实还没记录到位,还有很多遗漏的知识点,用我个人力量也很难把这些知识点都总结起来。因此,在把资料上传到百度网盘和 github 之间,我选择了后者,因为大家可以把自己觉得有用的知识点也提交上来,或者在已经存在的思维导图基础上补充进去,让大家都能学习到,也可以多多交流。有一点想强调的:思维导图就跟人的思维一样,每个人的思维都是不一样的,所以其他人记录下来的东西不一定适合你,最好大家自己都能养成把知识点用思维导图记录的习惯,自己记录下的东西才真正属于你的。年底了,希望大家过个好年,猪年一起牛逼。粮食链接目录如下:推荐阅读:公众号之设计模式系列文章

January 24, 2019 · 1 min · jiezi

QUIZ

实现css布局一个div垂直居中其距离屏幕左右两边各10px其高度始终是宽度的50%div中有文本’A’其font—size:20px文本水平垂直居中 <style> .wrapper { margin: 0 10px; display: flex; align-items: center; justify-content: center; background: pink; } </style><body> <div class=“wrapper”> <div class=“font”>A</div> </div></body>ps: 经试验 其高度始终是宽度的50% 这个没有实现2.函数中的arguments是数组吗?类数组转数组的方法了解一下?arguments类数组var array = […arguments]3.类型比较if([]==false){console.log(1)};if({}==false){console.log(2)};if([]){console.log(3)}if([1]==[1]){console.log(4)}都不打印4.EventLoopasync function a1 () { console.log(‘a1 start’) await a2() console.log(‘a1 end’)}async function a2 () { console.log(‘a2’)}console.log(‘script start’)setTimeout(() => { console.log(‘setTimeout’)}, 0)Promise.resolve().then(() => { console.log(‘promise1’)})a1()let promise2 = new Promise((resolve) => { resolve(‘promise2.then’) console.log(‘promise2’)})promise2.then((res) => { console.log(res) Promise.resolve().then(() => { console.log(‘promise3’) })})console.log(‘script end’)打印:script starta1 starta2a1 endscript endpromise1promise2promise2.thenpromise3setTimeoutpromise2错了,知道为什么,但是不知道为啥a1 end是在异步代码执行后打印的5.改正代码,输出0123401234function a () { for (var i = 0; i < 5; i++) { this.i = i setTimeout(function () { console.log(i) }, 0) console.log(this.i) }}a()将var改成let 考察闭包但是好像是错的….为什么改成let会中间出现undefined…..改成let后:01234undefined012346.手写bind实现Function.prototype.bind2 = function (context) { var self = this; // 获得bind的参数从第二个参数到最后一个参数 var args = Array.prototype.slice.call(arguments, 1); var fNOP = function () {}; var fBound = function () { // 指bind返回的函数传入的参数 var bindArgs = Array.prototype.slice.call(arguments); // new bind返回的函数,this失效,但传入的参数生效 return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs)); } // 保证继承,原型链,下面两行代码等同于Object.creater() fbound.prototype = Object.create(this.prototype); fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound;}7.看这个图,我的理解是,持续触发事件,每隔一段时间,只执行一次事件,事件节流function throttle(func, wait) { var context, args; var previous = 0; return function() { var now = +new Date(); context = this; args = arguments; if (now - previous > wait) { func.apply(context, args); previous = now; } }}//调用元素.onmousemove = throttle(func, 100);8.从一个无序,不相等的数组中,选取N个数,使其和为M实现算法算法题凉…..我感觉这道题应该和二叉树有关……9.一个字典[‘I’, ‘have’, ‘a’, ‘book’, ‘good’],实现一个函数,判断一个字符串中是否都是出自字典中的,输出true/false算法题凉…..笨方法:var arr = [‘I’, ‘have’, ‘a’, ‘book’, ‘good’]var str = ‘I have a book’function test(str,arr) { return arr.filter(v => str.indexOf(v) !== -1).length === str.split(’ ‘).length}10.一个长阶梯有n级,可以一次走1级,一次走2级,一共有多少种走法?function test (n) { if (n === 1) return 1 if (n === 2) return 2 return test(n - 1) + test(n - 2)}用笨方法做的….先写出来n为1,2,3,4,5时的走法,看出是用递归

January 23, 2019 · 2 min · jiezi

【剑指offer】10.程序的完整性

题目1 数值的整数次方给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。思路这道题逻辑上很简单,但很容易出错。关键是要考虑全面,考虑到所有情况。exponent 是正,负,0的情况base为0的情况。代码 function Power(base, exponent) { if (exponent === 0) { return 1; } else { if (exponent > 0) { var result = 1; for (let i = 0; i < exponent; i++) { result *= base; } return result; } else if (exponent < 0) { var result = 1; for (let i = 0; i < Math.abs(exponent); i++) { result *= base; } return result ? 1 / result : false; } } }题目2 调整数组顺序使奇数位于偶数前面输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。代码 function reOrderArray(array) { var odd = []; var even = []; for (var i = 0; i < array.length; i++) { const element = array[i]; if (element === 0 || element % 2 === 0) { even.push(element); } else { odd.push(element); } } return odd.concat(even); } ...

January 23, 2019 · 1 min · jiezi

前端进击的巨人(五):学会函数柯里化(curry)

柯里化(Curring, 以逻辑学家Haskell Curry命名)写在开头柯里化理解的基础来源于我们前几篇文章构建的知识,如果还未能掌握闭包,建议回阅前文。代码例子会用到 apply/call ,一般用来实现对象冒充,例如字符串冒充数组对象,让字符串拥有数组的方法。待对象讲解篇会细分解析。在此先了解,两者功能相同,区别在于参数传递方式的不同, apply 参数以数组方式传递,call 多个参数则是逗号隔开。apply(context, [arguments]);call(context, arg1, arg2, arg3, ….);代码例子中使用到了ES6语法,对ES6还不熟悉的话,可学习社区这篇文章:《30分钟掌握ES6/ES2015核心内容(上)》函数柯里化函数柯里化在JavaScript中其实是高阶函数的一种应用,上篇文章我们简略介绍了高阶函数(可以作为参数传递,或作为返回值)。理论知识太枯燥,来个生活小例子,“存款买房”(富二代绕道)。假设买房是我们存钱的终极目标。那么在买房前,存在卡里的钱(老婆本)就不能动。等到够钱买房了,钱从银行卡取出来,开始买买买。。。函数柯里化就像我们往卡里存钱,存够了,才能执行买房操作,存不够,接着存。函数柯里化公式先上几个公式(左边是普通函数,右边就是转化后柯里化函数支持的调用方式):// 公式类型一fn(a,b,c,d) => fn(a)(b)(c)(d);fn(a,b,c,d) => fn(a, b)(c)(d);fn(a,b,c,d) => fn(a)(b,c,d);// 公式类型二fn(a,b,c,d) => fn(a)(b)(c)(d)();fn(a,b,c,d) => fn(a);fn(b);fn(c);fn(d);fn();两种公式类型的区别 —— 函数触发执行的机制不同:公式一当传入参数等于函数参数数量时开始执行公式二当没有参数传入时(且参数数量满足)开始执行通过公式,我们先来理解这行代码 fn(a)(b)(c)(d), 执行 fn(a) 时返回的是一个函数,并且支持传参。何时返回的是值而不是函数的触发机制控制权在我们手里,我们可以为函数制定不同的触发机制。普通的函数调用,一次性传入参数就执行。而通过柯里化,它可以帮我们实现函数部分参数传入执行(并未立即执行原始函数,钱没存够接着存),这就是函数柯里化的特点:“延迟执行和部分求值"“函数柯里化:指封装一个函数,接收原始函数作为参数传入,并返回一个能够接收并处理剩余参数的函数"函数柯里化的例子// 等待我们柯里化实现的方法addfunction add(a, b, c, d) { return a + b + c + d;};// 最简单地实现函数add的柯里化// 有点low,有助于理解function add(a, b, c, d) { return function(a) { return function(b) { return function(c) { return a + b + c + d; } } }}分析代码知识点:函数作为返回值返回,闭包形成,外部环境可访问函数内部作用域子函数可访问父函数的作用域,作用域由内而外的作用域链查找规则,作用域嵌套形成在函数参数数量不满足时,返回一个函数(该函数可接收并处理剩余参数)当函数数量满足我们的触发机制(可自由制定),触发原始函数执行前几篇文章的知识点此时刚好。可见基础知识的重要性,高阶的东西始终要靠小砖头堆砌出来。弄清原理后,接下来就是将代码写得更通用些(高大上些)。// 公式类型一: 参数数量满足函数参数要求,触发执行// fn(a,b,c,d) => fn(a)(b)(c)(d);const createCurry = (fn, …args) { let _args = args || []; let length = fn.length; // fn.length代码函数参数数量 return (…rest) => { let _allArgs = _args.slice(0); // 深拷贝闭包共用对象_args,避免后续操作影响(引用类型) _allArgs.push(…rest); if (_allArgs.length < length) { // 参数数量不满足原始函数数量,返回curry函数 return createCurry.call(this, fn, …_allArgs); } else { // 参数数量满足原始函数数量,触发执行 return fn.apply(this, _allArgs); } }}const curryAdd = createCurry(2);let sum = curryAdd(3)(4)(5); // 14// 公式类型二: 无参数传入时并且参数数量已经满足函数要求// fn(a, b, c, d) => fn(a)(b)(c)(d)();// fn(a, b, c, d) => fn(a);fn(b);fn(c);fn(d);fn();const createCurry = (fn, …args) => { let all = args || []; let length = fn.length; return (…rest) => { let _allArgs = all.slice(0); _allArgs.push(…rest); if (rest.length > 0 || _allArgs.length < length) { // 调用时参数不为空或存储的参数不满足原始函数参数数量时,返回curry函数 return createCurry.call(this, fn, …_allArgs); } else { // 调用参数为空(),且参数数量满足时,触发执行 return fn.apply(this, _allArgs); } }}const curryAdd = createCurry(2);let sum = curryAdd(3)(4)(5)(); // 14为实现公式中不同的两种调用公式,两个createCurry方法制定了两种不同的触发机制。记住一个点,函数触发机制可根据需求自行制定。偏函数与柯里化的区别先上个公式看对比:// 函数柯里化:参数数量完整fn(a,b,c,d) => fn(a)(b)(c)(d);fn(a,b,c,d) => fn(a,b)(c)(d);// 偏函数:只执行了部分参数fn(a,b,c,d) => fn(a);fn(a,b,c,d) => fn(a, b);“函数柯里化中,当你传入部分参数时,返回的并不是原始函数的执行结果,而是一个可以继续支持后续参数的函数。而偏函数的调用方式更像是普通函数的调用方式,只调用一次,它通过原始函数内部来实现不定参数的支持。“如果已经看懂上述柯里化的代码例子,那么改写支持偏函数的代码,并不难。// 公式:// fn(a, b, c, d) => fn(a);// fn(a, b, c, d) => fn(a,b,c);const partialAdd = (a = 0, b = 0, c = 0, d = 0) => { return a + b + c +d;}partialAdd(6); // 6partialAdd(2, 3); // 5使用ES6函数参数默认值,为没有传入参数,指定默认值为0,支持无参数或不定参数传入。柯里化的特点:参数复用(固定易变因素)延迟执行提前返回柯里化的缺点柯里化是牺牲了部分性能来实现的,可能带来的性能损耗:存取 arguments 对象要比存取命名参数要慢一些老版本浏览器在 arguments.lengths 的实现相当慢(新版本浏览器忽略)fn.apply() 和 fn.call() 要比直接调用 fn() 慢大量嵌套的作用域和闭包会带来开销,影响内存占用和作用域链查找速度柯里化的应用利用柯里化制定约束条件,管控触发机制处理浏览器兼容(参数复用实现一次性判断)函数节流防抖(延迟执行)ES5前bind方法的实现一个应用例子:浏览器事件绑定的兼容处理// 普通事件绑定函数var addEvent = function(ele, type, fn, isCapture) { if(window.addEventListener) { ele.addEventListener(type, fn, isCapture) } else if(window.attachEvent) { ele.attachEvent(“on” + type, fn) }}// 弊端:每次调用addEvent都会进行判断// 柯里化事件绑定函数var addEvent = (function() { if(window.addEventListener) { return function(ele, type, fn, isCapture) { ele.addEventListener(type, fn, isCapture) } } else if(window.attachEvent) { return function(ele, type, fn) { ele.attachEvent(“on” + type, fn) } }})()// 优势:判断只执行一次,通过闭包保留了父级作用域的判断结果秒懂反柯里化先上公式,从来没有这么喜欢写公式,简明易懂。// 反柯里化公式:curryFn(a)(b)(c)(d) = fn(a, b, c, d);curryFn(a) = fn(a);看完公式,是不是似曾相识,这不就是我们日常敲码的普通函数么?没错的,函数柯里化就是把普通函数变成成一个复杂的函数,而反柯里化其就是柯里化的逆反,把复杂变得简单。函数柯里化是把支持多个参数的函数变成接收单一参数的函数,并返回一个函数能接收处理剩余参数:fn(a,b,c,d) => fn(a)(b)(c)(d),而反柯里化就是把参数全部释放出来:fn(a)(b)(c)(d) => fn(a,b,c,d)。// 反柯里化:最简单的反柯里化(普通函数)function add(a, b, c, d) { return a + b + c + d;}反思:为何要使用柯里化函数柯里化是函数编程中的一个重要的基础,它为我们提供了一种编程的思维方式。显然,它让我们的函数处理变得复杂,代码调用方式并不直观,还加入了闭包,多层作用域嵌套,会有一些性能上的影响。但在一些复杂的业务逻辑封装中,函数柯里化能够为我们提供更好的应对方案,让我们的函数更具自由度和灵活性。实际开发中,如果你的逻辑处理相对复杂,不妨换个思维,用函数柯里化来实现,技能包不嫌多。说到底,程序员就是解决问题的那群人。写在结尾本篇函数柯里化知识点的理解确实存在难度,暂时跳过这章也无妨,可以先了解再深入。耐得主寂寞的小伙伴回头多啃几遍,没准春季面试就遇到了。参考文档:js高阶函数应用—函数柯里化和反柯里化前端基础进阶(八):深入详解函数的柯里化系列更文请关注专栏:《前端进击的巨人》,不断更新中。。。本文首发Github,期待Star!https://github.com/ZengLingYong/blog作者:以乐之名本文原创,有不当的地方欢迎指出。转载请指明出处。 ...

January 22, 2019 · 2 min · jiezi

世界顶级公司的前端面试都问些什么

原文:http://davidshariff.com/blog/…作者:David Shariff翻译:疯狂的技术宅本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章在过去的几年里,我在亚马逊和雅虎面试过许多前端工程师。在这篇文章中,我想分享一些技巧,帮助大家做好准备。免责声明: 本文的目的并不是为你列出在前端面试中可能会被问到的问题,但是可以将其视为知识储备。面试很难,作为候选人,通常会给你45分钟的时间来让你展示自己的技能。 作为一名面试官,同样难以在这么短的时间里评估这个人是否适合这项工作。 对于面试来说,没有任何一种标准能够适合所有人,面试官通常会覆盖某一个领域,但除此之外,他们会自行决定应该问哪些问题。不管你坐在面试桌的哪一侧,这篇文章都会尽可能的涵盖前端开发中那些最重要的领域。常见的误解我看到面试者犯的最大错误之一是喜欢准备一些琐碎的问题,例如“什么是盒子模型?”或“告诉我在JavaScript中==和===之间的区别?”等等。知道这些问题的答案固然很好,但它并不能让面试官知道你真正的水平。相反,你应该为面试做一些非常实际的准备,能够真正体现出自己的JavaScript,CSS和HTML编码水平。为面试的准备包括去实现UI,构建窗口小部件或实现诸如Lodash和Underscore.js库中常见的功能,例如:构建常见Web应用程序的布局和交互,例如Netflix浏览器站点。实现小工具,如日期选择器,轮播或电子商务购物车。编写类似debounce或深度克隆对象的函数。说到库,常见的另一个错误是人们喜欢完全依赖最新的框架来解决面试问题。你可能会想:既然在开发中我可以使用jQuery,React,Angular等,为什么还要重新发明轮子,为什么不能在面试中使用它?这个问题很好,技术、框架和库总会随着时间的推移而发生变化 —— 我更感兴趣的是:你需要了解前端开发的基本原理,而不是依赖更高级别的抽象。如果你不能在不依赖这些库的情况下回答的面试问题,我希望你至少可以彻底解释和推测库在背后都做了什么。总的来说,你应该期望大部分的面试都是非常实际的。JavaScript你需要了解JavaScript,而且是深入了解。 在面试中,越高级别的人对语言知识深度的期望也越高。 至少,以下是你应该熟悉的JavaScript内容:执行上下文、尤其是词法作用域和闭包。提升机制、函数与块作用域、以及函数表达式和声明。绑定 - 特别是调用、bind、apply 和this关键字。对象原型,构造函数和mixins。组合函数和高阶函数。时间委托和冒泡。typeof,instanceof和Object.prototype.toString。使用回调,promises,await和async处理异步调用。何时使用函数声明和表达式。DOM如何遍历和操作DOM很重要,如果他们依赖jQuery或者编写了很多React和Angular类型的应用,那么这就是大多数面试者应该努力的地方。你可能不会每天都做这些,因为我们大多数人都使用抽象排序。但是如果不依赖第三方库,你应该知道该如何进行以下操作:使用document.querySelector和旧版浏览器中的document.getElementsByTagName选择或查找节点。上下遍历: Node.parentNode,Node.firstChild,Node.lastChild和Node.childNodes。左右遍历: Node.previousSibling和Node.nextSibling。操作:在DOM树中添加,删除,复制和创建节点。 你应该了解如何修改节点的文本内容,以及切换,删除或添加CSS类名等操作。性能:当你有很多节点时,操作DOM的代价可能会很高,所以你至少应该知道文档片段和节点缓存。CSS至少,你应该知道如何在页面上布局元素,如何使用子元素或直接用后代选择器来定位元素,以及何时使用classes与id。布局:坐在彼此相邻的元素以及如何将元素放在两列与三列中。响应式设计:根据浏览器宽度更改元素的尺寸。自适应设计:根据特定断点更改元素的尺寸。特异性:如何计算选择器的特异性以及级联怎样影响属性。使用恰当的命名空间和类名。HTML知道哪些HTML标签能最好的表现你正在显示的内容以及相关属性,应该掌握手写HTML的技能。语义标记。标记属性,例如disabled, async, defer以及何时使用data-*。知道如何声明你的doctype(很多人因为不是每天都写新页面,从而忘记了这一点),以及可以使用哪些meta标签。可访问性问题,例如:确保输入复选框具有更大的响应区域(使用标签“for”)。 另外还有role =“button”,role =“presentation”等。系统设计针对后端系统设计的面试通常会涉及MapReduce、设计分布式键值存储或需要CAP定理等知识。 尽管你的前端工作不需要深入了解此类系统是如何设计的,但是在被要求设计常见应用程序的前端架构时,千万不要感到惊讶。 通常这些问题会问的含糊,比如“设计像Pinterest这样的网站”或“告诉我如何构建购物结账服务?”。 以下是需要考虑的领域:渲染: 客户端渲染(CSR),服务器端渲染(SSR)和通用渲染。布局: 如果你正在设计多个开发团队使用的系统,则需要考虑构建组件,以及是否需要团队遵循一致的规范来使用这些组件。状态管理:例如在单向数据流或双向数据绑定之间进行选择。你还应该考虑自己的设计是否遵循被动或响应式编程模型,以及组件应该如何相互关联。异步流: 你的组件可能需要与服务器实时通信。你的设计应考虑XHR与双向调用。如果面试官要求你支持旧版浏览器,那么你的设计需要在隐藏的iFrame,脚本标签或XHR之间进行选择以进行消息传递。如果没有,你可以建议使用websockets,或者你可能决定服务器发送事件(SSE)更好。关注点分离: MVC、MVVM和MVP模式。多设备支持: 你的设计是否会针对Web、移动Web和混合应用使用相同的实现,或是单独实现?如果你正在开发类似于Pinterest这样的站点,可能会考虑在Web上使用三列,但在移动设备上只考虑一列,那么你的设计该如何处理这个问题?交付: 在大型应用程序中,让独立团队拥有自己的代码库并不罕见。这些不同的代码库可能彼此依赖,每个代码库通常都有自己的管道来释放对生产环境的更改。你的设计应考虑如何使用依赖关系(代码拆分)、测试(单元和集成测试)和部署来构建这些资源。你还应该考虑如何通过CDN分发资源或内联它们以减少网络延迟。网络表现除了通用编程技术之外,你应该期望面试官查看你的代码或设计及其对性能的影响。 在以前将CSS放在页面的顶部,并在底部放置JS脚本的做法就足够了,但是Web正在快速发展,你应该熟悉这个领域的复杂性。关键渲染路径。Service workers。图像优化。延迟加载和捆绑拆分。HTTP/2和服务器推送的一般含义。何时预取和预加载资源。减少浏览器重排以及何时将元素渲染交给GPU。浏览器布局,合成和绘制之间的差异。数据结构和算法这点可能具有争议,但是不了解高时间复杂度和常见运行时(如O(N)和O(N Log N)的基本知识会对你造成困扰。理解内存管理等方面的知识对当前十分常见的单页应用非常有帮助。 例如:如果你要实现一个拼写检查功能,那么了解常见的数据结构和算法将使你的工作变得更加轻松。我不是说你需要一个CS学位,但是这个行业已经不再是写一个简单的页面了。 网上有很多资源,你可以很快掌握这些基础知识。常用Web知识你需要掌握构成Web的技术和范例。HTTP请求: GET和POST以及相关标头,如Cache-Control,ETag,Status Codes和Transfer-Encoding。REST与RPC。安全性:何时使用JSONP,CORs和iFrame策略。总结作为Web开发人员或工程师,需要大量的知识。 不要拘泥于所需的知识深度,而要保持开放的心态,学习开发所需的所有复杂技术。除了本文涉及的技术主题外,在面试中你还需要谈谈自己过去的项目,描述有趣的纠结点以及所做的权衡。我知道前端面试中还有很多方面被我忽略了,所以我很想听听你的经历,或者你认为自己在面试时被问到,但是被我忽略的那些重要内容。本文首发微信公众号:jingchengyideng关注公众号 jingchengyideng ,回复“体系”,查看本文所讲到的知识体系大图

January 21, 2019 · 1 min · jiezi

【剑指offer】9.二进制中1的个数

题目输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。分析这是一道考察二进制的题目二进制或运算符(or):符号为|,表示若两个二进制位都为0,则结果为0,否则为1。二进制与运算符(and):符号为&,表示若两个二进制位都为1,则结果为1,否则为0。二进制否运算符(not):符号为~,表示对一个二进制位取反。异或运算符(xor):符号为^,表示若两个二进制位不相同,则结果为1,否则为0左移运算符m << n 表示把m左移n位,左移n位的时候,最左边的n位将被丢弃,同时在最右边补上n个0,比如:00001010<<2 = 00101000右移运算符m >> n 表示把m右移n位,右移n位的时候,最右边的n位将被丢弃,同时在最左边补上n个0,比如:00001010>>2 = 00000010我们可以让目标数字和一个数字做与运算这个用户比较的数字必须只有一位是1其他位是0,这样就可以知道目标数字的这一位是否为0。所以用于比较的这个数字初始值为1,比较完后让1左移1位,这样就可以依次比较所有位是否为1。代码function NumberOf1(n){ let flag = 1; let count = 0; while(flag){ if(flag & n){ count++; } flag = flag << 1; } return count;}

January 20, 2019 · 1 min · jiezi

「前端面试题系列4」this的原理以及用法

这是前端面试题系列的第 4 篇,你可能错过了前面的篇章,可以在这里找到:伪类与伪元素的区别及实战如何实现一个圣杯布局?今日头条 面试题和思路解析在前端的面试中,经常会问到有关 this 的指向问题。最近,朋友Z 向我求助说,他一看到 this 的题目就犯难,搞不清楚 this 究竟指向了谁。我为他做了解答,并整理成了这篇文章,希望能帮到有需要的同学。一道面试题朋友Z 给我看了这样一道题:var length = 10;function fn () { console.log(this.length);} var obj = { length: 5, method: function (fn) { fn(); arguments0; }}; obj.method(fn, 1);问:浏览器的输出结果是什么?它的答案是:先输出一个 10,然后输出一个 2。让我们来解析一下原因:在我们这道题中,虽然 fn 作为 method 的参数传了进来,但它的调用者并不受影响,任然是 window,所以输出了 10。arguments0;这条语句并不常见,可能大家有疑惑的点在这里。 其实,arguments 是一种特殊的对象。在函数中,我们无需指出参数名,就能访问。可以认为它是一种,隐式的传参形式。当执行 arguments0; 时,其实调用了 fn()。而这时,fn 函数中 this 就指向了 arguments,这个特殊的对象。obj.method 方法接收了 2 个参数,所以 arguments 的 length,很显然就是 2 了。改造一下再来,不少同学对 this 的指向感到疑惑,是因为 this 并没有指向我们预期的那个对象。就像这道题,从语义上来看,我们期望 fn() 输出的是 obj 自己的 length,也就是 5,而不是 10。那么如果要得到 5 的结果,我们该如何修改这段代码呢?其实只要多做一步处理就好。就是让 this 指向 obj 自己。这里,我们可以用 call 来改变 this 的指向,像下面这样:var length = 10;function fn () { console.log(this.length);}var obj = { length: 5, method: function (fn) { // 在这里用call 将 this 指向 obj 自己 fn.call(this); }}; obj.method(fn);输出的结果就是 5 了,搞定。看吧,this 也没那么复杂吧,我们只需要一些简单的操作,就能控制 this 的指向了。那么,问题来了,为什么有时候 this 会失控呢?其实,这与 this 机制背后的原理有关。不过别急,让我们从理解 this 的基本概念开始,先来看看 this 到底是什么?this 是什么?this 是 JavaScript 中的一个关键字。它通常被运用于函数体内,依赖于函数调用的上下文条件,与函数被调用的方式有关。它指向谁,则完全是由函数被调用的调用点来决定的。所以,this,是在运行时绑定的,而与编写时的绑定无关。随着函数使用场合的不同,this 的值也会发生变化。但是有一个总的原则:那就是this 总会指向,调用函数的那个对象。为什么要用this?从概念上理解起来,似乎有点费劲。那我们为什么还要使用 this 呢?用了 this 会带来什么好处?让我们先看下面这个例子:function identify() { return this.name.toUpperCase();}var me = { name: “Kyle”};var you = { name: “Reader”};identify.call( me ); // KYLEidentify.call( you ); // READER一开始我们可能太不明白为何这样输出。那不如先换个思路,与使用 this 相反,我们可以明确地将环境对象,传递给 identify()。像这样:function identify(context) { return context.name.toUpperCase();}identify( you ); // READER在这个简单的例子中,结果是一样的。我们可以把环境对象直接传入函数,这样看来比较直观。但是,当模式越发复杂时,将执行环境作为一个明确的参数传递给函数,就会显得非常混乱了。而 this 机制,可以提供一种更优雅的方式,来隐含地“传递”一个对象的引用,这会使得 API 的设计更加地干净,复用也会变得容易。this 的原理明白了 this 的概念之后,不经让我好奇,为何 this 指向的就是函数运的执行环境呢?之前,看到了 阮老师 的一篇文章,十分透彻地分析了 this 的原理。我根据自己的理解,整理如下。很多教科书会告诉你,this 指的是函数运行时所在的环境。但是,为什么会这样?也就是说,函数的运行环境到底是怎么决定的?理解 this 的原理,有助于帮我们更好地理解它的用法。JavaScript 语言之所以有 this 的设计,跟内存里面的数据结构有关系。来看一个简单的示例:var obj = { foo: 5 };上面的代码将一个对象赋值给变量 obj。JavaScript 引擎会先在内存里面,生成一个对象 { foo: 5 },然后把这个对象的内存地址赋值给变量 obj。也就是说,变量 obj 其实只是一个地址。后面如果要读取 obj.foo,引擎先从 obj 拿到内存地址,然后再从该地址读出原始的对象,返回它的 foo 属性。这样的结构很清晰,但如果属性的值是一个函数,又会怎么样呢?比如这样:var obj = { foo: function () {} };这时,JavaScript 引擎会将函数单独保存在内存中,然后再将函数的地址赋值给 foo 属性的 value 属性。可以看到,函数是一个单独的值(以地址形式赋值),所以才可以在不同的环境中执行。又因为,JavaScript 允许在函数体内部,引用当前环境的其他变量。所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。this 的用法在理解了 this 的原理之后,我们用下面的 5 种情况,来讨论 this 的用法。1、纯粹的函数调用这是函数的最通常用法,属于全局性调用,因此 this 就代表全局对象 window。function test(){ this.x = 1; console.log(this.x);}test(); // 12、作为对象方法的调用函数作为某个对象的方法调用,这时 this 就指这个上级对象。function test(){ console.log(this.x);}var o = {};o.x = 1;o.m = test;o.m(); // 13、作为构造函数调用所谓构造函数,就是通过这个函数生成一个新对象(object)。这时,this 就指这个新对象。function test(){ this.x = 1;}var o = new test();console.log(o.x); // 14、apply调用apply() 是函数对象的一个方法,它的作用是改变函数的调用对象,它的第一个参数就表示改变后的调用这个函数的对象。因此,this 指的就是这第一个参数。var x = 0;function test() { console.log(this.x);}var o = {};o.x = 1;o.m = test;o.m.apply(); //0apply() 的参数为空时,默认调用全局对象。因此,这时的运行结果为0,证明this指的是全局对象。它与上文中提到的 call 的作用是一样的,只是写法上略有区别。由于篇幅原因,我会另启一篇,来详述它们的用法。5、箭头函数ES6 中的箭头函数,在大部分情况下,使得 this 的指向,变得符合我们的预期。但有些时候,它也不是万能的,一不小心的话,this 同样会失控。因为篇幅内容较多,我会另写一篇文章来介绍。另一道面试题最后,让我们来巩固一下 this 的概念和用法。来看一道面试题:window.val = 1; var obj = { val: 2, dbl: function () { this.val *= 2; val *= 2; console.log(‘val:’, val); console.log(’this.val:’, this.val); }}; // 说出下面的输出结果 obj.dbl(); var func = obj.dbl; func();答案是输出:2 、 4 、 8 、 8 。解析:执行 obj.dbl(); 时, this.val 的 this 指向 obj,而下一行的 val 指向 window。所以,由 window.val 输出 2,obj.val 输出 4 。最后一行 func(); 的调用者是 window。 所以,现在的 this.val 的 this 指向 window。别忘了刚才的运算结果,window.val 已经是 2 了,所以现在 this.val *= 2; 的执行结果就是 4。val *= 2; 的执行结果,就是 8 了。 所以,最终的结果就是输出 8 和 8 。总结this 指代了函数当前的运行环境,依赖于函数调用的上下文条件,在运行时才会进行绑定。请牢记总原则:this 总会指向,调用函数的那个对象。参考文献this 与对象原型JavaScript 的 this 原理PS:欢迎关注我的公众号 “超哥前端小栈”,交流更多的想法与技术。 ...

January 20, 2019 · 2 min · jiezi

【剑指offer】8.斐波那契数列

题目题目描述大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。基本思路这道题在剑指offer中实际是当作递归的反例来说的。递归的本质是吧一个问题分解成两个或者多个小问题,如果多个小问题存在互相重叠的情况,那么就存在重复计算。f(n) = f(n-1) + f(n-2) 这种拆分使用递归是典型的存在重叠的情况,所以会造成非常多的重复计算。另外,每一次函数调用爱内存中都需要分配空间,每个进程的栈的容量是有限的,递归层次过多,就会造成栈溢出。递归是从最大数开始,不断拆解成小的数计算,如果不去考虑递归,我们只需要从小数开始算起,从底层不断往上累加就可以了,其实思路也很简单。代码function Fibonacci(n){ if(n<=1){ return n; } let i = 1; let pre = 0; let current = 1; let result = 0; while(i++ < n){ result = pre + current; pre = current; current = result; } return result;}扩展1.跳台阶:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。找规律:跳三级台阶等于跳两级台阶的跳法+跳一级台阶的跳法。跳四级台阶等于跳三级台阶的跳法+跳二级台阶的跳法。明显也符合斐波那契数列的规律function jumpFloor(n){ if(n<=2){ return n; } let i = 2; let pre = 1; let current = 2; let result = 0; while(i++ < n){ result = pre + current; pre = current; current = result; } return result;}3.矩形覆盖我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?跟上面跳台阶那个题很像。假设有8个块第1块竖着放,后面还剩7块,共f(7)种方法。第1块横着放,后面还剩6块,共f(6)种方法。即f(8)=f(6)+f(7) f(n)=f(n-1)+f(n-2)function rectCover(n){ if(n<=2){ return n; } let i = 2; let pre = 1; let current = 2; let result = 0; while(i++ < n){ result = pre + current; pre = current; current = result; } return result;}3.变态跳台阶一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。每个台阶都可以选择跳或者不跳,最后一个台阶必跳。每个台阶有两种选择,n个台阶有2的n次方种选择。所以一共有2的n-1次跳法。使用位运算function jumpFloorII(number){ return 1<<(–number);} ...

January 19, 2019 · 1 min · jiezi

常见面试问题回答技巧(非技术)

常见面试问题回答技巧 共48条仔细阅读 把握机会 赢得Offer 祝君好运!请你做一下自我介绍?回答提示:一般人回答这个问题过于平常,只说姓名、年龄、爱好、工作经验,这些在简历上都有。其实,企业最希望知道的是求职者能否胜任工作,包括:最强的技能、最深入研究的知识领域、性格中最积极的部分、做过的最成功的事,主要的成就等,这些回答关于学习或者生活都可以,但要突出积极的个性和做事的能力,说得合情合理企业才会相信。企业很重视一个人的礼貌,求职者要尊重考官,要在自我介绍结束后说一句“谢谢”。你觉得你最大的优点是什么?回答提示:沉着冷静、条理清楚、立场坚定、顽强向上、乐于助人和关心他人、适应能力和幽默感、乐观和友爱。要例举具体的优点,如:做事认真、抗压能力强、乐于助人、学习能力强等,然后要举实例证明这也是最重要的一点。说说你最大的缺点?回答提示:这个问题企业问的概率很大,通常不希望听到直接回答的缺点是什么,如果求职者说自己有强迫症、爱忌妒人、非常懒、脾气大、工作效率低,那么肯定不会被企业录用。绝对不要自作聪明地回答“我最大的缺点是过于追求完美”,有的人以为这样回答会显得自己比较出色,但事实上,他已经岌岌可危了。企业喜欢求职者从自己的优点说起,中间加一些小缺点,最后再把问题转回到优点上,突出优点的部分。回答样本:我平时比较爱忘事,然后我通过写便条来提醒我自己什么时候该做什么事。你对加班的看法?回答提示:实际上好多公司问这个问题,并不证明一定要加班,只是想测试你是否愿意为公司奉献。回答样本:如果是工作需要我会义不容辞加班,我现在单身,没有任何家庭负担,可以全身心的投入工作。但同时,我也会提高工作效率,减少不必要的加班。如何安排自己的时间?会不会排斥加班?回答提示:基本上,如果上班工作有效率,工作量合理的话,应该不太需要加班。可是我也知道有时候很难避免加班,加上现在工作都采用责任制,所以我会调配自己的时间,全力配合。分析:虽然不会有人心甘情愿的加班,但依旧要表现出高配合度的诚意。在五年的时间内,你的职业规划?回答提示:一般公司高管或者副总喜欢问个人职业规划方面的问题。当然,说出其他一些你感兴趣的职位也是可以的,比如产品销售部经理,生产部经理等一些与你的专业有相关背景的工作。要知道,考官总是喜欢有进取心的应聘者,此时如果说“不知道”,或许就会使你丧失一个好机会。最普通的回答应该是“我准备在技术领域有所作为”或“我希望能按照公司的管理思路发展”。能把职业规划分成几部分去完成,比如1个月、三个月、半年、1年以至3年的具体发展目标和实施方案。你的朋友是如何评价你的?回答提示: 面试官想从侧面了解一下你的性格及与人相处的问题。回答样本一:我的朋友都说我是一个可以信赖的人。因为,我一旦答应别人的事情,就一定会做到。如果我做不到,我就不会轻易许诺。回答样本二:我觉的我是一个比较随和的人,与不同的人都可以友好相处。在我与人相处时,我总是能站在别人的角度考虑问题。你还有什么要问我的吗?回答提示:企业的这个问题看上去可有可无,其实很关键,企业不喜欢说“没问题”的人,因为其很注重员工的个性和创新能力。企业不喜欢求职者问个人福利之类的问题,如果有人这样问:咱公司对新入公司的员工有没有什么培训项目,我可以参加吗?或者说咱公司的晋升机制是什么样的?公司希望我以后向什么方向发展?企业将很欢迎,因为体现出你对学习的热情和对公司的忠诚度以及你的上进心。你对薪资的要求?回答提示:如果你对薪酬的要求太低,那显然贬低自己的能力;如果你对薪酬的要求太高,那又会显得你分量过重,公司受用不起。一些雇主通常都事先对求聘的职位定下开支预算,因而他们第一次提出的价钱往往是他们所能给予的最高价钱,他们问你只不过想证实一下这笔钱是否足以引起你对该工作的兴趣。回答样本一:我对工资没有硬性要求,我相信贵公司在处理我的问题上会友善合理。我注重的是找对工作机会,所以只要条件公平,我则不会计较太多。回答样本二:我受过系统的软件编程的训练,不需要进行大量的培训,而且我本人也对编程特别感兴趣。因此,我希望公司能根据我的情况和市场标准的水平,给我合理的薪水。回答样本三:如果你必须自己说出具体数目,请不要说一个宽泛的范围,那样你将只能得到最低限度的数字。最好给出一个具体的数字,这样表明你已经对当今的人才市场作了调查,而且自己具有这方面的价值。为什么选择我们这家公司?回答提示:曾经在报章杂志看过关于公司的报道,与自己所追求的理念有志一同。而贵公司在业界的成绩也是有目共睹的,而且对员工的教育训练、升迁等也都很有制度。并具体详谈你调查到的内容会让面试官觉得你很用心。分析:去面试前先做功课,了解一下该公司的背景,让对方觉得你真的很有心想得到这份工作,而不只是探探路。谈谈你对跳槽的看法?回答提示:①我认为跳槽应该是进入到更好的企业和更大的平台,而不是在公司待几个月就认为公司同事关系不好,自己发展有瓶颈就跳槽,这样换工作是无意义的。②频繁的跳槽对单位和个人双方都不利,应该反对。谈谈如何适应新的工作环境?回答提示①办公室里每个人有各自的岗位与职责,不得擅离岗位。②根据领导指示和工作安排,制定工作计划,提前预备,并按计划完成。③多请示并及时汇报,遇到不明白的要虚心请教。④抓间隙时间,多学习,努力提高自己代码质量和代码规范性。你(如果面试后端开发)来我们公司做前端可以吗?回答提示:面试者在考验面试者是否对所述职位专一时,如果面试者过多表现在前端上的技能,那面试官就会用这个问题问你,如果你回答可以,那么面试官会认为你求职目标不明确,从而不会要你继续面试。假设你在某单位工作,成绩比较突出,得到领导的肯定。但同时你发现同事们越来越孤立你,你怎么看这个问题?你准备怎么办?回答提示:①成绩比较突出,得到领导的肯定是件好事情,以后更加努力。②检讨一下自己是不是对工作的热心度超过同事间交往的热心了,加强同事间的交往及共同的兴趣爱好。③工作中,切勿伤害别人的自尊心④不再领导前拨弄是非。请说出你选择这份工作的目的?回答提示:这是想知道面试者对这份工作的热忱及理解度,并筛选因一时兴起而来应试的人,如果是无经验者,可以强调“就算职种不同,也希望有机会发挥之前的经验”。包括对面试官的了解和团队的了解都是面试之前应该了解的。你能为我们公司带来什么呢?回答提示:企业很想知道未来的员工能为企业做什么,求职者应再次重复自己的优势,然后说:“就我的能力,我可以做一个优秀的员工在组织中发挥能力,给组织带来高效率和更多的收益”。给公司带来活力,“我更加积极、更加努力、更加有朝气。”怎样看待学历和能力?回答提示:学历不一定完全代表能力,虽然我的学历不够硬但是我会在技术上更努力更认真,并在短期内发挥自己的优势,把公司项目做的更好。你的业余爱好是什么?回答提示:找一些富于团体合作精神的,这里有一个真实的故事:有人被否决掉,因为他的爱好是深海潜水。主考官说:因为这是一项单人活动,我不敢肯定他能否适应团体工作。或者看技术论坛,会给工作带来新的思路,或者看关于数据库优化/数据原理的书籍。作为被面试者请你给我打一下分?回答提示:试着列出四个优点和一个非常非常非常小的缺点(可以抱怨一下设施,没有明确责任人的缺点是不会有人介意的)。你怎么理解 PHP 这个职位?回答提示:把岗位职责和任务及工作态度阐述一下,更多的说 PHP 发展和优势以及你学习 PHP 的原因。比如:PHP 语言上的应用发展, PHP 最大特点就是语法灵活,开发速度快, 开发成本低,最适合开发业务流程,所以互联中使用 PHP 开发动态网站将成为主流。喜欢这份工作的哪一点?回答提示:每个人的价值观不同,自然评断的标准也会不同,但是,在回答面试官这个问题时可不能太直接就把自己心理的话说出来,尤其是薪资方面的问题,不过一些无伤大雅的回答是不错的考虑,如交通方便,工作性质及内容颇能符合自己的兴趣等等都是不错的答案,不过如果这时自己能仔细思考出这份工作的与众不同之处,相信在面试上会大大加分。说说你对 PHP 行业和发展趋势的看法?回答提示:第一、语言本身的发展上,PHP 从5版本之后将面向过程转变到面向对象思想方面,做出过一次重大的改变,PHP以后的技术方展,除了在基本语法上保持开发高效之外,需要将部分模块不段升级和优化。 也要从运行效率上有大的提高,以及多提供一些针对不同企业扩展的第三方类库进行丰富。第二、 PHP 语言上的应用发展, PHP 最大特点就是语法灵活,开发速度快, 开发成本低,最适合开发业务流程,所以互联中使用 PHP 开发动态网站将成为主流。对工作的期望与目标何在?回答提示:就是职业规划的另一种问法,也是公司在以后培养你的方向上的选择。可以提一些具体的想法和做法。让上级认为你之前考虑过这个事,而且重视自己的职业发展。就你申请的这个职位,你认为你还欠缺什么?回答提示:企业喜欢问求职者弱点,他们希望看到这样的求职者:继续重复自己的优势,然后说:“对于这个职位和我的能力来说,我相信自己是可以胜任的,只是缺乏经验,这个问题我想我可以进入公司以后以最短的时间来解决,我的学习能力很强,我相信可以很快融入公司的企业文化,进入工作状态。”企业喜欢能够巧妙地躲过难题的求职者。你通常如何处理別人的批评?回答提示:①沈默是金,不必说什么,否则情况更糟,不过我会接受建设性的批评。②我会等大家冷靜下来再讨论,反思自己是否有这方面问题并进行完善。怎样对待自己的失败?回答提示:我认为失败不是坏事,一件事情没有成功肯定是我做的地方有不是,我觉得能积极面对失败并总结经验成功就指日可待。什么会让你有成就感?回答提示:为公司竭力效劳,尽我所能,完成一个项目,并且能提出自己的创新想法。眼下你生活中最重要的是什么?回答提示:对我来说,能在这个领域找到工作是最重要的,能在公司任职对我说最重要。你为什么愿意到我们公司来工作?回答提示:对于这个问题,你要格外小心,如果你已经对该单位作了研究,你可以回答一些详细的原因,像“公司本身的高技术开发环境很吸引我。”我希望能够进入一家与我共同成长的公司”,“你们公司一直都稳定发展,在近几年来在市场上很有竞争力”,“我认为公司能够给我提供一个与众不同的发展道路。”这都显示出你已经做了一些调查,也说明你对自己的未来有了较为具体的远景规划。你和别人发生过争执吗?你是怎样解决的?回答提示:这是面试中最险恶的问题,其实是考官布下的一个陷阱,千万不要说任何人的过错,应知成功解决矛盾是一个协作团体中成员所必备的能力。基本上没有和他人产生矛盾,首先有矛盾就是因为大家对同一个问题有不同的解决方案,把方案里的优秀点统一起来就是最佳解决方案。你是否能获得这份工作,将取决于这个问题的回答。考官希望看到你是成熟且乐于奉献的。他们通过这个问题了解你的成熟度和处世能力。在没有外界干涉的情况下,通过妥协的方式来解决才是正确答案。你做过的哪件事最令自己感到骄傲?回答提示:这是考官给你的一个机会,让你展示自己把握命运的能力。这会体现你潜在的领导能力以及你被提升的可能性。记住:你的前途取决于你的知识、你的社交能力和综合表现。想过创业吗?回答提示:这个问题可以显示你的冲劲,但如果你的回答是“有”的话,千万小心,下一个问题可能就是:那么为什么你不这样做呢?而且HR会觉得你在公司的稳定性弱,所以有或者没有不要马上说出来。对这项工作,你有哪些可预见的困难?回答提示:①不宜直接说出具体的困难,否则可能令对方怀疑应聘者不行。②可以尝试迂回战术,说出应聘者对困难所持有的态度——工作中出现一些困难是正常的,也是难免的,但是只要有坚忍不拔的毅力、良好的合作精神以及事前周密而充分的准备,任何困难都是可以克服。分析:一般问这个问题,面试者的希望就比较大了,因为已经在谈工作细节,但常规思路中的回答,又被面试官“骗”了。当面试官询问这个问题的时候,有两个目的。一是看看应聘者是不是在行,说出的困难是不是在这个职位中一般都不可避免的问题。二是想看一下应聘者解决困难的手法对不对,及公司能否提供这样的资源。而不是想了解应聘者对困难的态度。如果我录用你,你将怎样开展工作?回答提示: ①如果应聘者对于应聘的职位缺乏足够的了解,最好不要直接说出自己开展工作的具体办法。②可以尝试采用迂回战术来回答,如“首 先听取领导的指示和要求,然后就有关情况进行了解和熟悉,接下来制定一份近期的工作计划并报领导批准,最后根据计划开展工作。”分析:这个问题的主要目的也是了解应聘者的工作能力和计划性、条理性,而且重点想要知道细节。如果向思路中所讲的迂回战术,面试官会认为回避问题,如果引导了几次仍然是回避的话,此人绝对不会录用了。你希望与什么样的上级共事?回答提示:①通过应聘者对上级的“希望”可以判断出应聘者对自我要求的意识,这既上一个陷阱,又是一次机会。②最好回避对上级具体的希望,多谈对自己的要求。③如“做为刚步入社会的新人,我应该多要求自己尽快熟悉环境、适应环境,而不应该对环境提出什么要求,只要能发挥我的专长就可以了。分析:这个问题比较好的回答是,希望我的上级能够在工作中对我多指导,对我工作中的错误能够立即指出。总之,从上级指导这个方面谈,不会有大的纰漏。与上级意见不一致时,你将怎么办?回答提示:①一般可以这样回答“我会给上级以必要的解释和提醒,在这种情况下,我会服从上级的意见。”②如果面试你的是总经理,而你所应聘的职位另有一位经理,且这位经理当时不在场,可以这样回答:“对于非原则性问题,我会服从上级的意见,对于涉及公司利益的重大问题,我希望能向更高层领导反映。”分析:这个问题的标准答案是思路①,如果用②的回答,必死无疑。你没有摸清楚改公司的内部情况,先想打小报告,这样的人没有人敢要。您在前一家公司的离职原因是什么?回答提示:避免把“离职原因”说得太详细、太具体。尽量说原单位给你带来的提升,体现出感恩的心态,而不能抱怨公司哪里不好。相关例子:如“我离职是因为这家公司倒闭;我在公司工作了三年多,有较深的感情;从去年始,由于市场形势突变,公司的局面急转直下;到眼下这一步我觉得很遗憾,但还要面对显示,重新寻找能发挥我能力的舞台。”同一个面试问题并非只有一个答案,而同一个答案并不是在任何面试场合都有效,关键在应聘者掌握了规律后,对面试的具体情况进行把握,有意识地揣摩面试官提出问题的心理背景,然后投其所好。你工作经验欠缺,如何能胜任这项工作?回答提示:①如果招聘单位对应届毕业生的应聘者提出这个问题,说明招聘公司并不真正在乎“经验”,关键看应聘者怎样回答。②对这个问题的回答最好要体现出应聘者的诚恳、机智、果敢及敬业。③如“作为应届毕业生,在工作经验方面的确会有所欠缺,因此在读书期间我一直利用各种机会在这个行业里做兼职。我也发现,实际工作远比书本知识丰富、复杂。但我有较强的责任心、适应能力和学习能力,而且比较勤奋,所以在兼职中均能圆满完成各项工作,从中获取的经验也令我受益非浅。请公司放心,学校所学及兼职的工作经验使我一定能胜任这个职位。”分析:这个问题思路中的答案尚可。突出自己的吃苦能力和适应性以及学习能力(不是学习成绩)为好。如果你在这次面试中没有被录用,你怎么打算?回答提示:现在的社会是一个竞争的社会,从这次面试中也可看出这一点,有竞争就必然有优劣,有成功必定就会有失败。往往成功的背后有许多的困难和挫折,如果这次失败了也仅仅是一次而已,只有经过经验经历的积累才能塑造出一个完全的成功者。我会从以下几个方面来正确看待这次失败:①要敢于面对,面对这次失败不气馁,接受已经失去了这次机会就不会回头这个现实,从心理意志和精神上体现出对这次失败的抵抗力。要有自信,相信自己经历了这次之后经过努力一定能行,能够超越自我。②善于反思,对于这次面试经验要认真总结,思考剖析,能够从自身的角度找差距。正确对待自己,实事求是地评价自己,辩证的看待自己的长短得失,做一个明白人。③主要是技术上哪里薄弱,把不足的技术抓紧时间攻克。谈谈你做过的项目?回答提示:举一个你最有把握的例子,把来龙去脉说清楚,而不要说了很多却没有重点。切忌夸大其词,把别人的功劳到说成自己的,很多主管为了确保要用的人是最适合的,会打电话向你的前一个主管征询对你的看法及意见,所以如果说谎,是很容易穿梆的。谈谈你过去的工作中,最令你受挫的事情?回答提示:如果是学生可以用竞赛里的名次来举例;如果是员工可以以工作中自己努力而又没有做成功的项目来举例。分析:该问题的目的是借此了解你对挫折的容忍度及调解方式。你觉得原来的工作对你从事 PHP 开发有帮助吗?回答提示:这是针对求职者提出的问题,建议此时可以配合面试工作的特点作为主要依据来回答,如业务工作需要与人沟通,便可举出之前工作与人沟通的例子,经历了哪些困难,学习到哪些经验,把握这些要点做陈述,就可以轻易过关了。工作中你难以和同事、上司相处,你该怎么办?回答提示:①我会服从领导的指挥,配合同事的工作。②我会从自身找原因,仔细分析是不是自己工作做得不好让领导不满意,同事看不惯。还要看看是不是为人处世方面做得不好,如果是这样的话 我会努力改正。③如果我找不到原因,我会找机会跟他们沟通,请他们指出我的不足,有问题就及时改正。④作为优秀的员工,应该时刻以大局为重,即使在一段时间内,领导和同事对我不理解,我也会做好本职工作,虚心向他们学习,我相信,他们会看见我在努力,总有一天会对我微笑的。如果通过这次面试我们单位录用了你,但工作一段时间却发现你根本不适合这个职位,你怎么办?回答提示:工作一段时间后发现工作不适合你,有两种情况:①如果你确实热爱 PHP 程序员这个职业,那你就要不断学习,虚心向领导和同事学习业务知识和处事经验,了解这个职业的精神内涵和职业要求,力争减少差距;②你觉得这个职业可有可无,那还是趁早换个职业,去发现适合你的,你热爱的职业,那样你的发展前途也会大点,对单位和个人都有好处。而且我会推荐更适合 PHP 职位的人选。在完成某项工作时,你认为领导要求的方式不是最好的,自己还有更好的方法,你应该怎么做?回答提示:①原则上我会尊重和服从领导的工作安排,同时私底下找机会以请教的口吻,婉转地表达自己的想法,看看领导是否能改变想法。②如果领导没有采纳我的建议,我也同样会按领导的要求认真地去完成这项工作。③还有一种情况,假如领导要求的方式违背原则,我会坚决提出反对意见,如领导仍固执己见,我会毫不犹豫地再向上级领导反映。如果你的工作出现失误,给本公司造成经济损失,你认为该怎么办?回答提示:①我本意是为公司努力工作,如果造成经济损失,我认为首要的问题是想方设法去弥补或挽回经济损失。如果我无能力负责,希望公司帮助解决。②分清责任,各负其责,如果是我的责任,我甘愿受罚;如果是一个我负责的团队中别人的失误,也不能幸灾乐祸,作为一个团队,需要互相提携共同完成工作,安慰同事并且帮助同事查找原因总结经验。③总结经验教训,一个人的一生不可能不犯错误,重要的是能从自己的或者是别人的错误中吸取经验教训,并在今后的工作中避免发生同类的错误。检讨自己的工作方法、分析问题的深度和力度是否不够,以致出现了本可以避免的错误。如果你做的一项工作受到上级领导的表扬,但你主管领导却说是他做的,你该怎样?回答提示:我首先不会找那位上级领导说明这件事,我会主动找我的主管领导来沟通,因为沟通是解决人际关系的最好办法,但结果会有两种:①我的主管领导认识到自己的错误,我想我会视具体情况决定是否原谅他。②他更加变本加厉的来威胁我,那我会毫不犹豫地找我的上级领导反映此事,因为他这样做会造成负面影响,对今后的工作不利。祝你早日找到心仪的工作!原文传送门 https://www.hoehub.com/articles/Interview-answering-skills.html

January 18, 2019 · 1 min · jiezi

【剑指offer】7.旋转数组的最小数字

题目把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。基本思路肯定不能直接遍历,失去了这道题的意义旋转数组其实是由两个有序数组拼接而成的,因此我们可以使用二分法,只需要找到拼接点即可。(1)array[mid] > array[high]:出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。low = mid + 1(2)array[mid] == array[high]:出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边还是右边,这时只好一个一个试 。high = high - 1(3)array[mid] < array[high]:出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左边。因为右边必然都是递增的。high = mid代码function minNumberInRotateArray(arr){ let len = arr.length; if(len == 0) return 0; let low = 0, high = len - 1; while(low < high) { let mid = low + Math.floor((high-low)/2); if(arr[mid] > arr[high]) { low = mid + 1; } else if(arr[mid] == arr[high]) { high = high - 1; } else { high = mid; } } return arr[low];}扩展二分查找 function binarySearch(data, arr, start, end) { if (start > end) { return -1; } var mid = Math.floor((end + start) / 2); if (data == arr[mid]) { return mid; } else if (data < arr[mid]) { return binarySearch(data, arr, start, mid - 1); } else { return binarySearch(data, arr, mid + 1, end); } } ...

January 18, 2019 · 1 min · jiezi

为年后跳槽准备的133 道 Java 面试题及答案

为年后跳槽准备的133 道 Java 面试题及答案Java 面试随着时间的改变而改变。在过去的日子里,当你知道 String 和 StringBuilder 的区别就能让你直接进入第二轮面试,但是现在问题变得越来越高级,面试官问的问题也更深入。 在我初入职场的时候,类似于 Vector 与 Array 的区别、HashMap 与 Hashtable 的区别是最流行的问题,只需要记住它们,就能在面试中获得更好的机会,但这种情形已经不复存在。如今,你将会被问到许多 Java 程序员都没有看过的领域,如 NIO,[设计模式]“设计模式:可复用面向对象软件的基础”),成熟的单元测试,或者那些很难掌握的知识,如并发、算法、数据结构及编码。下面列出这份 Java 面试问题列表包含的主题:多线程,并发及线程基础数据类型转换的基本原则垃圾回收(GC)Java 集合框架数组字符串GOF 设计模式SOLID抽象类与接口Java 基础,如 equals 和 hashcode泛型与枚举Java IO 与 NIO常用网络协议Java 中的数据结构和算法正则表达式JVM 底层Java 最佳实践JDBCDate, Time 与 CalendarJava 处理 XMLJUnit编程现在是时候给你展示我近 5 年从各种面试中收集来的 133 个问题了。我确定你在自己的面试中见过很多这些问题,很多问题你也能正确回答。多线程、并发及线程的基础问题1)Java 中能创建 volatile 数组吗?能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不是整个数组。我的意思是,如果改变引用指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护作用了。2)volatile 能使得一个非原子操作变成原子操作吗?一个典型的例子是在类中有一个 long 类型的成员变量。如果你知道该成员变量会被多个线程访问,如计数器、价格等,你最好是将其设置为 volatile。为什么?因为 Java 中读取 long 类型变量不是原子的,需要分成两步,如果一个线程正在修改该 long 变量的值,另一个线程可能只能看到该值的一半(前 32 位)。但是对一个 volatile 型的 long 或 double 变量的读写是原子。3)volatile 修饰符的有过什么实践?一种实践是用 volatile 修饰 long 和 double 变量,使其能按原子类型来读写。double 和 long 都是64位宽,因此对这两种类型的读是分为两部分的,第一次读取第一个 32 位,然后再读剩下的 32 位,这个过程不是原子的,但 Java 中 volatile 型的 long 或 double 变量的读写是原子的。volatile 修复符的另一个作用是提供内存屏障(memory barrier),例如在分布式框架中的应用。简单的说,就是当你写一个 volatile 变量之前,Java 内存模型会插入一个写屏障(write barrier),读一个 volatile 变量之前,会插入一个读屏障(read barrier)。意思就是说,在你写一个 volatile 域时,能保证任何线程都能看到你写的值,同时,在写之前,也能保证任何数值的更新对所有线程是可见的,因为内存屏障会将其他所有写的值更新到缓存。4)volatile 类型变量提供什么保证?volatile 变量提供顺序和可见性保证,例如,JVM 或者 JIT为了获得更好的性能会对语句重排序,但是 volatile 类型变量即使在没有同步块的情况下赋值也不会与其他语句重排序。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。某些情况下,volatile 还能提供原子性,如读 64 位数据类型,像 long 和 double 都不是原子的,但 volatile 类型的 double 和 long 就是原子的。5) 10 个线程和 2 个线程的同步代码,哪个更容易写?从写代码的角度来说,两者的复杂度是相同的,因为同步代码与线程数量是相互独立的。但是同步策略的选择依赖于线程的数量,因为越多的线程意味着更大的竞争,所以你需要利用同步技术,如锁分离,这要求更复杂的代码和专业知识。6)你是如何调用 wait()方法的?使用 if 块还是循环?为什么?wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:// The standard idiom for using the wait methodsynchronized (obj) {while (condition does not hold)obj.wait(); // (Releases lock, and reacquires on wakeup)… // Perform action appropriate to condition}参见 [Effective Java]第 69 条,获取更多关于为什么应该在循环中来调用 wait 方法的内容。7)什么是多线程环境下的伪共享(false sharing)?伪共享是多线程系统(每个处理器有自己的局部缓存)中一个众所周知的性能问题。伪共享发生在不同处理器的上的线程对变量的修改依赖于相同的缓存行,如下图所示:有经验程序员的 Java 面试题伪共享问题很难被发现,因为线程可能访问完全不同的全局变量,内存中却碰巧在很相近的位置上。如其他诸多的并发问题,避免伪共享的最基本方式是仔细审查代码,根据缓存行来调整你的数据结构。8)什么是 Busy spin?我们为什么要使用它?Busy spin 是一种在不释放 CPU 的基础上等待事件的技术。它经常用于避免丢失 CPU 缓存中的数据(如果线程先暂停,之后在其他CPU上运行就会丢失)。所以,如果你的工作要求低延迟,并且你的线程目前没有任何顺序,这样你就可以通过循环检测队列中的新消息来代替调用 sleep() 或 wait() 方法。它唯一的好处就是你只需等待很短的时间,如几微秒或几纳秒。LMAX 分布式框架是一个高性能线程间通信的库,该库有一个 BusySpinWaitStrategy 类就是基于这个概念实现的,使用 busy spin 循环 EventProcessors 等待屏障。9)Java 中怎么获取一份线程 dump 文件?在 Linux 下,你可以通过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java 应用的 dump 文件。在 Windows 下,你可以按下 Ctrl + Break 来获取。这样 JVM 就会将线程的 dump 文件打印到标准输出或错误文件中,它可能打印在控制台或者日志文件中,具体位置依赖应用的配置。如果你使用Tomcat。10)Swing 是线程安全的?不是,Swing 不是线程安全的。你不能通过任何线程来更新 Swing 组件,如 JTable、JList 或 JPanel,事实上,它们只能通过 GUI 或 AWT 线程来更新。这就是为什么 Swing 提供 invokeAndWait() 和 invokeLater() 方法来获取其他线程的 GUI 更新请求。这些方法将更新请求放入 AWT 的线程队列中,可以一直等待,也可以通过异步更新直接返回结果。你也可以在参考答案中查看和学习到更详细的内容。11)什么是线程局部变量?线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。12)用 wait-notify 写一段代码来解决生产者-消费者问题?答案http://java67.blogspot.sg/201…请参考答案中的示例代码。只要记住在同步块中调用 wait() 和 notify()方法,如果阻塞,通过循环来测试等待条件。13) 用 Java 写一个线程安全的单例模式(Singleton)?答案http://javarevisited.blogspot…请参考答案中的示例代码,这里面一步一步教你创建一个线程安全的 Java 单例类。当我们说线程安全时,意思是即使初始化是在多线程环境中,仍然能保证单个实例。Java 中,使用枚举作为单例类是最简单的方式来创建线程安全单例模式的方式。14)Java 中 sleep 方法和 wait 方法的区别?虽然两者都是用来暂停当前运行的线程,但是 sleep() 实际上只是短暂停顿,因为它不会释放锁,而 wait() 意味着条件等待,这就是为什么该方法要释放锁,因为只有这样,其他等待的线程才能在满足条件时获取到该锁。15)什么是不可变对象(immutable object)?Java 中怎么创建一个不可变对象?不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。详情参见答案,一步一步指导你在 Java 中创建一个不可变的类。16)我们能创建一个包含可变对象的不可变对象吗?是的,我们是可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝。最常见的例子就是对象中包含一个日期对象的引用。数据类型和 Java 基础面试问题17)Java 中应该使用什么数据类型来代表价格?如果不是特别关心内存和性能的话,使用BigDecimal,否则使用预定义精度的 double 类型。18)怎么将 byte 转换为 String?可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同。19)Java 中怎样将 bytes 转换为 long 类型?这个问题你来回答 :-)20)我们能将 int 强制转换为 byte 类型的变量吗?如果该值大于 byte 类型的范围,将会出现什么现象?是的,我们可以做强制转换,但是 Java 中 int 是 32 位的,而 byte 是 8 位的,所以,如果强制转化是,int 类型的高 24 位将会被丢弃,byte 类型的范围是从 -128 到 128。21)存在两个类,B 继承 A,C 继承 B,我们能将 B 转换为 C 么?如 C = (C) B;答案http://javarevisited.blogspot…22)哪个类包含 clone 方法?是 Cloneable 还是 Object?java.lang.Cloneable 是一个标示性接口,不包含任何方法,clone 方法在 object 类中定义。并且需要知道 clone() 方法是一个本地方法,这意味着它是由 c 或 c++ 或 其他本地语言实现的。23)Java 中 ++ 操作符是线程安全的吗?答案:不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。23)不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。24)a = a + b 与 a += b 的区别+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两这个整型相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。如果加法操作的结果比 a 的最大值要大,则 a+b 会出现编译错误,但是 a += b 没问题,如下:byte a = 127;byte b = 127;b = a + b; // error : cannot convert from int to byteb += a; // ok(译者注:这个地方应该表述的有误,其实无论 a+b 的值为多少,编译器都会报错,因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)25)我能在不进行强制转换的情况下将一个 double 值赋值给 long 类型的变量吗?不行,你不能在没有强制类型转换的前提下将一个 double 值赋值给 long 类型的变量,因为 double 类型的范围比 long 类型更广,所以必须要进行强制转换。26)30.1 == 0.3 将会返回什么?true 还是 false?false,因为有些浮点数不能完全精确的表示出来。27)int 和 Integer 哪个会占用更多的内存?Integer 对象会占用更多的内存。Integer 是一个对象,需要存储对象的元数据。但是 int 是一个原始类型的数据,所以占用的空间更少。28)为什么 Java 中的 String 是不可变的(Immutable)?Java 中的 String 不可变是因为 Java 的设计者认为字符串使用非常频繁,将字符串设置为不可变可以允许多个客户端之间共享相同的字符串。29)我们能在 Switch 中使用 String 吗?从 Java 7 开始,我们可以在 switch case 中使用字符串,但这仅仅是一个语法糖。内部实现在 switch 中使用字符串的 hash code。30)Java 中的构造器链是什么?当你从一个构造器中调用另一个构造器,就是Java 中的构造器链。这种情况只在重载了类的构造器的时候才会出现。JVM 底层 与 GC(Garbage Collection) 的面试问题31)64 位 JVM 中,int 的长度是多数?Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。32)Serial 与 Parallel GC之间的不同之处?Serial 与 Parallel 在GC执行的时候都会引起 stop-the-world。它们之间主要不同 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,而 parallel 收集器使用多个 GC 线程来执行。33)32 位和 64 位的 JVM,int 类型变量的长度是多数?32 位和 64 位的 JVM 中,int 类型变量的长度是相同的,都是 32 位或者 4 个字节。34)Java 中 WeakReference 与 SoftReference的区别?虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。35)WeakHashMap 是怎么工作的?WeakHashMap 的工作与正常的 HashMap 类似,但是使用弱引用作为 key,意思就是当 key 对象没有任何引用时,key/value 将会被回收。36)JVM 选项 -XX:+UseCompressedOops 有什么作用?为什么要使用?当你将你的应用从 32 位的 JVM 迁移到 64 位的 JVM 时,由于对象的指针从 32 位增加到了 64 位,因此堆内存会突然增加,差不多要翻倍。这也会对 CPU 缓存(容量比内存小很多)的数据产生不利的影响。因为,迁移到 64 位的 JVM 主要动机在于可以指定最大堆大小,通过压缩 OOP 可以节省一定的内存。通过 -XX:+UseCompressedOops 选项,JVM 会使用 32 位的 OOP,而不是 64 位的 OOP。37)怎样通过 Java 程序来判断 JVM 是 32 位 还是 64 位?你可以检查某些系统属性如 sun.arch.data.model 或 os.arch 来获取该信息。38)32 位 JVM 和 64 位 JVM 的最大堆内存分别是多数?理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会比这个小很多。不同操作系统之间不同,如 Windows 系统大约 1.5 GB,Solaris 大约 3GB。64 位 JVM允许指定最大的堆内存,理论上可以达到 2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到 100GB。甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的。39)JRE、JDK、JVM 及 JIT 之间有什么不同?JRE 代表 Java 运行时(Java run-time),是运行 Java 引用所必须的。JDK 代表 Java 开发工具(Java development kit),是 Java 程序的开发工具,如 Java 编译器,它也包含 JRE。JVM 代表 Java 虚拟机(Java virtual machine),它的责任是运行 Java 应用。JIT 代表即时编译(Just In Time compilation),当代码执行的次数超过一定的阈值时,会将 Java 字节码转换为本地代码,如,主要的热点代码会被准换为本地代码,这样有利大幅度提高 Java 应用的性能。3 年工作经验的 Java 面试题40)解释 Java 堆空间及 GC?当通过 Java 命令启动 Java 进程的时候,会为它分配内存。内存的一部分用于创建堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC 是 JVM 内部的一个进程,回收无效对象的内存用于将来的分配。JVM 底层面试题及答案41)你能保证 GC 执行吗?不能,虽然你可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC 的执行。42)怎么获取 Java 程序使用的内存?堆使用的百分比?可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。Runtime.freeMemory() 方法返回剩余空间的字节数,Runtime.totalMemory() 方法总内存的字节数,Runtime.maxMemory() 返回最大内存的字节数。43)Java 中堆和栈有什么区别?JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。关于内存的的面试问题和答案Java 基本概念面试题44)“a==b”和”a.equals(b)”有什么区别?如果 a 和 b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。45)a.hashCode() 有什么用?与 a.equals(b) 有什么关系?hashCode() 方法是相应对象整型的 hash 值。它常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap等等。它与 equals() 方法关系特别紧密。根据 Java 规范,两个使用 equal() 方法来判断相等的对象,必须具有相同的 hash code。46)final、finalize 和 finally 的不同之处?final 是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。finalize 方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会,但是什么时候调用 finalize 没有保证。finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行,无论在 try 块中是否有发生异常。47)Java 中的编译期常量是什么?使用它又什么风险?公共静态不可变(public static final )变量也就是我们所说的编译期常量,这里的 public 可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。Java 集合框架的面试题这部分也包含数据结构、算法及数组的面试问题48) List、Set、Map 和 Queue 之间的区别(答案)List 是一个有序集合,允许元素重复。它的某些实现可以提供基于下标值的常量访问时间,但是这不是 List 接口保证的。Set 是一个无序集合。49)poll() 方法和 remove() 方法的区别?poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。50)Java 中 LinkedHashMap 和 PriorityQueue 的区别是什么?PriorityQueue 保证最高或者最低优先级的的元素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个 PriorityQueue 时,没有任何顺序保证,但是 LinkedHashMap 课保证遍历顺序是元素插入的顺序。51)ArrayList 与 LinkedList 的不区别?最明显的区别是 ArrrayList 底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构书链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。更多细节的讨论参见答案。52)用哪两种方式来实现集合的排序?你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有顺序的的集合,如 list,然后通过 Collections.sort() 来排序。53)Java 中怎么打印数组?你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法来打印数组。由于数组没有实现 toString() 方法,所以如果将数组传递给 System.out.println() 方法,将无法打印出数组的内容,但是 Arrays.toString() 可以打印每个元素。54)Java 中的 LinkedList 是单向链表还是双向链表?是双向链表,你可以检查 JDK 的源码。在 Eclipse,你可以使用快捷键 Ctrl + T,直接在编辑器中打开该类。55)Java 中的 TreeMap 是采用什么树实现的?(答案)Java 中的 TreeMap 是使用红黑树实现的。56) Hashtable 与 HashMap 有什么不同之处?这两个类有许多不同的地方,下面列出了一部分:a) Hashtable 是 JDK 1 遗留下来的类,而 HashMap 是后来增加的。b)Hashtable 是同步的,比较慢,但 HashMap 没有同步策略,所以会更快。c)Hashtable 不允许有个空的 key,但是 HashMap 允许出现一个 null key。更多的不同之处参见答案。57)Java 中的 HashSet,内部是如何工作的?HashSet 的内部采用 HashMap来实现。由于 Map 需要 key 和 value,所以所有 key 的都有一个默认 value。类似于 HashMap,HashSet 不允许重复的 key,只允许有一个null key,意思就是 HashSet 中只允许存储一个 null 对象。58)写一段代码在遍历 ArrayList 时移除一个元素?该问题的关键在于面试者使用的是 ArrayList 的 remove() 还是 Iterator 的 remove()方法。这有一段示例代码,是使用正确的方式来实现在遍历的过程中移除元素,而不会出现 ConcurrentModificationException 异常的示例代码。59)我们能自己写一个容器类,然后使用 for-each 循环码?可以,你可以写一个自己的容器类。如果你想使用 Java 中增强的循环来遍历,你只需要实现 Iterable 接口。如果你实现 Collection 接口,默认就具有该属性。60)ArrayList 和 HashMap 的默认大小是多数?在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是16个元素(必须是2的幂)。这就是 Java 7 中 ArrayList 和 HashMap 类的代码片段:// from ArrayList.java JDK 1.7private static final int DEFAULT_CAPACITY = 10; //from HashMap.java JDK 7static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 1661)有没有可能两个不相等的对象有有相同的 hashcode?有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。相等 hashcode 值的规定只是说如果两个对象相等,必须有相同的hashcode 值,但是没有关于不相等对象的任何规定。62)两个相同的对象会有不同的的 hash code 吗?不能,根据 hash code 的规定,这是不可能的。63)我们可以在 hashcode() 中使用随机数字吗?答案http://javarevisited.blogspot…不行,因为对象的 hashcode 值必须是相同的。参见答案获取更多关于 Java 中重写 hashCode() 方法的知识。64)Java 中,Comparator 与 Comparable 有什么不同?Comparable 接口用于定义对象的自然顺序,而 comparator 通常用于定义用户定制的顺序。Comparable 总是只有一个,但是可以有多个 comparator 来定义对象的顺序。65)为什么在重写 equals 方法的时候需要重写 hashCode 方法?(答案)因为有强制的规范指定需要同时重写 hashcode 与 equal 是方法,许多容器类,如 HashMap、HashSet 都依赖于 hashcode 与 equals 的规定。Java IO 和 NIO 的面试题IO 是 Java 面试中一个非常重要的点。你应该很好掌握 Java IO,NIO,NIO2 以及与操作系统,磁盘 IO 相关的基础知识。下面是 Java IO 中经常问的问题。66)在我 Java 程序中,我有三个 socket,我需要多少个线程来处理?67)Java 中怎么创建 ByteBuffer?byte[] bytes = new byte[10];ByteBuffer buf = ByteBuffer.wrap(bytes);68)Java 中,怎么读写 ByteBuffer ?69)Java 采用的是大端还是小端?70)ByteBuffer 中的字节序是什么?71)Java 中,直接缓冲区与非直接缓冲器有什么区别?答案http://javarevisited.blogspot…72)Java 中的内存映射缓存区是什么?答案http://javarevisited.blogspot…73)socket 选项 TCP NO DELAY 是指什么?74)TCP 协议与 UDP 协议有什么区别?答案http://javarevisited.blogspot…75)Java 中,ByteBuffer 与 StringBuffer有什么区别?(答案)Java 最佳实践的面试问题包含 Java 中各个部分的最佳实践,如集合,字符串,IO,多线程,错误和异常处理,设计模式等等。76)Java 中,编写多线程程序的时候你会遵循哪些最佳实践?这是我在写Java 并发程序的时候遵循的一些最佳实践:a)给线程命名,这样可以帮助调试。b)最小化同步的范围,而不是将整个方法同步,只对关键部分做同步。c)如果可以,更偏向于使用 volatile 而不是 synchronized。d)使用更高层次的并发工具,而不是使用 wait() 和 notify() 来实现线程间通信,如 BlockingQueue,CountDownLatch 及 Semeaphore。e)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。77)说出几点 Java 中使用 Collections 的最佳实践这是我在使用 Java 中 Collectionc 类的一些最佳实践:a)使用正确的集合类,例如,如果不需要同步列表,使用 ArrayList 而不是 Vector。b)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。c)使用接口代表和访问集合,如使用List存储 ArrayList,使用 Map 存储 HashMap 等等。d)使用迭代器来循环集合。e)使用集合的时候使用泛型。78)说出至少 5 点在 Java 中使用线程的最佳实践。答案http://java67.blogspot.com/20…这个问题与之前的问题类似,你可以使用上面的答案。对线程来说,你应该:a)对线程命名b)将线程和任务分离,使用线程池执行器来执行 Runnable 或 Callable。c)使用线程池79)说出 5 条 IO 的最佳实践(答案)IO 对 Java 应用的性能非常重要。理想情况下,你不应该在你应用的关键路径上避免 IO 操作。下面是一些你应该遵循的 Java IO 最佳实践:a)使用有缓冲区的 IO 类,而不要单独读取字节或字符。b)使用 NIO 和 NIO2c)在 finally 块中关闭流,或者使用 try-with-resource 语句。d)使用内存映射文件获取更快的 IO。80)列出 5 个应该遵循的 JDBC 最佳实践答案http://javarevisited.blogspot…有很多的最佳实践,你可以根据你的喜好来例举。下面是一些更通用的原则:a)使用批量的操作来插入和更新数据b)使用 PreparedStatement 来避免 SQL 异常,并提高性能。c)使用数据库连接池d)通过列名来获取结果集,不要使用列的下标来获取。81)说出几条 Java 中方法重载的最佳实践?下面有几条可以遵循的方法重载的最佳实践来避免造成自动装箱的混乱。a)不要重载这样的方法:一个方法接收 int 参数,而另个方法接收 Integer 参数。b)不要重载参数数量一致,而只是参数顺序不同的方法。c)如果重载的方法参数个数多于 5 个,采用可变参数。Date、Time 及 Calendar 的面试题82)在多线程环境下,SimpleDateFormat 是线程安全的吗?不是,非常不幸,DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使用,除非是在对外线程安全的环境中使用,如 将 SimpleDateFormat 限制在 ThreadLocal 中。如果你不这么做,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。因此,从日期、时间处理的所有实践来说,我强力推荐 joda-time 库。83)Java 中如何格式化一个日期?如格式化为 ddMMyyyy 的形式?答案http://javarevisited.blogspot…Java 中,可以使用 SimpleDateFormat 类或者 joda-time 库来格式日期。DateFormat 类允许你使用多种流行的格式来格式化日期。参见答案中的示例代码,代码中演示了将日期格式化成不同的格式,如 dd-MM-yyyy 或 ddMMyyyy。84)Java 中,怎么在格式化的日期中显示时区?答案http://java67.blogspot.sg/201…85)Java 中 java.util.Date 与 java.sql.Date 有什么区别?答案http://java67.blogspot.sg/201…86)Java 中,如何计算两个日期之间的差距?程序http://javarevisited.blogspot…87)Java 中,如何将字符串 YYYYMMDD 转换为日期?答案http://java67.blogspot.sg/201…单元测试 JUnit 面试题89)如何测试静态方法?(答案)可以使用 PowerMock 库来测试静态方法。90)怎么利用 JUnit 来测试一个方法的异常?答案http://javarevisited.blogspot…91)你使用过哪个单元测试库来测试你的 Java 程序?92)@Before 和 @BeforeClass 有什么区别?答案http://javarevisited.blogspot…编程和代码相关的面试题93)怎么检查一个字符串只包含数字?解决方案http://java67.blogspot.com/20…94)Java 中如何利用泛型写一个 LRU 缓存?95)写一段 Java 程序将 byte 转换为 long?95)在不使用 StringBuffer 的前提下,怎么反转一个字符串?解决方案http://java67.blogspot.com/20…97)Java 中,怎么获取一个文件中单词出现的最高频率?解决方案http://java67.blogspot.com/20…98)如何检查出两个给定的字符串是反序的?解决方案http://javarevisited.blogspot…99)Java 中,怎么打印出一个字符串的所有排列?解决方案http://javarevisited.blogspot…100)Java 中,怎样才能打印出数组中的重复元素?解决方案http://javarevisited.blogspot…101)Java 中如何将字符串转换为整数?String s=“123”;int i;第一种方法:i=Integer.parseInt(s);第二种方法:i=Integer.valueOf(s).intValue();102)在没有使用临时变量的情况如何交换两个整数变量的值?解决方案https://blog.csdn.net/zidane_…关于 OOP 和设计模式的面试题这部分包含 Java 面试过程中关于 SOLID 的设计原则,OOP 基础,如类,对象,接口,继承,多态,封装,抽象以及更高级的一些概念,如组合、聚合及关联。也包含了 GOF 设计模式的问题。103)接口是什么?为什么要使用接口而不是直接使用具体类?接口用于定义 API。它定义了类必须得遵循的规则。同时,它提供了一种抽象,因为客户端只使用接口,这样可以有多重实现,如 List 接口,你可以使用可随机访问的 ArrayList,也可以使用方便插入和删除的 LinkedList。接口中不允许写代码,以此来保证抽象,但是 Java 8 中你可以在接口声明静态的默认方法,这种方法是具体的。104)Java 中,抽象类与接口之间有什么不同?Java 中,抽象类和接口有很多不同之处,但是最重要的一个是 Java 中限制一个类只能继承一个类,但是可以实现多个接口。抽象类可以很好的定义一个家族类的默认行为,而接口能更好的定义类型,有助于后面实现多态机制。105)除了单例模式,你在生产环境中还用过什么设计模式?这需要根据你的经验来回答。一般情况下,你可以说依赖注入,工厂模式,装饰模式或者观察者模式,随意选择你使用过的一种即可。不过你要准备回答接下的基于你选择的模式的问题。106)你能解释一下里氏替换原则吗?答案https://blog.csdn.net/pu_xubo…107) 什么情况下会违反迪米特法则?为什么会有这个问题?迪米特法则建议“只和朋友说话,不要陌生人说话”,以此来减少类之间的耦合。108)适配器模式是什么?什么时候使用?适配器模式提供对接口的转换。如果你的客户端使用某些接口,但是你有另外一些接口,你就可以写一个适配去来连接这些接口。109)什么是“依赖注入”和“控制反转”?为什么有人使用?控制反转(IOC)是Spring框架的核心思想,用我自己的话说,就是你要做一件事,别自己可劲new了,你就说你要干啥,然后外包出去就好依赖注入(DI) 在我浅薄的想法中,就是通过接口的引用和构造方法的表达,将一些事情整好了反过来传给需要用到的地方110)抽象类是什么?它与接口有什么区别?你为什么要使用过抽象类?接口用于规范,抽象类用于共性. 声明方法的存在而不去实现它的类被叫做抽象类接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的。111)构造器注入和 setter 依赖注入,那种方式更好?每种方式都有它的缺点和优点。构造器注入保证所有的注入都被初始化,但是 setter 注入提供更好的灵活性来设置可选依赖。如果使用 XML 来描述依赖,Setter 注入的可读写会更强。经验法则是强制依赖使用构造器注入,可选依赖使用 setter 注入。112)依赖注入和工程模式之间有什么不同?虽然两种模式都是将对象的创建从应用的逻辑中分离,但是依赖注入比工程模式更清晰。通过依赖注入,你的类就是 POJO,它只知道依赖而不关心它们怎么获取。使用工厂模式,你的类需要通过工厂来获取依赖。因此,使用 DI 会比使用工厂模式更容易测试。113)适配器模式和装饰器模式有什么区别?虽然适配器模式和装饰器模式的结构类似,但是每种模式的出现意图不同。适配器模式被用于桥接两个接口,而装饰模式的目的是在不修改类的情况下给类增加新的功能。114)适配器模式和代理模式之前有什么不同?这个问题与前面的类似,适配器模式和代理模式的区别在于他们的意图不同。由于适配器模式和代理模式都是封装真正执行动作的类,因此结构是一致的,但是适配器模式用于接口之间的转换,而代理模式则是增加一个额外的中间层,以便支持分配、控制或智能访问。115)什么是模板方法模式?模板方法提供算法的框架,你可以自己去配置或定义步骤。例如,你可以将排序算法看做是一个模板。它定义了排序的步骤,但是具体的比较,可以使用 Comparable 或者其语言中类似东西,具体策略由你去配置。列出算法概要的方法就是众所周知的模板方法。116)什么时候使用访问者模式?访问者模式用于解决在类的继承层次上增加操作,但是不直接与之关联。这种模式采用双派发的形式来增加中间层。117)什么时候使用组合模式?组合模式使用树结构来展示部分与整体继承关系。它允许客户端采用统一的形式来对待单个对象和对象容器。当你想要展示对象这种部分与整体的继承关系时采用组合模式。118)继承和组合之间有什么不同?虽然两种都可以实现代码复用,但是组合比继承共灵活,因为组合允许你在运行时选择不同的实现。用组合实现的代码也比继承测试起来更加简单。119)描述 Java 中的重载和重写?重载和重写都允许你用相同的名称来实现不同的功能,但是重载是编译时活动,而重写是运行时活动。你可以在同一个类中重载方法,但是只能在子类中重写方法。重写必须要有继承。120)Java 中,嵌套公共静态类与顶级类有什么不同?类的内部可以有多个嵌套公共静态类,但是一个 Java 源文件只能有一个顶级公共类,并且顶级公共类的名称与源文件名称必须一致。121) OOP 中的 组合、聚合和关联有什么区别?如果两个对象彼此有关系,就说他们是彼此相关联的。组合和聚合是面向对象中的两种形式的关联。组合是一种比聚合更强力的关联。组合中,一个对象是另一个的拥有者,而聚合则是指一个对象使用另一个对象。如果对象 A 是由对象 B 组合的,则 A 不存在的话,B一定不存在,但是如果 A 对象聚合了一个对象 B,则即使 A 不存在了,B 也可以单独存在。122)给我一个符合开闭原则的设计模式的例子?开闭原则要求你的代码对扩展开放,对修改关闭。这个意思就是说,如果你想增加一个新的功能,你可以很容易的在不改变已测试过的代码的前提下增加新的代码。有好几个设计模式是基于开闭原则的,如策略模式,如果你需要一个新的策略,只需要实现接口,增加配置,不需要改变核心逻辑。一个正在工作的例子是 Collections.sort() 方法,这就是基于策略模式,遵循开闭原则的,你不需为新的对象修改 sort() 方法,你需要做的仅仅是实现你自己的 Comparator 接口。123)抽象工厂模式和原型模式之间的区别?抽象工厂模式:通常由工厂方法模式来实现。但一个工厂中往往含有多个工厂方法生成一系列的产品。这个模式强调的是客户代码一次保证只使用一个系列的产品。当要切换为另一个系列的产品,换一个工厂类即可。原型模式:工厂方法的最大缺点就是,对应一个继承体系的产品类,要有一个同样复杂的工厂类的继承体系。我们可以把工厂类中的工厂方法放到产品类自身之中吗?如果这样的话,就可以将两个继承体系为一个。这也就是原型模式的思想,原型模式中的工厂方法为clone,它会返回一个拷贝(可以是浅拷贝,也可以是深拷贝,由设计者决定)。为了保证用户代码中到时可以通过指针调用clone来动态绑定地生成所需的具体的类。这些原型对象必须事先构造好。原型模式想对工厂方法模式的另一个好处是,拷贝的效率一般对构造的效率要高。124)什么时候使用享元模式?享元模式通过共享对象来避免创建太多的对象。为了使用享元模式,你需要确保你的对象是不可变的,这样你才能安全的共享。JDK 中 String 池、Integer 池以及 Long 池都是很好的使用了享元模式的例子。Java 面试中其他各式各样的问题这部分包含 Java 中关于 XML 的面试题,正则表达式面试题,Java 错误和异常及序列化面试题125)嵌套静态类与顶级类有什么区别?一个公共的顶级类的源文件名称与类名相同,而嵌套静态类没有这个要求。一个嵌套类位于顶级类内部,需要使用顶级类的名称来引用嵌套静态类,如 HashMap.Entry 是一个嵌套静态类,HashMap 是一个顶级类,Entry是一个嵌套静态类。126)你能写出一个正则表达式来判断一个字符串是否是一个数字吗?一个数字字符串,只能包含数字,如 0 到 9 以及 +、- 开头,通过这个信息,你可以下一个如下的正则表达式来判断给定的字符串是不是数字。首先要import java.util.regex.Pattern 和 java.util.regex.Matcherpublic boolean isNumeric(String str){ Pattern pattern = Pattern.compile("[0-9]"); Matcher isNum = pattern.matcher(str); if( !isNum.matches() ){ return false; } return true; } 127)Java 中,受检查异常 和 不受检查异常的区别?受检查异常编译器在编译期间检查。对于这种异常,方法强制处理或者通过 throws 子句声明。其中一种情况是 Exception 的子类但不是 RuntimeException 的子类。非受检查是 RuntimeException 的子类,在编译阶段不受编译器的检查。128)Java 中,throw 和 throws 有什么区别throw 用于抛出 java.lang.Throwable 类的一个实例化对象,意思是说你可以通过关键字 throw 抛出一个 Error 或者 一个Exception,如:throw new IllegalArgumentException(“size must be multiple of 2″)而throws 的作用是作为方法声明和签名的一部分,方法被抛出相应的异常以便调用者能处理。Java 中,任何未处理的受检查异常强制在 throws 子句中声明。129)Java 中,Serializable 与 Externalizable 的区别?Serializable 接口是一个序列化 Java 类的接口,以便于它们可以在网络上传输或者可以将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化方式,成本高、脆弱而且不安全。Externalizable 允许你控制整个序列化过程,指定特定的二进制格式,增加安全机制。130)Java 中,DOM 和 SAX 解析器有什么不同?DOM 解析器将整个 XML 文档加载到内存来创建一棵 DOM 模型树,这样可以更快的查找节点和修改 XML 结构,而 SAX 解析器是一个基于事件的解析器,不会将整个 XML 文档加载到内存。由于这个原因,DOM 比 SAX 更快,也要求更多的内存,不适合于解析大 XML 文件。131)说出 JDK 1.7 中的三个新特性?虽然 JDK 1.7 不像 JDK 5 和 8 一样的大版本,但是,还是有很多新的特性,如 try-with-resource 语句,这样你在使用流或者资源的时候,就不需要手动关闭,Java 会自动关闭。Fork-Join 池某种程度上实现 Java 版的 Map-reduce。允许 Switch 中有 String 变量和文本。菱形操作符(<>)用于类型推断,不再需要在变量声明的右边申明泛型,因此可以写出可读写更强、更简洁的代码。另一个值得一提的特性是改善异常处理,如允许在同一个 catch 块中捕获多个异常。132)说出 5 个 JDK 1.8 引入的新特性?Java 8 在 Java 历史上是一个开创新的版本,下面 JDK 8 中 5 个主要的特性:Lambda 表达式,允许像对象一样传递匿名函数Stream API,充分利用现代多核 CPU,可以写出很简洁的代码Date 与 Time API,最终,有一个稳定、简单的日期和时间库可供你使用扩展方法,现在,接口中可以有静态、默认方法。重复注解,现在你可以将相同的注解在同一类型上使用多次。133)Java 中,Maven 和 ANT 有什么区别?虽然两者都是构建工具,都用于创建 Java 应用,但是 Maven 做的事情更多,在基于“约定优于配置”的概念下,提供标准的Java 项目结构,同时能为应用自动管理依赖(应用中所依赖的 JAR 文件),Maven 与 ANT 工具更多的不同之处请参见答案。这就是所有的面试题,如此之多,是不是?我可以保证,如果你能回答列表中的所有问题,你就可以很轻松的应付任何核心 Java 或者高级 Java 面试。虽然,这里没有涵盖 Servlet、JSP、JSF、JPA,JMS,EJB 及其它 Java EE 技术,也没有包含主流的框架如 Spring MVC,Struts 2.0,Hibernate,也没有包含 SOAP 和 RESTful web service,但是这份列表对做 Java 开发的、准备应聘 Java web 开发职位的人还是同样有用的,因为所有的 Java 面试,开始的问题都是 Java 基础和 JDK API 相关的。如果你认为我这里有任何应该在这份列表中而被我遗漏了的 Java 流行的问题,你可以自由的给我建议。我的目的是从最近的面试中创建一份最新的、最优的 Java 面试问题列表。 ...

January 17, 2019 · 7 min · jiezi

前端基本功-示例代码 (二)

前端基本功-示例代码 (一) 点这里前端基本功-示例代码 (二) 点这里1.一像素伪类 + transform 实现对于老项目,有没有什么办法能兼容1px的尴尬问题了,个人认为伪类+transform是比较完美的方法了。原理是把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border ,并 transform 的 scale 缩小一半,原先的元素相对定位,新做的 border 绝对定位。单条border样式设置:.scale-1px{ position: relative; border:none;}.scale-1px:after{ content: ‘’; position: absolute; bottom: 0; background: #000; width: 100%; height: 1px; -webkit-transform: scaleY(0.5); transform: scaleY(0.5); -webkit-transform-origin: 0 0; transform-origin: 0 0;}四条boder样式设置:.scale-1px{ position: relative; margin-bottom: 20px; border:none;}.scale-1px:after{ content: ‘’; position: absolute; top: 0; left: 0; border: 1px solid #000; -webkit-box-sizing: border-box; box-sizing: border-box; width: 200%; height: 200%; -webkit-transform: scale(0.5); transform: scale(0.5); -webkit-transform-origin: left top; transform-origin: left top;}最好在使用前也判断一下,结合 JS 代码,判断是否 Retina 屏:if(window.devicePixelRatio && devicePixelRatio >= 2){ document.querySelector(‘ul’).className = ‘scale-1px’;}方法二/移动端正常展示1px的问题 start/%border-1px{ display: block; position:absolute; left: 0; width: 100%; content: ’ ‘;}.border-1px{ position: relative; &::after{ @extend %border-1px; bottom: 0; border-top: 1px solid #ccc; } &::before{ @extend %border-1px; top: 0; border-bottom: 1px solid #ccc; }}@media (-webkit-min-device-pixel-ratio:1.5),(min-device-pixel-ratio:1.5){ .border-1px{ &::after{ -webkit-transform: scaleY(0.7); transform: scaleY(0.7); } }}@media (-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2){ .border-1px{ &::after{ -webkit-transform: scaleY(0.5); transform: scaleY(0.5); } }}/移动端正常展示1px的问题 end/方法三 .hairline-border { box-shadow: 0 0 0 1px; } @media (min-resolution: 2dppx) { .hairline-border { box-shadow: 0 0 0 0.5px red; } } @media (min-resolution: 3dppx) { .hairline-border { box-shadow: 0 0 0 0.33333333px; } } @media (min-resolution: 4dppx) { .hairline-border { box-shadow: 0 0 0 0.25px; } }2.动画animation:mymove 5s infinite;@keyframes mymove { from {top:0px;} to {top:200px;}}js实现一个持续的动画效果//兼容性处理window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback){ window.setTimeout(callback, 1000 / 60); };})();var e = document.getElementById(“e”);var flag = true;var left = 0;function render() { left == 0 ? flag = true : left == 100 ? flag = false : ‘’; flag ? e.style.left = ${left++}px : e.style.left = ${left--}px;}(function animloop() { render(); requestAnimFrame(animloop);})();3. 实现sum(2)(3)// 写一个 function 让下面两行代码输出的结果都为 5console.log(sum(2, 3));console.log(sum(2)(3));实现function sum() { var cache; if (arguments.length === 1) { cache = arguments[0]; return function ( number ) {return cache + number;}; } else return arguments[0] + arguments[1];};4. 函数柯里化函数柯里化指的是将能够接收多个参数的函数转化为接收单一参数的函数,并且返回接收余下参数或结果的新函数的技术。函数柯里化的主要作用和特点就是参数复用、提前返回和延迟计算/执行。1. 参数复用引导// 普通函数function add(x,y){ return x + y;}add(3,4); //5// 实现了柯里化的函数// 接收参数,返回新函数,把参数传给新函数使用,最后求值let add = function(x){ return function(y){ return x + y; }};add(3)(4); // 7通用的柯里化函数感觉currying就是返回函数的函数,在此函数闭包中定义了私有域变量。function curry(fn) { let slice = Array.prototype.slice, // 将slice缓存起来 args = slice.call(arguments, 1); // 这里将arguments转成数组并保存 return function() { // 将新旧的参数拼接起来 let newArgs = args.concat(slice.call(arguments)); return fn.apply(null, newArgs); // 返回执行的fn并传递最新的参数 }}if (typeof Function.prototype.bind === “undefined”){ Function.prototype.bind = function (thisArgs){ var fn = this, slice = Array.prototype.slice, args = slice.call(arguments, 1); return function (){ let newArgs = args.concat(slice.call(arguments)) return fn.apply(thisArgs, newArgs); } }}ES6版的柯里化函数function curry(fn, …allArgs) { const g = (…allArgs) => allArgs.length >= fn.length ? fn(…allArgs) : (…args) => g(…allArgs, …args) return g;}// 测试用例const foo = curry((a, b, c, d) => { console.log(a, b, c, d);});foo(1)(2)(3)(4); // 1 2 3 4const f = foo(1)(2)(3);f(5); // 1 2 3 5function trueCurrying(fn, …args) { if (args.length >= fn.length) { return fn(…args) } return function (…args2) { return trueCurrying(fn, …args, …args2) }}// 比较多次接受的参数总数与函数定义时的入参数量,//当接受参数的数量大于或等于被 Currying 函数的传入参数数量时,//就返回计算结果,否则返回一个继续接受参数的函数。//注意这点和上边的区别题目:需要写一个函数,满足curry(fn)(1)(2)(3) //6var fn = function(a,b,c) { return a+b+c;}function curry(fn) { var arr = [], mySlice = arr.slice fnLen = fn.length; function curring() { arr = arr.concat(mySlice.call(arguments)); if(arr.length < fnLen) { return curring; } return fn.apply(this, arr); } return curring;}curry(fn)(1)(2)(3);//6本小题来自:几个让我印象深刻的面试题(一)2. 提前返回var addEvent = function(el, type, fn, capture) { if (window.addEventListener) { el.addEventListener(type, function(e) { fn.call(el, e); }, capture); } else if (window.attachEvent) { el.attachEvent(“on” + type, function(e) { fn.call(el, e); }); } };上面的方法有什么问题呢?很显然,我们每次使用addEvent为元素添加事件的时候,(eg. IE6/IE7)都会走一遍if…else if …,其实只要一次判定就可以了,怎么做?–柯里化。改为下面这样子的代码:var addEvent = (function(){ if (window.addEventListener) { return function(el, sType, fn, capture) { el.addEventListener(sType, function(e) { fn.call(el, e); }, (capture)); }; } else if (window.attachEvent) { return function(el, sType, fn, capture) { el.attachEvent(“on” + sType, function(e) { fn.call(el, e); }); }; }})();初始addEvent的执行其实值实现了部分的应用(只有一次的if…else if…判定),而剩余的参数应用都是其返回函数实现的,典型的柯里化。对比:惰性加载let addEvent = function(ele, type, fn) { if (window.addEventListener) { addEvent = function(ele, type, fn) { ele.addEventListener(type, fn, false); } } else if (window.attachEvent) { addEvent = function(ele, type, fn) { ele.attachEvent(‘on’ + type, function() { fn.call(ele) }); } } addEvent(ele, type, fn);3. 延迟计算/运行ES5中的bind方法if (!Function.prototype.bind) { Function.prototype.bind = function(context) { var self = this, args = Array.prototype.slice.call(arguments); return function() { return self.apply(context, args.slice(1)); } };}推荐阅读:从一道面试题认识函数柯里化参考文章:ES6版的柯里化函数、JS中的柯里化(currying)5.手写一个 bind 方法带一个参数:Function.prototype.bind = function(context) { let self = this, slice = Array.prototype.slice, args = slice.call(arguments); return function() { return self.apply(context, args.slice(1)); }};带多个参数://ES3实现if(!Function.prototype.bind){ Function.prototype.bind = function(o, args){ var self = this, boundArgs = arguments;//注:arguments是指sum.bind(null,1)中的参数null和1 return function(){ //此时返回的只是一个函数 var args = [], i; for(var i=1; i< boundArgs.length; i++){ args.push(boundArgs[i]); } for(var i =0; i< arguments.length; i++){ args.push(arguments[i]);//注:这里的arguments是指result(2)中的参数2 } return self.apply(o, args); } }}或者// 代码来自书籍 《javaScript 模式》if (typeof Function.prototype.bind === “undefined”){ Function.prototype.bind = function (thisArgs){ var fn = this, slice = Array.prototype.slice, args = slice.call(arguments, 1); return function (){ return fn.apply(thisArgs, args.concat(slice.call(arguments))); } }}//注:前后arguments不是一回事哦~//调用var sum = function(x,y){ return x+y };var result = sum.bind(null,1);result(2); // 3或者Function.prototype.bind = function(){ var fn = this; var args = Array.prototye.slice.call(arguments); var context = args.shift(); return function(){ return fn.apply(context, args.concat(Array.prototype.slice.call(arguments))); };本节参考文章:js中的bind其他文章:JavaScirpt 的 bind 函数究竟做了哪些事6.经典面试问题:new 的过程首先来看一下,函数声明的过程// 实际代码function fn1() {}// JavaScript 自动执行fn1.protptype = { constructor: fn1, proto: Object.prototype}fn1.proto = Function.prototypevar a = new myFunction(“Li”,“Cherry”);//伪代码new myFunction{ var obj = {}; obj.proto = myFunction.prototype; var result = myFunction.call(obj,“Li”,“Cherry”); return typeof result === ‘object’? result : obj;}创建一个空对象 obj;将新创建的空对象的隐式原型指向其构造函数的显示原型。使用 call 改变 this 的指向如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。所以我们可以看到,在 new 的过程中,我们是使用 call 改变了 this 的指向。7.javascript里面的继承怎么实现,如何避免原型链上面的对象共享什么是原型链当一个引用类型继承另一个引用类型的属性和方法时候就会产生一个原型连。ES5:寄生组合式继承:通过借用构造函数来继承属性和原型链来实现子继承父。 function ParentClass(name) { this.name = name; } ParentClass.prototype.sayHello = function () { console.log(“I’m parent!” + this.name); } function SubClass(name, age) { //若是要多个参数可以用apply 结合 …解构 ParentClass.call(this, name); this.age = age; } SubClass.prototype.sayChildHello = function (name) { console.log(“I’m child " + this.name) } SubClass.prototype = Object.create(ParentClass.prototype); SubClass.prototype.constructor = SubClass; let testA = new SubClass(‘CRPER’) // Object.create()的polyfill /* function pureObject(obj){ //定义了一个临时构造函数 function F() {} //将这个临时构造函数的原型指向了传入进来的对象。 F.prototype = obj; //返回这个构造函数的一个实例。该实例拥有obj的所有属性和方法。 //因为该实例的原型是obj对象。 return new F(); } / 或 function subClass() { superClass.apply(this, arguments); this.abc = 1; } function inherits(subClass, superClass) { function Inner() {} Inner.prototype = superClass.prototype; subClass.prototype = new Inner(); subClass.prototype.constructor = subClass; } inherits(subClass, superClass); subClass.prototype.getTest = function() { console.log(“hello”) };ES6: 其实就是ES5的语法糖,不过可读性很强.. class ParentClass { constructor(name) { this.name = name; } sayHello() { console.log(“I’m parent!” + this.name); } } class SubClass extends ParentClass { constructor(name) { super(name); } sayChildHello() { console.log(“I’m child " + this.name) } // 重新声明父类同名方法会覆写,ES5的话就是直接操作自己的原型链上 sayHello(){ console.log(“override parent method !,I’m sayHello Method”) } } let testA = new SubClass(‘CRPER’)8.继承 JS 内置对象(Date)写在前面,本节只记录了如何继承Date对象…的解决方案,具体问题和解析过程请看原文ES5// 需要考虑polyfill情况Object.setPrototypeOf = Object.setPrototypeOf ||function(obj, proto) { obj.proto = proto; return obj;};/* * 用了点技巧的继承,实际上返回的是Date对象 /function MyDate() { // bind属于Function.prototype,接收的参数是:object, param1, params2… var dateInst = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))(); // 更改原型指向,否则无法调用MyDate原型上的方法 // ES6方案中,这里就是[[prototype]]这个隐式原型对象,在没有标准以前就是__proto__ Object.setPrototypeOf(dateInst, MyDate.prototype); dateInst.abc = 1; return dateInst;}// 原型重新指回Date,否则根本无法算是继承Object.setPrototypeOf(MyDate.prototype, Date.prototype);MyDate.prototype.getTest = function getTest() { return this.getTime();};let date = new MyDate();// 正常输出,譬如1515638988725console.log(date.getTest());ES6class MyDate extends Date { constructor() { super(); this.abc = 1; } getTest() { return this.getTime(); }}let date = new MyDate();// 正常输出,譬如1515638988725console.log(date.getTest());注意:这里的正常输出环境是直接用ES6运行,不经过babel打包,打包后实质上是转化成ES5的,所以效果完全不一样,会报错的9.简易双向数据绑定<body> <input type=“text” id=“foo”> <p id=“test”></p> <script> var user = {} Object.defineProperty(user, ‘inputValue’, { configurable: true, get: function() { return document.getElementById(‘foo’).value }, set: function(value) { document.getElementById(‘foo’).value = value document.getElementById(’test’).innerHTML = value } }) document.getElementById(‘foo’).addEventListener(‘keyup’, function() { document.getElementById(’test’).innerHTML = user.inputValue }) </script></body>10.JavaScript实现发布-订阅模式发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。JavaScript开发中我们一般用事件模型来代替传统的发布-订阅模式示例1 function Dep() {//发布者 this.subs = []; } Dep.prototype.addSub = function (sub) { this.subs.push(sub); } Dep.prototype.notify = function () { this.subs.forEach(sub=>sub.update()); } function Watcher(fn) {//订阅者 this.fn = fn; } Watcher.prototype.update = function () { this.fn(); } var dep = new Dep(); dep.addSub(new Watcher(function () { console.log(‘okokok’); })) dep.notify();推荐阅读:Javascript设计模式之发布-订阅模式示例2function Event(){ this.list={}, this.on=function(key,cb){//订阅事件 if(!this.list[key]){ this.list[key] = [] } this.list[key].push(cb) }, this.emit = function(){//触发事件 var key = Array.prototype.shift.call(arguments) var e = this.list[key] if(!e){ return } var args = Array.prototype.slice.call(arguments) for(var i = 0;i<e.length;i++){ e[i].apply(null,args) } }}尝试一下:var a = new Event()a.on(‘a’,function(x){console.log(x)})a.emit(‘a’,1)//1推荐阅读:从单向到双向数据绑定示例3var myBus = (function() { var clienlist = {}, addlisten, trigger, remove; /* * 增加订阅者 * @key {String} 类型 * @fn {Function} 回掉函数 * / addlisten = function(key, fn) { if(!clienlist[key]) { clienlist[key] = []; } clienlist[key].push(fn); }; /* * 发布消息 * / trigger = function() { var key = [].shift.call(arguments), //取出消息类型 fns = clienlist[key]; //取出该类型的对应的消息集合 if(!fns || fns.length === 0) { return false; } for(var i = 0, fn; fn = fns[i++];) { fn.apply(this, arguments); } }; /* * 删除订阅 * @key {String} 类型 * @fn {Function} 回掉函数 * / remove = function(key, fn) { var fns = clienlist[key]; //取出该类型的对应的消息集合 if(!fns) { //如果对应的key没有订阅直接返回 return false; } if(!fn) { //如果没有传入具体的回掉,则表示需要取消所有订阅 fns && (fns.length = 0); } else { for(var l = fns.length - 1; l >= 0; l–) { //遍历回掉函数列表 if(fn === fns[l]) { fns.splice(l, 1); //删除订阅者的回掉 } } } }; return { $on: addlisten, $emit: trigger, $off: remove }})();推荐阅读:写一个简单vue 中间件,$emit、$on示例4这个示例更像示例2、示例3的总结,我也放这里吧,多看几种写法也多少开阔一下思路或全当复习卖烧饼的店主可以把小明、小龙的电话记录下来,等店里有烧饼了在通知小龙小明来拿这就是所谓的发布-订阅模式,代码如下:/烧饼店/ var Sesamecakeshop={ clienlist:[],//缓存列表 addlisten:function(fn){//增加订阅者 this.clienlist.push(fn); }, trigger:function(){//发布消息 for(var i=0,fn;fn=this.clienlist[i++];){ fn.apply(this,arguments); } }}/小明发布订阅/Sesamecakeshop.addlisten(function(price,taste){ console.log(“小明发布的”+price+“元,"+taste+“味道的”);});/小龙发布订阅/Sesamecakeshop.addlisten(function(price,taste){ console.log(“小龙发布的”+price+“元,"+taste+“味道的”);}); Sesamecakeshop.trigger(10,“椒盐”);从代码中可以看出,只有小明,小龙预定了烧饼,烧饼店就可以发布消息告诉小龙与小明。但是有个问题不知道大家发现了没有。小明只喜欢椒盐味道的。而小龙只喜欢焦糖味道的。上面的代码就满足不了客户的需求,给客户一种感觉就是,不管我喜欢不喜欢,你都会发给我。如果发布比较多,客户就会感到厌烦,甚至会想删除订阅。下边是对代码进行改良大家可以看看。/烧饼店/ var Sesamecakeshop={ clienlist:{},/缓存列表/ /* * 增加订阅者 * @key {String} 类型 * @fn {Function} 回掉函数 * / addlisten:function(key,fn){ if(!this.clienlist[key]){ this.clienlist[key]=[]; } this.clienlist[key].push(fn); }, /* * 发布消息 * / trigger:function(){ var key=[].shift.call(arguments),//取出消息类型 fns=this.clienlist[key];//取出该类型的对应的消息集合 if(!fns || fns.length===0){ return false; } for(var i=0,fn;fn=fns[i++];){ fn.apply(this,arguments); } }, /* * 删除订阅 * @key {String} 类型 * @fn {Function} 回掉函数 * */ remove:function(key,fn){ var fns=this.clienlist[key];//取出该类型的对应的消息集合 if(!fns){//如果对应的key没有订阅直接返回 return false; } if(!fn){//如果没有传入具体的回掉,则表示需要取消所有订阅 fns && (fns.length=0); }else{ for(var l=fns.length-1;l>=0;l–){//遍历回掉函数列表 if(fn===fns[l]){ fns.splice(l,1);//删除订阅者的回掉 } } } }}/小明发布订阅/Sesamecakeshop.addlisten(“焦糖”,fn1=function(price,taste){ console.log(“小明发布的”+price+“元,"+taste+“味道的”);});/小龙发布订阅/Sesamecakeshop.addlisten(“椒盐”,function(price,taste){ console.log(“小龙发布的”+price+“元,"+taste+“味道的”);}); Sesamecakeshop.trigger(“椒盐”,10,“椒盐”);Sesamecakeshop.remove(“焦糖”,fn1);//注意这里是按照地址引用的。如果传入匿名函数则删除不了 Sesamecakeshop.trigger(“焦糖”,40,“焦糖”);推荐必读:发布-订阅模式11.扁平化后的数组如:[1, [2, [ [3, 4], 5], 6]] => [1, 2, 3, 4, 5, 6] var data = [1, [2, [ [3, 4], 5], 6]]; function flat(data, result) { var i, d, len; for (i = 0, len = data.length; i < len; ++i) { d = data[i]; if (typeof d === ’number’) { result.push(d); } else { flat(d, result); } } } var result = []; flat(data, result); console.log(result);12.冒泡排序解析:比较相邻的两个元素,如果前一个比后一个大,则交换位置。第一轮的时候最后一个元素应该是最大的一个。按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。js代码实现function bubble_sort(arr){ for(var i = 0;i < arr.length - 1; i++){ for(var j = 0;j < arr.length - i - 1;j++){ if(arr[j] > arr[j+1]){ [arr[j], arr[j+1]] = [arr[j + 1], arr[j]] } } }}var arr = [3,1,5,7,2,4,9,6,10,8];bubble_sort(arr);console.log(arr);13.快速排序快速排序是对冒泡排序的一种改进解析:第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。然后递归调用,在两边都实行快速排序。js代码实现function quick_sort(arr){ if(arr.length <= 1){ return arr; } var pivotIndex = Math.floor(arr.length / 2); var pivot = arr.splice(pivotIndex, 1)[0]; var left = []; var right = []; for (var i = 0;i < arr.length; i++) { if(arr[i] < pivot){ left.push(arr[i]); } else { right.push(arr[i]); } } return quick_sort(left).concat([pivot],quick_sort(right));}var arr=[5,6,2,1,3,8,7,1,2,3,4,7];console.log(quick_sort(arr));14.选择排序// 选择排序:大概思路是找到最小的放在第一位,找到第二小的放在第二位,以此类推 算法复杂度O(n^2)选择demo:function selectionSort(arr) { let len = arr.length; let minIndex; for (let i = 0; i < len - 1; i++) { minIndex = i; for (let j = i + 1; j < len; j++) { if (arr[j] < arr[minIndex]) { //寻找最小的数 minIndex = j; //将最小数的索引保存 } } [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; }return arr;}本节参考文章:2018前端面试总结…15.插入排序解析:从第一个元素开始,该元素可以认为已经被排序取出下一个元素,在已经排序的元素序列中从后向前扫描如果该元素(已排序)大于新元素,将该元素移到下一位置重复步骤3,直到找到已排序的元素小于或者等于新元素的位置将新元素插入到下一位置中重复步骤2js代码实现function insert_sort(arr){ var i=1, j,key,len=arr.length; for(;i<len;i++){ var j=i; var key=arr[j]; while(–j>-1){ if(arr[j]>key){ arr[j+1]=arr[j]; }else{ break; } } arr[j+1]=key; } return arr;}或function insert_sort(arr) { let len = arr.length; let preIndex, current; for (let i = 1; i < len; i++) { preIndex = i - 1; current = arr[i]; while (preIndex >= 0 && arr[preIndex] > current) { arr[preIndex + 1] = arr[preIndex]; preIndex–; } arr[preIndex + 1] = current; } return arr;}insert_sort([2,34,54,2,5,1,7]); ...

January 16, 2019 · 8 min · jiezi

【LeetCode】贪心算法--买卖股票的最佳时机 II(122)

一、写在前面为什么要在LeetCode刷题?大家都知道不管是校招还是社招算法题是必考题,而这一部分恰巧是大多数人的短板,所以刷题首先是为了提高自身的编程能力,能够在算法面试中脱颖而出,拿到满意的offer。自己是打算考研的,计算机考研数据结构也是必考题,所以刷题的第二个原因就是为了巩固自己的数据结构知识。应该如何刷题呢?这两个月自己是顺序刷题的,但是总结的时候发现知识点太零散,前二十题有栈,链表,数组等等,自己总结的时候没有形成一个完整的体系,也没有清晰的分类,这不是自己想要的,所以自己后期刷题将采用专题的方式,比如数组,链表,二叉树等等。那么第一个专题就是贪心算法。前20题链接【LeetCode】汇总贴(NO.1-20)自己建了一个LeetCode刷题群,交流自己的刷题心得,现在还没有到达预定的人数,感兴趣的小伙伴可以参加哦,个人微信:wxb950917。为面试而生,期待你的加入。二、什么是贪心算法贪心算法在LeetCode共有41个题目,以中等难度居多。那么什么是贪心算法呢?贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。贪心算法每一步必须满足一下条件: 1、可行的:即它必须满足问题的约束。 2、局部最优:他是当前步骤中所有可行选择中最佳的局部选择。 3、不可取消:即选择一旦做出,在算法的后面步骤就不可改变了。学习贪心算法的时候可以结合动态规划一起来学习,两者还是很相似的。三、今日题目买卖股票的最佳时机 II(122)给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。示例 1:输入: [7,1,5,3,6,4]输出: 7解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。示例 2:输入: [1,2,3,4,5]输出: 4解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。示例 3:输入: [7,6,4,3,1]输出: 0解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。四、题目分析贪心算法,总是做出在当前看来是最好的选择,不从整体最优上加以考虑,也就是说,只关心当前最优解,按照贪心策略,不关心以后,我们只关心当前利益。第0天买入,花费prices[0],第一天卖出,得到prices[1],那么我们的收获就是profit = prices[1] - prices[0],那么有两种情况(1)当profit > 0 时,赶紧买入卖出,能赚一笔是一笔。(2)当profit <= 0 时,再买入卖出的话,那就是傻了,亏钱。以此方式类推下去,即得最大利润。五、代码实现class Solution:def maxProfit(self, prices): """ :type prices: List[int] :rtype: int """ profit = 0 temp=0 for i in range(1,len(prices)): temp=prices[i] - prices[i-1] if temp>0: profit+=temp return profit【推荐阅读】【Python爬虫】初识爬虫(1)用Python来一场人工造雪机器学习实战–住房月租金预测(1)Python之禅 ...

January 16, 2019 · 1 min · jiezi

【剑指offer】6.用两个栈实现队列

题目用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。基本思路栈1:用于入队列存储栈2:出队列时将栈1的数据依次出栈,并入栈到栈2中栈2出栈即栈1的底部数据即队列要出的数据。注意:栈2为空才能补充栈1的数据,否则会打乱当前的顺序。代码const stack1 = [];const stack2 = [];function push(node){ stack1.push(node);}function pop(){ if(stack2.length === 0){ while(stack1.length>0){ stack2.push(stack1.pop()); } } return stack2.pop() || null;}

January 16, 2019 · 1 min · jiezi

【LeetCode】贪心算法--划分字母区间(763)

写在前面今天这篇文章是贪心算法系列的第三篇–划分字母区间。前文回顾:【LeetCode】贪心算法–分发糖果(135)刷题汇总:【LeetCode】汇总贴(NO.1-20)今日题目字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一个字母只会出现在其中的一个片段。返回一个表示每个字符串片段的长度的列表。示例 1:输入: S = “ababcbacadefegdehijhklij"输出: [9,7,8]解释:划分结果为 “ababcbaca”, “defegde”, “hijhklij”。每个字母最多出现在一个片段中。像 “ababcbacadefegde”, “hijhklij” 的划分是错误的,因为划分的片段数较少。注意:S的长度在[1, 500]之间。S只包含小写字母’a’到’z’。题目分析解决此题关键就是找到能分割的条件,对S的每个字符进行判断,看是否此字符是被分割到另一个字符中,从题目中得到每个字母最多出现在一个片段中,那么从第一个字符开始,它的最后一个相同的字符一定在这个片段中,得到第一个条件:当此字符在前面分割中出现,就不能当做分割点只有这个条件就可以了吗?我们再考虑一下,当前字符并没有在前面分割的区间中出现,是不是能直接作为分割点呢?以下面的字符串为例进行分割。“aaaaab cdaefgh”当判断b的时候,先在前面已经分好的字符串aaaaa里面没有,符合找到的第一个条件,如果我们把b当做新的分割点,很明显是错误的,因为在b后面的字符串里,又一次出现了a,当我们以b作为分割点是不符合条件的,因此得到第二个限制条件:分割点后面不能出现前面一个字符串中的字符。 进行了上面的分析但是可以用python做个弊,使用rindex()方法,从第一个字符开始,假设位置为a,用rindex方法找到最后一次出现的位置b,那么这个区间就为[a,b]。之后每个字符都找最后一个位置,如果在区间之外则扩大区间,如果遍历到区间的最后一个位置,则结束,长度就为结束位置减开始位置加1。代码实现class Solution:def partitionLabels(self, S): "”" :type S: str :rtype: List[int] """ i = 0 res = [] while i < len(S): start = i end = S.rindex(S[i]) for j in range(i,len(S)): last = S.rindex(S[j]) if last > end: end = last elif j == end: res.append(end-start + 1) i = end + 1 break return res推荐阅读:python异常报错详解机器学习实战–住房月租金预测(3)机器学习实战–住房月租金预测(2) ...

January 15, 2019 · 1 min · jiezi

长期维护更新,前端面试题整理

网上找到的各种面试题整理,长期更新。大部分答案整理来自网络,有问题的地方,希望大家能指出,及时修改;技术更新迭代,也会及时更新博客原地址:https://finget.github.io/2019…前端前端性能优化1.清理 HTML 文档HTML,即超文本标记语言,几乎是所有网站的支柱。HTML 为网页带来标题、子标题、列表和其它一些文档结构的格式。在最近更新的 HTML5 中,甚至可以创建图表。HTML 很容易被网络爬虫识别,因此搜索引擎可以根据网站的内容在一定程度上实时更新。在写 HTML 的时候,你应该尝试让它简洁而有效。此外,在 HTML 文档中引用外部资源的时候也需要遵循一些最佳实践方法。a.恰当放置 CSSWeb 设计者喜欢在网页建立起主要的 HTML 骨架之后再来创建样式表。这样一来,网页中的样式表往往会放在 HTML 的后面,接近文档结束的地方。然而推荐的做法是把 CSS 放在 HTML 的上面部分,文档头之内,这可以确保正常的渲染过程。这个策略不能提高网站的加载速度,但它不会让访问者长时间看着空白屏幕或者无格式的文本(FOUT)等待。如果网页大部分可见元素已经加载出来了,访问者才更有可能等待加载整个页面,从而带来对前端的优化效果。这就是知觉性能b.正确放置 Javascript另一方面,如果将 JavaScript 放置在 head 标签内或 HTML 文档的上部,这会阻塞 HTML 和 CSS 元素的加载过程。这个错误会导致页面加载时间增长,增加用户等待时间,容易让人感到不耐烦而放弃对网站的访问。不过,您可以通过将 JavaScript 属性置于 HTML 底部来避免此问题。此外,在使用 JavaScript 时,人们通常喜欢用异步脚本加载。这会阻止<script>标签在 HTML 中的呈现过程,如,在文档中间的情况。虽然对于网页设计师来说, HTML 是最值得使用的工具之一,但它通常要与 CSS 和 JavaScript 一起使用,这可能会导致网页浏览速度减慢。 虽然 CSS 和 JavaScript 有利于网页优化,但使用时也要注意一些问题。使用 CSS 和 JavaScript 时,要避免嵌入代码。因为当您嵌入代码时,要将 CSS 放置在样式标记中,并在脚本标记中使用 JavaScript,这会增加每次刷新网页时必须加载的 HTML 代码量。2.优化 CSS 性能CSS,即级联样式表,能从 HTML 描述的内容生成专业而又整洁的文件。很多 CSS 需要通过 HTTP 请求来引入(除非使用内联 CSS),所以你要努力去除累赘的 CSS 文件,但要注意保留其重要特征。如果你的 Banner、插件和布局样式是使用 CSS 保存在不同的文件内,那么,访问者的浏览器每次访问都会加载很多文件。虽然现在 HTTP/2 的存在,减少了这种问题的发生,但是在外部资源加载的情况下,仍会花费较长时间。要了解如何减少 HTTP 请求以大幅度缩减加载时间,请阅读WordPress 性能。此外,不少网站管理员在网页中错误的使用 @import 指令 来引入外部样式表。这是一个过时的方法,它会阻止浏览并行下载。link 标签才是最好的选择,它也能提高网站的前端性能。多说一句,通过 link 标签请求加载的外部样式表不会阻止并行下载。3.减少外部HTTP请求在很多情况下,网站的大部分加载时间来自于外部的 Http 请求。外部资源的加载速度随着主机提供商的服务器架构、地点等不同而不同。减少外部请求要做的第一步就是简略地检查网站。研究你网站的每个组成部分,消除任何影响访问者体验不好的成分。这些成分可能是:不必要的图片没用的 JavaScript 代码过多的 css多余的插件在你去掉这些多余的成分之后,再对剩下的内容进行整理,如,压缩工具、CDN 服务和预获取(prefetching)等,这些都是管理 HTTP 请求的最佳选择。除此之外,减少DNS路由查找教程会教你如何一步一步的减少外部 HTTP 请求。4.压缩 CSS, JS 和 HTML压缩技术可以从文件中去掉多余的字符。你在编辑器中写代码的时候,会使用缩进和注释,这些方法无疑会让你的代码简洁而且易读,但它们也会在文档中添加多余的字节。使用预先获取预先获取可以在真正需要之前通过取得必需的资源和相关数据来改善访问用户的浏览体验,主要有3类预先获取:链接预先获取DNS 预先获取预先渲染在你离开当前 web 页面之前,使用预先获取方式,对应每个链接的 URL 地址,CSS,图片和脚本都会被预先获取。这保证了访问者能在最短时间内使用链接在画面间切换。幸运的是,预先获取很容易实现。根据你想要使用的预先获取形式,你只需在网站 HTML 中的链接属性上增加 rel=”prefetch”,rel=”dns-prefetch”,或者 rel=”prerender” 标记。6.使用 CDN 和缓存提高速度内容分发网络能显著提高网站的速度和性能。使用 CDN 时,您可以将网站的静态内容链接到全球各地的服务器扩展网络。如果您的网站观众遍布全球,这项功能十分有用。 CDN 允许您的网站访问者从最近的服务器加载数据。如果您使用 CDN,您网站内的文件将自动压缩,以便在全球范围内快速分发。CDN 是一种缓存方法,可极大改善资源的分发时间,同时,它还能实现一些其他的缓存技术,如,利用浏览器缓存。合理地设置浏览器缓存,能让浏览器自动存储某些文件,以便加快传输速度。此方法的配置可以直接在源服务器的配置文件中完成。7.压缩文件虽然许多 CDN 服务可以压缩文件,但如果不使用 CDN,您也可以考虑在源服务器上使用文件压缩方法来改进前端优化。 文件压缩能使网站的内容轻量化,更易于管理。 最常用的文件压缩方法之一是 Gzip。 这是缩小文档、音频文件、PNG图像和等其他大文件的绝佳方法。Brotli 是一个比较新的文件压缩算法,目前正变得越来越受欢迎。 此开放源代码算法由来自 Google 和其他组织的软件工程师定期更新,现已被证明比其他现有压缩方法更好用。 这种算法的支持目前还比较少,但作为后起之秀指日可待。8.使用轻量级框架除非你只用现有的编码知识构建网站,不然,你可以尝试使用一个好的前端框架来避免许多不必要的前端优化错误。虽然有一些更大,更知名的框架能提供更多功能和选项,但它们不一定适合你的 Web 项目。所以说,不仅确定项目所需功能很重要,选择合适的框架也很重要——它要在提供所需功能的同时保持轻量。最近许多框架都使用简洁的 HTML,CSS 和 JavaScript 代码。一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?参考链接:详细解读https://segmentfault.com/a/1190000006879700详细解读https://mp.weixin.qq.com/s/jjL4iA7p6aYEAQyWhn4QbQ输入地址1.浏览器查找域名的 IP 地址2.这一步包括 DNS 具体的查找过程,包括:浏览器缓存->系统缓存->路由器缓存…3.浏览器向 web 服务器发送一个 HTTP 请求4.服务器的永久重定向响应(从 http://example.com 到 http://www.example.com)5.浏览器跟踪重定向地址6.服务器处理请求7.服务器返回一个 HTTP 响应8.浏览器显示 HTML9.浏览器发送请求获取嵌入在 HTML 中的资源(如图片、音频、视频、CSS、JS等等)10.浏览器发送异步请求URL 到底是啥URL(Uniform Resource Locator),统一资源定位符,用于定位互联网上资源,俗称网址。比如 http://www.w3school.com.cn/ht…,遵守以下的语法规则:scheme://host.domain:port/path/filename各部分解释如下:scheme - 定义因特网服务的类型。常见的协议有 http、https、ftp、file,其中最常见的类型是 http,而 https 则是进行加密的网络传输。host - 定义域主机(http 的默认主机是 www)domain - 定义因特网域名,比如 w3school.com.cnport - 定义主机上的端口号(http 的默认端口号是 80)path - 定义服务器上的路径(如果省略,则文档必须位于网站的根目录中)。filename - 定义文档/资源的名称讲tcp/ip网络层、三次握手,为什么不能两次握手客服端和服务端在进行http请求和返回的工程中,需要创建一个TCP connection(由客户端发起),http不存在连接这个概念,它只有请求和响应。请求和响应都是数据包,它们之间的传输通道就是TCP connection。位码即tcp标志位,有6种标示:SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)Sequence number(顺序号码) Acknowledge number(确认号码)第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;(第一次握手,由浏览器发起,告诉服务器我要发送请求了)第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包;(第二次握手,由服务器发起,告诉浏览器我准备接受了,你赶紧发送吧)第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功;(第三次握手,由浏览器发送,告诉服务器,我马上就发了,准备接受吧)谢希仁著《计算机网络》中讲“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。这种情况是:一端(client)A发出去的第一个连接请求报文并没有丢失,而是因为某些未知的原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间才到达另一端(server)B。本来这是一个早已失效的报文段,但是B收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉很多资源。如果采用“三次握手”的话就不会出现这种情况,B端收到一个过时失效的报文段之后,向A端发出确认,此时A并没有要求建立连接,所以就不会向B端发送确认,这个时候B端也能够知道连接没有建立。问题的本质是,信道是不可靠的,但是我们要建立可靠的连接发送可靠的数据,也就是数据传输是需要可靠的。在这个时候三次握手是一个理论上的最小值,并不是说是tcp协议要求的,而是为了满足在不可靠的信道上传输可靠的数据所要求的。这个网上转载的例子不错:三次握手:A:“喂,你听得到吗?”A->SYN_SENDB:“我听得到呀,你听得到我吗?”应答与请求同时发出 B->SYN_RCVD | A->ESTABLISHEDA:“我能听到你,今天balabala……”B->ESTABLISHED四次挥手:A:“喂,我不说了。”A->FIN_WAIT1B:“我知道了。等下,上一句还没说完。Balabala…..”B->CLOSE_WAIT | A->FIN_WAIT2B:”好了,说完了,我也不说了。”B->LAST_ACKA:”我知道了。”A->TIME_WAIT | B->CLOSEDA等待2MSL,保证B收到了消息,否则重说一次”我知道了”,A->CLOSEiframe有那些缺点?iframe会阻塞主页面的Onload事件;搜索引擎的检索程序无法解读这种页面,不利于SEO;iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。使用iframe之前需要考虑这两个缺点。如果需要使用iframe,最好是通过javascript动态给iframe添加src属性值,这样可以绕开以上两个问题websocket握手过程在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为“握手” (handshaking)。客户端请求web socket连接时,会向服务器端发送握手请求请求头大致内容:请求包说明:必须是有效的http request 格式;HTTP request method 必须是GET,协议应不小于1.1 如: Get / HTTP/1.1;必须包括Upgrade头域,并且其值为”websocket”;必须包括”Connection” 头域,并且其值为”Upgrade”;必须包括”Sec-WebSocket-Key”头域,其值采用base64编码的随机16字节长的字符序列;如果请求来自浏览器客户端,还必须包括Origin头域 。 该头域用于防止未授权的跨域脚本攻击,服务器可以从Origin决定是否接受该WebSocket连接;必须包括”Sec-webSocket-Version” 头域,当前值必须是13;可能包括”Sec-WebSocket-Protocol”,表示client(应用程序)支持的协议列表,server选择一个或者没有可接受的协议响应之;可能包括”Sec-WebSocket-Extensions”, 协议扩展, 某类协议可能支持多个扩展,通过它可以实现协议增强;可能包括任意其他域,如cookie.服务端响应如下:应答包说明: *必须包括Upgrade头域,并且其值为”websocket”; 必须包括Connection头域,并且其值为”Upgrade”; 必须包括Sec-WebSocket-Accept头域,其值是将请求包“Sec-WebSocket-Key”的值,与”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″这个字符串进行拼接,然后对拼接后的字符串进行sha-1运算,再进行base64编码,就是“Sec-WebSocket-Accept”的值; 应答包中冒号后面有一个空格; 最后需要两个空行作为应答包结束参考链接:Websocket协议之握手连接跨域以及解决办法同源符合”协议+域名+端口”三者相同,就是同源同源策略同源策略,其初衷是为了浏览器的安全性,通过以下三种限制,保证浏览器不易受到XSS、CSFR等攻击。- Cookie、LocalStorage 和 IndexDB 无法读取- DOM 和 Js对象无法获得- AJAX 请求不能发送跨域解决方案通过jsonp跨域document.domain + iframe跨域location.hash + iframewindow.name + iframe跨域postMessage跨域跨域资源共享(CORS)nginx代理跨域nodejs中间件代理跨域WebSocket协议跨域前端持久化的方式、区别最容易想到的解决方案是:1.使用前端cookie技术来保存本地化数据,如jquery.cookie.js;2.使用html5提供的Web Storage技术来提供解决方案;用cookie存储永久数据存在以下几个问题:1.大小:cookie的大小被限制在4KB。2.带宽:cookie是随HTTP事务一起被发送的,因此会浪费一部分发送cookie时使用的带宽。3.复杂性:要正确的操纵cookie是很困难的。针对这些问题,在HTML5中,重新提供了一种在客户端本地保存数据的功能,它就是Web Storage。具体来说,Web Storage又分为两种:1.sessionStorage:将数据保存在session对象中。所谓session,是指用户在浏览某个网站时,从进入网站到浏览器关闭所经过的这段时间,也就是用户浏览这个网站所花费的时间。session对象可以用来保存在这段时间内所要求保存的任何数据。2.localStorage:将数据保存在客户端本地的硬件设备(通常指硬盘,也可以是其他硬件设备)中,即使浏览器被关闭了,该数据仍然存在,下次打开浏览器访问网站时仍然可以继续使用。这两者的区别在于,sessionStorage为临时保存,而localStorage为永久保存。前端持久化–evercookie介绍http2.0所有数据以二进制传输。HTTP1.x是基于文本的,无法保证健壮性,HTTP2.0绝对使用新的二进制格式,方便且健壮同一个连接里面发送多个请求不再需要按照顺序来头信息压缩以及推送等提高效率的功能Http 2.0协议简介HTTP 2.0 详细介绍,http2.0详细介绍HTTP/2.0 相比1.0有哪些重大改进通过什么做到并发请求我能想到的只有Promise.all(),欢迎补充b和strong的区别<b> 粗体文本,<strong> 用于强调文本,他们的样式是一样的有一种说法,是<strong>貌似在盲人用的机器上会读两遍。因为没有对应的测试条件,所以没做验证。Access-Control-Allow-Origin在服务端哪里配置header(‘Access-Control-Allow-Origin:’); csrf跨站攻击怎么解决CSRF,全称为Cross-Site Request Forgery,跨站请求伪造,是一种网络攻击方式,它可以在用户毫不知情的情况下,以用户的名义伪造请求发送给被攻击站点,从而在未授权的情况下进行权限保护内的操作。具体来讲,可以这样理解CSRF。攻击者借用用户的名义,向某一服务器发送恶意请求,对服务器来讲,这一请求是完全合法的,但攻击者确完成了一个恶意操作,比如以用户的名义发送邮件,盗取账号,购买商品等等一般网站防御CSRF攻击的方案:(1)验证token值。(2)验证HTTP头的Referer。(3)在HTTP头中自定义属性并验证(4)服务器端表单hash认证在所有的表单里面随机生成一个hash,server在表单处理时去验证这个hash值是否正确,这样工作量比较大CSRF(跨站请求伪造攻击)漏洞详解CSS清除浮动的方式// 第一种.ovh{ overflow:hidden;}// 第二种.clear{ clear:both;}// 第三种.clearfix:after{ content:"";//设置内容为空 height:0;//高度为0 line-height:0;//行高为0 display:block;//将文本转为块级元素 visibility:hidden;//将元素隐藏 clear:both//清除浮动}.clearfix{ zoom:1;为了兼容IE}免费公开课带你彻底掌握 CSS 浮动当给父元素设置"overflow:hidden"时,实际上创建了一个超级属性BFC,此超级属性反过来决定了"height:auto"是如何计算的。在“BFC布局规则”中提到:计算BFC的高度时,浮动元素也参与计算。因此,父元素在计算其高度时,加入了浮动元素的高度,“顺便”达成了清除浮动的目标,所以父元素就包裹住了子元素。BFC是什么BFC(Block Formatting Context),块级格式化上下文,是Web页面中盒模型布局的CSS渲染模式。它的定位体系属于常规文档流。浮动,绝对定位元素,inline-blocks, table-cells, table-captions,和overflow的值不为visible的元素,(除了这个值已经被传到了视口的时候)将创建一个新的块级格式化上下文。上面的引述几乎总结了一个BFC是怎样形成的。但是让我们以另一种方式来重新定义以便能更好的去理解。一个BFC是一个HTML盒子并且至少满足下列条件中的任何一个:float的值不为noneposition的值不为static或者relativedisplay的值为 table-cell, table-caption, inline-block, flex, 或者 inline-flex中的其中一个overflow的值不为visible参考链接:理解CSS中BFC讲flex,手写出flex常用的属性,并且讲出作用这个直接看 阮一峰:Flex 布局教程介绍css3中position:sticky单词sticky的中文意思是“粘性的”,position:sticky表现也符合这个粘性的表现。基本上,可以看出是position:relative和position:fixed的结合体——当元素在屏幕内,表现为relative,就要滚出显示器屏幕的时候,表现为fixed。详细讲解的还是看大神的吧,张鑫旭:position:stickyJavaScriptjs三座大山原型与原型链,作用域及闭包,异步和单线程。三座大山,真不是一两句可以说清楚的,只有靠大家多看,多用,多理解,放点链接吧。原型,原型链,call/applyJavaScript从初级往高级走系列————prototypeJavaScript从初级往高级走系列————异步JavaScript的预编译过程内存空间详解作用域和闭包JavaScript深入之词法作用域和动态作用域JavaScript深入之作用域链事件循环机制什么是闭包参考链接:什么是闭包?https://mp.weixin.qq.com/s/OthfFRwf-rQmVbMnXAqnCg作用域与闭包https://yangbo5207.github.io/wutongluo/ji-chu-jin-jie-xi-lie/si-3001-zuo-yong-yu-lian-yu-bi-bao.html简言之,闭包是由函数引用其周边状态(词法环境)绑在一起形成的(封装)组合结构。在 JavaScript 中,闭包在每个函数被创建时形成。这是基本原理,但为什么我们关心这些?实际上,由于闭包与它的词法环境绑在一起,因此闭包让我们能够从一个函数内部访问其外部函数的作用域。要使用闭包,只需要简单地将一个函数定义在另一个函数内部,并将它暴露出来。要暴露一个函数,可以将它返回或者传给其他函数。内部函数将能够访问到外部函数作用域中的变量,即使外部函数已经执行完毕。在 JavaScript 中,闭包是用来实现数据私有的原生机制。当你使用闭包来实现数据私有时,被封装的变量只能在闭包容器函数作用域中使用。你无法绕过对象被授权的方法在外部访问这些数据。在 JavaScript 中,任何定义在闭包作用域下的公开方法才可以访问这些数据。宏任务 与 微任务参考链接:js引擎执行机制https://segmentfault.com/a/1190000012806637事件循环机制一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。micro-task大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。// setTimeout中的回调函数才是进入任务队列的任务setTimeout(function() { console.log(‘xxxx’);})// 非常多的同学对于setTimeout的理解存在偏差。所以大概说一下误解:// setTimeout作为一个任务分发器,这个函数会立即执行,而它所要分发的任务,也就是它的第一个参数,才是延迟执行来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。promise里面和then里面执行有什么区别promise里面的是宏任务,then后面的是微任务。JS为什么要区分微任务和宏任务这个问题本质就是为啥需要异步。如果js不是异步的话,由于js代码本身是自上而下执行的,那么如果上一行代码需要执行很久,下面的代码就会被阻塞,对用户来说,就是”卡死”,这样的话,会造成很差的用户体验。JavaScript 实现异步编程的4种方法你可能知道,Javascript语言的执行环境是"单线程"(single thread)。所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。回调函数假定有两个函数f1和f2,后者等待前者的执行结果。如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数function f1(callback){ setTimeout(function () { // f1的任务代码 callback(); }, 1000);}回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。事件监听另一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。f1.on(‘done’, f2);上面这行代码的意思是,当f1发生done事件,就执行f2。然后,对f1进行改写:function f1(){ setTimeout(function () { // f1的任务代码 f1.trigger(‘done’); }, 1000);}发布订阅我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。jQuery.subscribe(“done”, f2);function f1(){ setTimeout(function () { // f1的任务代码 jQuery.publish(“done”); }, 1000);}Promisef1().then(f2).then(f3);new 的过程新生成了一个对象链接到原型绑定 this返回新对象function create() { // 创建一个空的对象 let obj = new Object() // 获得构造函数 let Con = [].shift.call(arguments) // 链接到原型 obj.proto = Con.prototype // 绑定 this,执行构造函数 let result = Con.apply(obj, arguments) // 确保 new 出来的是个对象 return typeof result === ‘object’ ? result : obj}原型继承与类继承JS原型继承和类式继承http://www.cnblogs.com/constantince/p/4754992.html// 类继承var father = function() { this.age = 52; this.say = function() { alert(‘hello i am ‘+ this.name ’ and i am ‘+this.age + ‘years old’); }} var child = function() { this.name = ‘bill’; father.call(this);} var man = new child();man.say();// 原型继承var father = function() {}father.prototype.a = function() {}var child = function(){}//开始继承child.prototype = new father();var man = new child();man.a();和原型对比起来,构造函数(类)式继承有什么不一样呢?首先,构造函数继承的方法都会存在父对象之中,每一次实例,都会将funciton保存在内存中,这样的做法毫无以为会带来性能上的问题。其次类式继承是不可变的。在运行时,无法修改或者添加新的方法,这种方式是一种固步自封的死方法。而原型继承是可以通过改变原型链接而对子类进行修改的。另外就是类式继承不支持多重继承,而对于原型继承来说,你只需要写好extend对对象进行扩展即可。== 和 ===的区别,什么情况下用相等====是===类型转换(又称强制),==只需要值相等就会返回true,而===必须值和数据类型都相同才会返回true。bind、call、apply的区别1.每个函数都包含两个非继承而来的方法:call()方法和apply()方法。2.相同点:这两个方法的作用是一样的。都是在特定的作用域中调用函数,等于设置函数体内this对象的值,以扩充函数赖以运行的作用域。一般来说,this总是指向调用某个方法的对象,但是使用call()和apply()方法时,就会改变this的指向。3.不同点:接收参数的方式不同。apply()方法 接收两个参数,一个是函数运行的作用域(this),另一个是参数数组。语法:apply([thisObj [,argArray] ]);,调用一个对象的一个方法,2另一个对象替换当前对象。说明:如果argArray不是一个有效数组或不是arguments对象,那么将导致一个TypeError,如果没有提供argArray和thisObj任何一个参数,那么Global对象将用作thisObj。call()方法 第一个参数和apply()方法的一样,但是传递给函数的参数必须列举出来。语法:call([thisObject[,arg1 [,arg2 [,…,argn]]]]);,应用某一对象的一个方法,用另一个对象替换当前对象。说明: call方法可以用来代替另一个对象调用一个方法,call方法可以将一个函数的对象上下文从初始的上下文改变为thisObj指定的新对象,如果没有提供thisObj参数,那么Global对象被用于thisObj。bind和call、apply最大的区别就是,call、apply不仅改变this的指向,还会直接支持代码,而bind不会。var cat = { name: ‘咪咪’}function beatTheMonster(){ console.log(this.name);}beatTheMonster.call(cat);// 1.call 改变了this的指向。改变到了cat上。// 2.beatTheMonster函数/方法执行了// 3.bind(),保存了方法,并没有直接调用它图片预览<input type=“file” name=“file” onchange=“showPreview(this)” /><img id=“portrait” src="" width=“70” height=“75”>function showPreview(source) { var file = source.files[0]; if(window.FileReader) { var fr = new FileReader(); fr.onloadend = function(e) { document.getElementById(“portrait”).src = e.target.result; }; fr.readAsDataURL(file); }}扁平化多维数组var result = []function unfold(arr){ for(var i=0;i< arr.length;i++){ if(typeof arr[i]==“object” && arr[i].length>1) { unfold(arr[i]); } else { result.push(arr[i]); } }}var arr = [1,3,4,5,[6,[0,1,5],9],[2,5,[1,5]],[5]];unfold(arr)var c=[1,3,4,5,[6,[0,1,5],9],[2,5,[1,5]],[5]];var b = c.toString().split(’,’)var arr=[1,3,4,5,[6,[0,1,5],9],[2,5,[1,5]],[5]];const flatten = arr => arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);var result = flatten(arr)this的指向问题参考链接:归纳总结this的指向问题https://finget.github.io/2018/11/28/this/ECMAScript规范解读thishttps://github.com/mqyqingfeng/Blog/issues/7function foo() { console.log(this.a)}var a = 1foo()var obj = { a: 2, foo: foo}obj.foo()// 以上两者情况 this 只依赖于调用函数前的对象,优先级是第二个情况大于第一个情况// 以下情况是优先级最高的,this 只会绑定在 c 上,不会被任何方式修改 this 指向var c = new foo()c.a = 3console.log(c.a)// 还有种就是利用 call,apply,bind 改变 this,这个优先级仅次于 new箭头函数中的this:function a() { return () => { return () => { console.log(this) } }}console.log(a()()())箭头函数其实是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,因为调用 a 符合前面代码中的第一个情况,所以 this 是 window。并且 this 一旦绑定了上下文,就不会被任何代码改变。async/await理解 JavaScript 的 async/awaithttps://segmentfault.com/a/1190000007535316async function async1() { console.log( ‘async1 start’) await async2() console.log( ‘async1 end’)}async function async2() { console.log( ‘async2’)}async1()console.log( ‘script start’)这里注意一点,可能大家都知道await会让出线程,阻塞后面的代码,那么上面例子中, async2 和 script start 谁先打印呢?是从左向右执行,一旦碰到await直接跳出,阻塞 async2() 的执行?还是从右向左,先执行async2后,发现有await关键字,于是让出线程,阻塞代码呢?实践的结论是,从右向左的。先打印async2,后打印的 script start。之所以提一嘴,是因为我经常看到这样的说法,「一旦遇到await就立刻让出线程,阻塞后面的代码」。Promise 和 async/await 和 callback的区别我的理解:callback是解决异步的早期方案,但是会导致‘回调地狱’,然后就出现了Promise,利用.then优化了回调地狱的问题,而async/await是在promise 进一步封装,利用看似同步的方式解决异步问题。Promise和async/await都是语法糖。就是写起来更简单,阅读性和维护性增强。Promise 和 async/await在执行时都干了什么,推荐看看:8 张图帮你一步步看清 async/await 和 promise 的执行顺序手写实现promise直接粘贴大神的代码:// 三种状态const PENDING = “pending”;const RESOLVED = “resolved”;const REJECTED = “rejected”;// promise 接收一个函数参数,该函数会立即执行function MyPromise(fn) { let _this = this; _this.currentState = PENDING; _this.value = undefined; // 用于保存 then 中的回调,只有当 promise // 状态为 pending 时才会缓存,并且每个实例至多缓存一个 _this.resolvedCallbacks = []; _this.rejectedCallbacks = []; _this.resolve = function (value) { if (value instanceof MyPromise) { // 如果 value 是个 Promise,递归执行 return value.then(_this.resolve, _this.reject) } setTimeout(() => { // 异步执行,保证执行顺序 if (_this.currentState === PENDING) { _this.currentState = RESOLVED; _this.value = value; _this.resolvedCallbacks.forEach(cb => cb()); } }) }; _this.reject = function (reason) { setTimeout(() => { // 异步执行,保证执行顺序 if (_this.currentState === PENDING) { _this.currentState = REJECTED; _this.value = reason; _this.rejectedCallbacks.forEach(cb => cb()); } }) } // 用于解决以下问题 // new Promise(() => throw Error(’error)) try { fn(_this.resolve, _this.reject); } catch (e) { _this.reject(e); }}MyPromise.prototype.then = function (onResolved, onRejected) { var self = this; // 规范 2.2.7,then 必须返回一个新的 promise var promise2; // 规范 2.2.onResolved 和 onRejected 都为可选参数 // 如果类型不是函数需要忽略,同时也实现了透传 // Promise.resolve(4).then().then((value) => console.log(value)) onResolved = typeof onResolved === ‘function’ ? onResolved : v => v; onRejected = typeof onRejected === ‘function’ ? onRejected : r => throw r; if (self.currentState === RESOLVED) { return (promise2 = new MyPromise(function (resolve, reject) { // 规范 2.2.4,保证 onFulfilled,onRjected 异步执行 // 所以用了 setTimeout 包裹下 setTimeout(function () { try { var x = onResolved(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }); })); } if (self.currentState === REJECTED) { return (promise2 = new MyPromise(function (resolve, reject) { setTimeout(function () { // 异步执行onRejected try { var x = onRejected(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }); })); } if (self.currentState === PENDING) { return (promise2 = new MyPromise(function (resolve, reject) { self.resolvedCallbacks.push(function () { // 考虑到可能会有报错,所以使用 try/catch 包裹 try { var x = onResolved(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (r) { reject(r); } }); self.rejectedCallbacks.push(function () { try { var x = onRejected(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (r) { reject(r); } }); })); }};// 规范 2.3function resolutionProcedure(promise2, x, resolve, reject) { // 规范 2.3.1,x 不能和 promise2 相同,避免循环引用 if (promise2 === x) { return reject(new TypeError(“Error”)); } // 规范 2.3.2 // 如果 x 为 Promise,状态为 pending 需要继续等待否则执行 if (x instanceof MyPromise) { if (x.currentState === PENDING) { x.then(function (value) { // 再次调用该函数是为了确认 x resolve 的 // 参数是什么类型,如果是基本类型就再次 resolve // 把值传给下个 then resolutionProcedure(promise2, value, resolve, reject); }, reject); } else { x.then(resolve, reject); } return; } // 规范 2.3.3.3.3 // reject 或者 resolve 其中一个执行过得话,忽略其他的 let called = false; // 规范 2.3.3,判断 x 是否为对象或者函数 if (x !== null && (typeof x === “object” || typeof x === “function”)) { // 规范 2.3.3.2,如果不能取出 then,就 reject try { // 规范 2.3.3.1 let then = x.then; // 如果 then 是函数,调用 x.then if (typeof then === “function”) { // 规范 2.3.3.3 then.call( x, y => { if (called) return; called = true; // 规范 2.3.3.3.1 resolutionProcedure(promise2, y, resolve, reject); }, e => { if (called) return; called = true; reject(e); } ); } else { // 规范 2.3.3.4 resolve(x); } } catch (e) { if (called) return; called = true; reject(e); } } else { // 规范 2.3.4,x 为基本类型 resolve(x); }}Promise.all实现原理Promise.all = arr => { let aResult = []; //用于存放每次执行后返回结果 return new _Promise(function (resolve, reject) { let i = 0; next(); //开始逐次执行数组中的函数 function next() { arr[i].then(function (res) { aResult.push(res); //执行后返回的结果放入数组中 i++; if (i == arr.length) { //如果函数数组中的函数都执行完,便把结果数组传给then resolve(aResult); } else { next(); } }) } }) };手写函数防抖和函数节流你是否在日常开发中遇到一个问题,在滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作。这些需求都可以通过函数防抖动来实现。尤其是第一个需求,如果在频繁的事件回调中做复杂计算,很有可能导致页面卡顿,不如将多次计算合并为一次计算,只在一个精确点做操作。PS:防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的 情况会每隔一定时间(参数wait)调用函数。我们先来看一个袖珍版的防抖理解一下防抖的实现:// func是用户传入需要防抖的函数// wait是等待时间const debounce = (func, wait = 50) => { // 缓存一个定时器id let timer = 0 // 这里返回的函数是每次用户实际调用的防抖函数 // 如果已经设定过定时器了就清空上一次的定时器 // 开始一个新的定时器,延迟执行用户传入的方法 return function(…args) { if (timer) clearTimeout(timer) timer = setTimeout(() => { func.apply(this, args) }, wait) }}// 不难看出如果用户调用该函数的间隔小于wait的情况下,上一次的时间还未到就被清除了,并不会执行函数这是一个简单版的防抖,但是有缺陷,这个防抖只能在最后调用。一般的防抖会有immediate选项,表示是否立即调用。这两者的区别,举个栗子来说:例如在搜索引擎搜索问题的时候,我们当然是希望用户输入完最后一个字才调用查询接口,这个时候适用延迟执行的防抖函数,它总是在一连串(间隔小于wait的)函数触发之后调用。例如用户给interviewMap点star的时候,我们希望用户点第一下的时候就去调用接口,并且成功之后改变star按钮的样子,用户就可以立马得到反馈是否star成功了,这个情况适用立即执行的防抖函数,它总是在第一次调用,并且下一次调用必须与前一次调用的时间间隔大于wait才会触发。// 这个是用来获取当前时间戳的function now() { return +new Date()}/ * 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行 * * @param {function} func 回调函数 * @param {number} wait 表示时间窗口的间隔 * @param {boolean} immediate 设置为ture时,是否立即调用函数 * @return {function} 返回客户调用函数 /function debounce (func, wait = 50, immediate = true) { let timer, context, args // 延迟执行函数 const later = () => setTimeout(() => { // 延迟函数执行完毕,清空缓存的定时器序号 timer = null // 延迟执行的情况下,函数会在延迟函数中执行 // 使用到之前缓存的参数和上下文 if (!immediate) { func.apply(context, args) context = args = null } }, wait) // 这里返回的函数是每次实际调用的函数 return function(…params) { // 如果没有创建延迟执行函数(later),就创建一个 if (!timer) { timer = later() // 如果是立即执行,调用函数 // 否则缓存参数和调用上下文 if (immediate) { func.apply(this, params) } else { context = this args = params } // 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个 // 这样做延迟函数会重新计时 } else { clearTimeout(timer) timer = later() } }}节流:/ * underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait * * @param {function} func 回调函数 * @param {number} wait 表示时间窗口的间隔 * @param {object} options 如果想忽略开始函数的的调用,传入{leading: false}。 * 如果想忽略结尾函数的调用,传入{trailing: false} * 两者不能共存,否则函数不能执行 * @return {function} 返回客户调用函数 */_.throttle = function(func, wait, options) { var context, args, result; var timeout = null; // 之前的时间戳 var previous = 0; // 如果 options 没传则设为空对象 if (!options) options = {}; // 定时器回调函数 var later = function() { // 如果设置了 leading,就将 previous 设为 0 // 用于下面函数的第一个 if 判断 previous = options.leading === false ? 0 : _.now(); // 置空一是为了防止内存泄漏,二是为了下面的定时器判断 timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { // 获得当前时间戳 var now = _.now(); // 首次进入前者肯定为 true // 如果需要第一次不执行函数 // 就将上次时间戳设为当前的 // 这样在接下来计算 remaining 的值时会大于0 if (!previous && options.leading === false) previous = now; // 计算剩余时间 var remaining = wait - (now - previous); context = this; args = arguments; // 如果当前调用已经大于上次调用时间 + wait // 或者用户手动调了时间 // 如果设置了 trailing,只会进入这个条件 // 如果没有设置 leading,那么第一次会进入这个条件 // 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了 // 其实还是会进入的,因为定时器的延时 // 并不是准确的时间,很可能你设置了2秒 // 但是他需要2.2秒才触发,这时候就会进入这个条件 if (remaining <= 0 || remaining > wait) { // 如果存在定时器就清理掉否则会调用二次回调 if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 判断是否设置了定时器和 trailing // 没有的话就开启一个定时器 // 并且不能不能同时设置 leading 和 trailing timeout = setTimeout(later, remaining); } return result; }; };图片懒加载与预加载懒加载也就是延迟加载原理:页面中的img元素,如果没有src属性,浏览器就不会发出请求去下载图片,只有通过javascript设置了图片路径,浏览器才会发送请求。懒加载的原理就是先在页面中把所有的图片统一使用一张占位图进行占位,把正真的路径存在元素的“data-url”(这个名字起个自己认识好记的就行)属性里,要用的时候就取出来,再设置// 懒加载function loadImg(src){ let promise = new Promise(function (resolve, reject) { let img = document.createElement(‘img’) img.onload = function () { resolve(img) } img.onerror = function () { reject(‘图片加载失败’) } img.src = src }) return promise}预加载 提前加载图片,当用户需要查看时可直接从本地缓存中渲染实现预加载的三种方法:用CSS和JavaScript实现预加载仅使用JavaScript实现预加载使用Ajax实现预加载用CSS和JavaScript实现预加载#preload-01 { background: url(http://domain.tld/image-01.png) no-repeat -9999px -9999px; } #preload-02 { background: url(http://domain.tld/image-02.png) no-repeat -9999px -9999px; } #preload-03 { background: url(http://domain.tld/image-03.png) no-repeat -9999px -9999px; }将这三个ID选择器应用到(X)HTML元素中,我们便可通过CSS的background属性将图片预加载到屏幕外的背景上。只要这些图片的路径保持不变,当它们在Web页面的其他地方被调用时,浏览器就会在渲染过程中使用预加载(缓存)的图片。简单、高效,不需要任何JavaScript。该方法虽然高效,但仍有改进余地。使用该法加载的图片会同页面的其他内容一起加载,增加了页面的整体加载时间。为了解决这个问题,我们增加了一些JavaScript代码,来推迟预加载的时间,直到页面加载完毕。代码如下:function preloader() { if (document.getElementById) { document.getElementById(“preload-01”).style.background = “url(http://domain.tld/image-01.png) no-repeat -9999px -9999px”; document.getElementById(“preload-02”).style.background = “url(http://domain.tld/image-02.png) no-repeat -9999px -9999px”; document.getElementById(“preload-03”).style.background = “url(http://domain.tld/image-03.png) no-repeat -9999px -9999px”; } } function addLoadEvent(func) { var oldonload = window.onload; if (typeof window.onload != ‘function’) { window.onload = func; } else { window.onload = function() { if (oldonload) { oldonload(); } func(); } } } addLoadEvent(preloader);仅使用JavaScript实现预加载var images = new Array() function preload() { for (i = 0; i < preload.arguments.length; i++) { images[i] = new Image() images[i].src = preload.arguments[i] } } preload( “http://domain.tld/gallery/image-001.jpg", “http://domain.tld/gallery/image-002.jpg", “http://domain.tld/gallery/image-003.jpg" ) 使用Ajax实现预加载window.onload = function() { setTimeout(function() { // XHR to request a JS and a CSS var xhr = new XMLHttpRequest(); xhr.open(‘GET’, ‘http://domain.tld/preload.js'); xhr.send(’’); xhr = new XMLHttpRequest(); xhr.open(‘GET’, ‘http://domain.tld/preload.css'); xhr.send(’’); // preload image new Image().src = “http://domain.tld/preload.png"; }, 1000); };上面代码预加载了“preload.js”、“preload.css”和“preload.png”。1000毫秒的超时是为了防止脚本挂起,而导致正常页面出现功能问题。window.onload = function() { setTimeout(function() { // reference to <head> var head = document.getElementsByTagName(‘head’)[0]; // a new CSS var css = document.createElement(’link’); css.type = “text/css”; css.rel = “stylesheet”; css.href = “http://domain.tld/preload.css"; // a new JS var js = document.createElement(“script”); js.type = “text/javascript”; js.src = “http://domain.tld/preload.js"; // preload JS and CSS head.appendChild(css); head.appendChild(js); // preload image new Image().src = “http://domain.tld/preload.png"; }, 1000); };这里,我们通过DOM创建三个元素来实现三个文件的预加载。正如上面提到的那样,使用Ajax,加载文件不会应用到加载页面上。从这点上看,Ajax方法优越于JavaScript。参考链接:Javascript图片预加载详解使用es5实现es6的class借用babel工具可以学习一下,es6的class 编译成es5时,长什么样// ES6class Person{ constructor(name,age){ this.name = name this.age = age } say() { console.log(this.name) } run() { console.log(‘run fast’) } // 静态方法,类调用 static getGirl(){ console.log(‘girl friend’) }}// ES5var _createClass = function() { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; // 枚举 descriptor.enumerable = descriptor.enumerable || false; // 可配置 descriptor.configurable = true; if (“value” in descriptor) // 可写 descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function(Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();// 禁止 直接调用 Person()function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); }}var Person = function () { function Person(name, age) { _classCallCheck(this, Person); this.name = name; this.age = age; } _createClass(Person, [{ key: ‘say’, value: function say() { console.log(this.name); } }, { key: ‘run’, value: function run() { console.log(‘run fast’); } }], [{ key: ‘getGirl’, value: function getGirl() { console.log(‘girl friend’); } }]); return Person;}();关于对象的enumerable、writable、configurable,可以看看Javascript properties are enumerable, writable and configurableJavaScript的sort方法内部使用的什么排序默认排序顺序是根据字符串Unicode码点函数式编程函数式编程的本质,函数式编程中的函数这个术语不是指计算机中的函数,而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,无论什么时候调用,调用几次,值都是不变的。函数式的最主要的好处是不可变性带来的。没有可变的状态,函数就是引用透明的没有副作用。函数即不依赖外部的状态也不修改外部的状态,函数调用的结果不依赖调用的时间和位置,这样写的代码容易进行推理,不容易出错。这使得单元测试和调试更容易。参考链接:js函数式编程指南回调函数的坏处回调地狱、代码的可阅读性和可维护性降低如何实现一个可设置过期时间的localStorage直接上链接:如何给localStorage设置一个过期时间?用JavaScript的异步实现sleep函数async function test() { console.log(‘Hello’) let res = await sleep(1000) console.log(res)}function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms))}test()参考链接:JavaScript的sleep实现–Javascript异步编程学习手写实现jsonpwindow._pt_lt = new Date().getTime();window._pt_sp_2 = [];_pt_sp_2.push(‘setAccount,2953009d’);var _protocol = ((“https:” == document.location.protocol) ? " https://” : " http://”);(function() { var atag = document.createElement(‘script’); atag.type = ’text/javascript’; atag.async = true; atag.src = _protocol + ‘js.ptengine.cn/2953009d.js’; var s = document.getElementsByTagName(‘script’)[0]; s.parentNode.insertBefore(atag, s);})();浅拷贝和深拷贝的区别浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。参考链接:js浅拷贝和深拷贝for..in 和 for..of 的区别推荐在循环对象属性的时候,使用for…in,在遍历数组的时候的时候使用for…of。for…in…遍历对象会遍历出对象的所有可枚举的属性for…in循环出的是key,for…of循环出的是value注意,for…of是ES6新引入的特性。修复了ES5引入的for…in的不足for…of不能循环普通的对象,需要通过和Object.keys()搭配使用cookie和localStorage的区别特性 cookie sessionStorage localStorage 数据生命期 生成时就会被指定一个maxAge值,这就是cookie的生存周期,在这个周期内cookie有效,默认关闭浏览器失效 页面会话期间可用 除非数据被清除,否则一直存在 存放数据大小 4K左右(因为每次http请求都会携带cookie) 一般5M或更大 与服务器通信 由对服务器的请求来传递,每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题 数据不是由每个服务器请求传递的,而是只有在请求时使用数据,不参与和服务器的通信 易用性 cookie需要自己封装setCookie,getCookie 可以用源生接口,也可再次封装来对Object和Array有更好的支持 共同点 都是保存在浏览器端,和服务器端的session机制不同 JS执行过程中分为哪些阶段数组里面有10万个数据,取第一个元素和第10万个元素的时间相差多少时间一样。引用类型的变量都是堆内存。堆内存就像书架一样,只要你知道书名,就能直接找到对应的书。内存空间var a = {b: 1} 存放在哪里?var a = {b: {c: 1}}存放在哪里?var a = {name: “前端开发”}; var b = a; a = null, 那么b输出什么?js变量可以用来保存两种类型的值:基本类型值和引用类型值。在ES6之前共有6种数据类型:Undefined、Null、Boolean、Number,String和Object,其中前5种是基本类型值。基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中。从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本。引用类型的值是对象,保存在堆内存中。包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针理解队列数据结构的目的主要是为了清晰的明白事件循环(Event Loop)的机制到底是怎么回事。VueVue 生命周期1.beforcreate2.created3.beformount4.mounted5.beforeUpdate6.updated7.actived8.deatived9.beforeDestroy10.destroyedvue里面的虚拟dom是怎么回事,虚拟DOM主要做了什么JavaScript从初级往高级走系列————Virtual Domvue双向绑定讲一讲Vue3基于Proxy 的新数据监听系统,全语音特性支持 + 更好的性能Vue2.x用的是基于ES5的getter/setter,也就是Object.defineProperty这个API。每个vue 组件都会代理它所包含的 data、props、computed,这些代理都是通过Object.defineProperty实现的,大量的Object.defineProperty是很大的性能消耗利用Proxy减少组件实例初始化开销,暴露给用户的这个this,其实是一个真正的组件实例的一个Proxy基于Proxy的监听是所谓的Lazy by default,只有当一个数据被用到的时候才会监听讲vue-lazyloader的原理,手写伪代码原理简述:vue-lazyload是通过指令的方式实现的,定义的指令是v-lazy指令指令被bind时会创建一个listener,并将其添加到listener queue里面, 并且搜索target dom节点,为其注册dom事件(如scroll事件)上面的dom事件回调中,会遍历 listener queue里的listener,判断此listener绑定的dom是否处于页面中perload的位置,如果处于则加载异步加载当前图片的资源同时listener会在当前图片加载的过程的loading,loaded,error三种状态触发当前dom渲染的函数,分别渲染三种状态下dom的内容参考链接:Vue-lazyload原理详解之源码解析讲vue的响应式原理、依赖收集、监听数组、虚拟domVue.js 技术揭秘手写vue双向绑定<!DOCTYPE html><html><head> <meta charset=“utf-8”> <title>双向绑定</title></head><body> 手写一个简单双向绑定<br/> <input type=“text” id=“model”><br/> <div id=“modelText”></div></body><script> var model = document.querySelector("#model”); var modelText = document.querySelector("#modelText”); var defaultName = “defaultName”; var userInfo = {} model.value = defaultName; Object.defineProperty(userInfo, “name”, { get: function () { return defaultName; }, set: function (value) { defaultName = value; model.value = value; console.log(”—–value”); console.log(value); modelText.textContent = value; } }) userInfo.name = “new value”; var isEnd = true; model.addEventListener(“keyup”, function () { if (isEnd) { userInfo.name = this.value; } }, false) //加入监听中文输入事件 model.addEventListener(“compositionstart”, function () { console.log(“开始输入中文”); isEnd = false; }) model.addEventListener(“compositionend”, function () { isEnd = true; console.log(“结束输入中文”); })</script></html>vue-router的原理参考链接:前端路由简介以及vue-router实现原理【源码拾遗】从vue-router看前端路由的两种实现浅谈vue-router原理router-link 与 a 标签的区别<router-link> 组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 <a> 标签,可以通过配置 tag 属性生成别的标签.。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。<router-link> 比起写死的 <a href=”…"> 会好一些,理由如下:无论是 HTML5 history 模式还是 hash 模式,它的表现行为一致,所以,当你要切换路由模式,或者在 IE9 降级使用 hash 模式,无须作任何变动。在 HTML5 history 模式下,router-link会守卫点击事件,让浏览器不再重新加载页面。当你在 HTML5 history 模式下使用 base 选项之后,所有的 to属性都不需要写 (基路径) 了。手写vue的mixin方法参考链接:react-router从Link组件和a标签的区别说起vue里面哪儿不会用到双向绑定对于非UI控件来说,不存在双向,只有单向。只有UI控件才有双向的问题。React生命周期实例化getDefaultPropsgetInitialStatecomponentWillMountrendercomponentDidMount存在期componentWillReceivePropsshouldComponentUpdatecomponentWillUpdatecomponentDidUpdate销毁时componentWillUnmountstate是怎么注入到组件的,从reducer到组件经历了什么样的过程调用 setState 之后发生了什么?在代码中调用setState函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发所谓的调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个UI界面。在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。React 中 Element 与 Component 的区别是?简单而言,React Element 是描述屏幕上所见内容的数据结构,是对于 UI 的对象表述。典型的 React Element 就是利用 JSX 构建的声明式代码片然后被转化为createElement的调用组合。而 React Component 则是可以接收参数输入并且返回某个React Element的函数或者类。更多介绍可以参考React Elements vs React Components。在什么情况下你会优先选择使用 Class Component 而不是 Functional Component?在组件需要包含内部状态或者使用到生命周期函数的时候使用 Class Component ,否则使用函数式组件。React 中 refs 的作用是什么?Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄。我们可以为元素添加ref属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回:class CustomForm extends Component { handleSubmit = () => { console.log(“Input Value: “, this.input.value) } render () { return ( <form onSubmit={this.handleSubmit}> <input type=‘text’ ref={(input) => this.input = input} /> <button type=‘submit’>Submit</button> </form> ) }}上述代码中的input域包含了一个ref属性,该属性声明的回调函数会接收input对应的 DOM 元素,我们将其绑定到this指针以便在其他的类函数中使用。另外值得一提的是,refs 并不是类组件的专属,函数式组件同样能够利用闭包暂存其值:function CustomForm ({handleSubmit}) { let inputElement return ( <form onSubmit={() => handleSubmit(inputElement.value)}> <input type=‘text’ ref={(input) => inputElement = input} /> <button type=‘submit’>Submit</button> </form> )}React 中 keys 的作用是什么?Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。render () { return ( <ul> {this.state.todoItems.map(({task, uid}) => { return <li key={uid}>{task}</li> })} </ul> )}在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中 Key 的重要性。Controlled Component 与 Uncontrolled Component 之间的区别是什么?React 的核心组成之一就是能够维持内部状态的自治组件,不过当我们引入原生的HTML表单元素时(input,select,textarea 等),我们是否应该将所有的数据托管到 React 组件中还是将其仍然保留在 DOM 元素中呢?这个问题的答案就是受控组件与非受控组件的定义分割。受控组件(Controlled Component)代指那些交由 React 控制并且所有的表单数据统一存放的组件。譬如下面这段代码中username变量值并没有存放到DOM元素中,而是存放在组件状态数据中。任何时候我们需要改变username变量值时,我们应当调用setState函数进行修改。class ControlledForm extends Component { state = { username: ’’ } updateUsername = (e) => { this.setState({ username: e.target.value, }) } handleSubmit = () => {} render () { return ( <form onSubmit={this.handleSubmit}> <input type=‘text’ value={this.state.username} onChange={this.updateUsername} /> <button type=‘submit’>Submit</button> </form> ) }}而非受控组件(Uncontrolled Component)则是由DOM存放表单数据,并非存放在 React 组件中。我们可以使用 refs 来操控DOM元素:class UnControlledForm extends Component { handleSubmit = () => { console.log(“Input Value: “, this.input.value) } render () { return ( <form onSubmit={this.handleSubmit}> <input type=‘text’ ref={(input) => this.input = input} /> <button type=‘submit’>Submit</button> </form> ) }}竟然非受控组件看上去更好实现,我们可以直接从 DOM 中抓取数据,而不需要添加额外的代码。不过实际开发中我们并不提倡使用非受控组件,因为实际情况下我们需要更多的考虑表单验证、选择性的开启或者关闭按钮点击、强制输入格式等功能支持,而此时我们将数据托管到 React 中有助于我们更好地以声明式的方式完成这些功能。引入 React 或者其他 MVVM 框架最初的原因就是为了将我们从繁重的直接操作 DOM 中解放出来。在生命周期中的哪一步你应该发起 AJAX 请求?我们应当将AJAX 请求放到 componentDidMount 函数中执行,主要原因有下:React 下一代调和算法 Fiber 会通过开始或停止渲染的方式优化应用性能,其会影响到 componentWillMount 的触发次数。对于 componentWillMount 这个生命周期函数的调用次数会变得不确定,React 可能会多次频繁调用 componentWillMount。如果我们将 AJAX 请求放到 componentWillMount 函数中,那么显而易见其会被触发多次,自然也就不是好的选择。如果我们将 AJAX 请求放置在生命周期的其他函数中,我们并不能保证请求仅在组件挂载完毕后才会要求响应。如果我们的数据请求在组件挂载之前就完成,并且调用了setState函数将数据添加到组件状态中,对于未挂载的组件则会报错。而在 componentDidMount 函数中进行 AJAX 请求则能有效避免这个问题。shouldComponentUpdate 的作用是啥以及为何它这么重要?shouldComponentUpdate允许我们手动地判断是否要进行组件更新,根据组件的应用场景设置函数的合理返回值能够帮我们避免不必要的更新。如何告诉 React 它应该编译生产环境版本?通常情况下我们会使用 Webpack 的 DefinePlugin 方法来将 NODE_ENV 变量值设置为 production。编译版本中 React 会忽略 propType 验证以及其他的告警信息,同时还会降低代码库的大小,React 使用了 Uglify 插件来移除生产环境下不必要的注释等信息。为什么我们需要使用 React 提供的 Children API 而不是 JavaScript 的 map?props.children并不一定是数组类型,譬如下面这个元素:<Parent> <h1>Welcome.</h1></Parent>概述下 React 中的事件处理逻辑为了解决跨浏览器兼容性问题,React 会将浏览器原生事件(Browser Native Event)封装为合成事件(SyntheticEvent)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外有意思的是,React 并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理。这样 React 在更新 DOM 的时候就不需要考虑如何去处理附着在 DOM 上的事件监听器,最终达到优化性能的目的。createElement 与 cloneElement 的区别是什么?createElement 函数是 JSX 编译之后使用的创建 React Element 的函数,而 cloneElement 则是用于复制某个元素并传入新的 Props。传入 setState 函数的第二个参数的作用是什么?该函数会在setState函数调用完成并且组件开始重渲染的时候被调用,我们可以用该函数来监听渲染是否完成:this.setState( { username: ’tylermcginnis33’ }, () => console.log(‘setState has finished and the component has re-rendered.’))setState为什么默认是异步,什么时候是同步的下述代码有错吗?this.setState((prevState, props) => { return { streak: prevState.streak + props.count }})这段代码没啥问题,不过只是不太常用罢了,详细可以参考React中setState同步更新策略React组件中怎么做事件代理区别于浏览器事件处理方式,React并未将事件处理函数与对应的DOM节点直接关联,而是在顶层使用了一个全局事件监听器监听所有的事件;React会在内部维护一个映射表记录事件与组件事件处理函数的对应关系;当某个事件触发时,React根据这个内部映射表将事件分派给指定的事件处理函数;当映射表中没有事件处理函数时,React不做任何操作;当一个组件安装或者卸载时,相应的事件处理函数会自动被添加到事件监听器的内部映射表中或从表中删除。参考链接:深入浅出React(五)(React组件事件详解)Nodejsexpress框架的设计思想参考链接:Express框架详解深入理解express框架express框架的简单实现浏览器的事件循环和nodejs事件循环的区别阮一峰:JavaScript 运行机制详解:再谈Event Loop其他怎么配webpack深入浅出 Webpacknpm2和npm3+有什么区别npm3 与 npm2 相比有什么改进?打包时Hash码是怎么生成的Webpack中hash与chunkhash的区别推荐链接前端工程师,揭开HTTP的神秘面纱前端工程师,必备知识深入浅出 Webpackjs函数式编程指南Vue.js技术揭秘从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理前端基础进阶八段代码彻底掌握Promise通俗大白话来理解TCP协议的三次握手和四次分手js深入底层系列最后创建了一个前端学习交流群,感兴趣的朋友,一起来嗨呀! ...

January 15, 2019 · 13 min · jiezi

【剑指offer】5.二叉树的镜像和打印

二叉树简介基本结构:function TreeNode(x) { this.val = x; this.left = null; this.right = null;}二叉树的前序、中序、后序遍历的定义:前序遍历:对任一子树,先访问跟,然后遍历其左子树,最后遍历其右子树;中序遍历:对任一子树,先遍历其左子树,然后访问根,最后遍历其右子树;后序遍历:对任一子树,先遍历其左子树,然后遍历其右子树,最后访问根。题目1 二叉树的镜像1.1 题目描述操作给定的二叉树,将其变换为源二叉树的镜像。输入描述:二叉树的镜像定义:源二叉树 8 / \ 6 10 / \ / \ 5 7 9 11 镜像二叉树 8 / \ 10 6 / \ / \ 11 9 7 51.2 解题思路递归交换二叉树两棵字树的位置。1.3 代码function Mirror(root){ if(root){ const temp = root.right; root.right = root.left; root.left = temp; Mirror(root.right); Mirror(root.left); }}题目2 从上往下打印二叉树2.1 题目描述从上往下打印出二叉树的每个节点,同层节点从左至右打印。2.2 解题思路1.借助队列先进先出的数据结构2.让二叉树每层依次进入队列3.依次打印队列中的值2.3 代码 function PrintFromTopToBottom(root) { const queue = []; const print = []; if(root != null){ queue.push(root); } while (queue.length > 0) { const current = queue.shift(); print.push(current.val); if (current.left) { queue.push(current.left); } if (current.right) { queue.push(current.right); } } return print; } ...

January 14, 2019 · 1 min · jiezi

前端基本功-常见概念(一)

前端基本功-常见概念(一) 点这里前端基本功-常见概念(二) 点这里前端基本功-常见概念(三) 点这里1.什么是原型链当一个引用类型继承另一个引用类型的属性和方法时候就会产生一个原型链。(js高级程序设计)所有 引用类型 都有一个 proto 属性的隐式原型所有 函数 都有一个 prototype属性的显式原型所有 引用类型的 隐式原型,指向其构造函数的 显式原型当试图得到一个属性或方法时,如果这个对象没有,那么会去他的__proto__(即它构造函数的prototype)中查找原型链 是针对构造函数的,比如我先创建了一个函数,然后通过一个变量new了这个函数,那么这个被new出来的函数就会继承创建出来的那个函数的属性,然后如果我访问new出来的这个函数的某个属性,但是我并没有在这个new出来的函数中定义这个变量,那么它就会往上(向创建出它的函数中)查找,这个查找的过程就叫做原型链。2.什么是闭包有权访问另一个函数作用域中的变量函数。(js高级程序设计)当一个函数存在对非自身作用域的变量的引用 就产生一个闭包有权访问另一个作用域的函数就是闭包闭包三点作用: 创建私有变量;延长变量生命周期;防止全局变量污染3.什么是作用域就是函数和变量的可访问范围。作用域 是针对变量的,特点是:先在自己的变量范围中查找,如果找不到,就会沿着作用域往上找。只有函数作用域和全局作用域(貌似还有个eval作用域),ES6中新增块级作用域那是后话函数外声明的变量为全局作用域,函数内可以使用函数内声明的变量为函数作用域,函数外不可以使用作用域链:一个自由变量一直往上寻找(定义时的)父级作用域内的变量的过程。自由变量:当前作用域没有定义的变量作用域什么时候生成的?页面加载–>创建window全局对象,并生成全局作用域–>然后生成执行上下文,预解析变量(变量提升),生成全局变量对象;$scope补充:花括号内声明的变量为块级作用域,只能内部使用,减少全局污染JavaScript是静态作用域,在对变量进行查询时,变量值由函数定义时的位置决定,和执行时的所处的作用域无关。4.什么是构造函数在 JavaScript 中,用 new 关键字来调用的函数,称为构造函数。5.什么是面向对象ECMAscript开发的两种模式:1.面向过程: 就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了2.面向对象(OOP): 面向对象是以功能来划分问题,而不是步骤面向对象的语言有一个标志,那就是类的概念,而通过类可以创建任意多个具有相同属性的方法的对象。但是ECMAscript中没有类的概念!prototype是javascript实现与管理继承的一种机制,也是面向对象的设计思想,可以借助prototype属性,可以访问原型内部的属性和方法。通常使用构造函数,当构造函数被实列化后,所有的实例对象都可以访问构造函数的原型(prototype)成员,如果在原型中声明一个成员,所有的实列方法都可以共享它面向对象编程的基本思想是使用对象,类,继承,封装等基本概念来进行程序设计,达到数据结构化,简单抽象的目的(为什么面向对象)优点易维护采用面向对象思想设计的结构,可读性高,由于继承的存在,即使改变需求,那么维护也只是在局部模块,所以维护起来是非常方便和较低成本的- 易扩展开发工作的重用性、继承性高,降低重复工作量。缩短了开发周期面向对象的三大特性:继承(子类继承父类,提高复用减少冗余),封装(数据的权限和保密),多态(同一接口的不同实现)面向对象离不开 类 和 实例 两个概念6.什么是响应式设计?响应式设计的基本原理是什么?响应式网站设计(Responsive Web design)是一个网站能够兼容多个终端,而不是为每一个终端做一个特定的版本。基本原理:是通过媒体查询检测不同的设备屏幕尺寸做处理。页面头部必须有meta声明的viewport。<meta name=’viewport’ content=”width=device-width, initial-scale=1. maximum-scale=1,user-scalable=no”>7.什么是高阶函数函数可以作为参数传递函数可以作为返回值输出8.什么是函数式编程?是指通过复合纯函数来构建软件的过程,它避免了共享的状态、易变的数据、以及副作用。函数式编程是声明式而不是命令式,并且应用程序状态通过纯函数流转。简单说,“函数式编程"是一种"编程范式”(programming paradigm),也就是如何编写程序的方法论它具有以下特性:闭包和高阶函数、惰性计算、递归、函数是"第一等公民"、只用"表达式"进一步了解9.css盒模型盒模型 : margin、padding、border、content标准盒模型 width = content 对应css属性 box-sizing:content-box怪异盒模型width = content + padding + border 对应css属性 box-sizing:border-box10.关于跨域关于跨域,有两个误区:✕ 动态请求就会有跨域的问题✔ 跨域只存在于浏览器端,不存在于安卓/ios/Node.js/python/ java等其它环境✕ 跨域就是请求发不出去了✔ 跨域请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。同源策略具体限制些什么呢?不能向工作在不同源的的服务请求数据(client to server)但是script标签能够加载非同源的资源,不受同源策略的影响。无法获取不同源的document/cookie等BOM和DOM,可以说任何有关另外一个源的信息都无法得到 (client to client)跨域最常用的方法,应当属CORS如下图所示:只要浏览器检测到响应头带上了CORS,并且允许的源包括了本网站,那么就不会拦截请求响应。CORS把请求分为两种,一种是简单请求,另一种是需要触发预检请求,这两者是相对的,怎样才算“不简单”?只要属于下面的其中一种就不是简单请求:(1)使用了除GET/POST/HEAD之外的请求方式,如PUT/DELETE(2)使用了除Content-Type/Accept等几个常用的http头这个时候就认为需要先发个预检请求,预检请求使用OPTIONS方式去检查当前请求是否安全代码里面只发了一个请求,但在控制台看到了两个请求,第一个是OPTIONS,服务端返回:详见阮一峰的跨域资源共享CORS详解第二种常用的跨域的方法是JSONPJSONP是利用了script标签能够跨域,如下代码所示:function updateList (data) { console.log(data);}$body.append(‘<script src=“http://otherdomain.com/request?callback=updateList"></script>’);代码先定义一个全局函数,然后把这个函数名通过callback参数添加到script标签的src,script的src就是需要跨域的请求,然后这个请求返回可执行的JS文本:// script响应返回的js内容为updateList([{ name: ‘hello’}]);由于它是一个js,并且已经定义了upldateList函数,所以能正常执行,并且跨域的数据通过传参得到。这就是JSONP的原理。小结跨域分为两种,一种是跨域请求,另一种访问跨域的页面,跨域请求可以通过CORS/JSONP等方法进行访问,跨域的页面主要通过postMesssage的方式。由于跨域请求不但能发出去还能带上cookie,所以要规避跨站请求伪造攻击的风险,特别是涉及到钱的那种请求。本节参考文章:我知道的跨域与安全11.http/httpsHTTP超文本传输协议(HTTP)是用于传输诸如HTML的超媒体文档的应用层协议。它被设计用于Web浏览器和Web服务器之间的通信,但它也可以用于其他目的。 HTTP遵循经典的客户端-服务端模型,客户端打开一个连接以发出请求,然后等待它收到服务器端响应。 HTTP是无状态协议,意味着服务器不会在两个请求之间保留任何数据(状态)。HTTP是明文传输的,也就意味着,介于发送端、接收端中间的任意节点都可以知道你们传输的内容是什么。这些节点可能是路由器、代理等。举个最常见的例子,用户登陆。用户输入账号,密码,采用HTTP的话,只要在代理服务器上做点手脚就可以拿到你的密码了。用户登陆 –> 代理服务器(做手脚)–> 实际授权服务器在发送端对密码进行加密?没用的,虽然别人不知道你原始密码是多少,但能够拿到加密后的账号密码,照样能登陆。HTTP是应用层协议,位于HTTP协议之下是传输协议TCP。TCP负责传输,HTTP则定义了数据如何进行包装。HTTPSHTTPS相对于HTTP有哪些不同呢?其实就是在HTTP跟TCP中间加多了一层加密层TLS/SSL。神马是TLS/SSL?通俗的讲,TLS、SSL其实是类似的东西,SSL是个加密套件,负责对HTTP的数据进行加密。TLS是SSL的升级版。现在提到HTTPS,加密套件基本指的是TLS。传输加密的流程原先是应用层将数据直接给到TCP进行传输,现在改成应用层将数据给到TLS/SSL,将数据加密后,再给到TCP进行传输。HTTPS是如何加密数据的对安全或密码学基础有了解的同学,应该知道常见的加密手段。一般来说,加密分为对称加密、非对称加密(也叫公开密钥加密)HTTPS与HTTP的一些区别HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费。HTTP协议运行在TCP之上,所有传输的内容都是明文,内容可能会被窃听。HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的。HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题。不验证通信方的身份,通信方的身份有可能遭遇伪装。无法证明报文的完整性,报文有可能遭篡改。使用SPDY加快你的网站速度谷歌推行一种协议(HTTP 之下SSL之上[TCP]),可以算是HTTP2的前身,SPDY可以说是综合了HTTPS和HTTP两者优点于一体的传输协议,比如压缩数据(HEADER)多路复用优先级(可以给请求设置优先级)SPDY构成图:SPDY位于HTTP之下,TCP和SSL之上,这样可以轻松兼容老版本的HTTP协议(将HTTP1.x的内容封装成一种新的frame格式),同时可以使用已有的SSL功能。HTTP2HTTP2.0可以说是SPDY的升级版(其实原本也是基于SPDY设计的),但是,HTTP2.0 跟 SPDY 仍有不同的地方,主要是以下两点HTTP2.0 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPSHTTP2.0 消息头的压缩算法采用 HPACK,而非 SPDY 采用的 DEFLATEhttp2 新特性新的二进制格式(Binary Format),HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。多路复用(MultiPlexing),支持单个连接多次请求,即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。header压缩,如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。服务端推送(server push),同SPDY一样,HTTP2.0也具有server push功能。目前,有大多数网站已经启用HTTP2.0,例如YouTuBe,淘宝网等网站,利用chrome控制台可以查看是否启用H2:chrome=>Network=>Name栏右键=>√Protocol本节参考文章:简单比较 http https http2、HTTPS科普扫盲帖12.GET/POSTGET在游览器回退会刷新,而POST会再次提交请求GET请求会被游览器主动缓存,而POST不会,除非手动设置GET请求参数会被完整的保留在游览器历史记录里,而POST中的参数不会被保留GET产生的URL地址可以被收藏,而POST不可以GET参数通过URL传递,而POST放在Request bodyGET请求只能进行URL编码,而POST支持多种编码方式GET请求在URL中传递的参数是有长度限制的,而POST没有限制GET请求会把参数直接暴露在URL上,相比POST更安全对参数的数据类型,GET只接受ASCII字符,而POST没有限制1.请求参数:get参数附在URL后面?隔开,POST参数放在包体中2.大小限制:GET限制为2048字符,post无限制3.安全问题:GET参数暴露在URL中,不如POST安全4.浏览器历史记录:GET可以记录,POST无记录5.缓存:GET可被缓存,post无6.书签:GET可被收藏为书签,post不可7.数据类型:GET只能ASCII码,post无限制13.MVC/MVVMMVCMVC是一种设计模式,它将应用划分为3个部分:数据(模型)、展示层(视图)和用户交互层。结合一下下图,更能理解三者之间的关系。换句话说,一个事件的发生是这样的过程用户和应用交互控制器的事件处理器被触发控制器从模型中请求数据,并将其交给视图视图将数据呈现给用户MVVMMVVM是Model-View-ViewModel的缩写。mvvm是一种设计思想。Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成UI 展现出来,ViewModel 是一个同步View 和 Model的对象。在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。在之前的MVC中我们提到一个控制器对应一个视图,控制器用状态机进行管理,如果项目足够大的时候,状态机的代码量就变得非常臃肿,难以维护。性能问题,在MVC中我们大量的操作了DOM,而大量操作DOM会让页面渲染性能降低,加载速度变慢,影响用户体验。最后就是当Model频繁变化的时候,开发者就手动更新View,那么数据的维护就变得困难。为了减小工作量,节约时间,一个更适合前端开发的架构模式就显得非常重要,这时候MVVM模式在前端中的应用就应运而生。mvvm和mvc区别mvc和mvvm其实区别并不大。都是一种设计思想。主要就是mvc中Controller演变成mvvm中的viewModel。mvvm主要解决了mvc中大量的DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。和当 Model 频繁发生变化,开发者需要主动更新到View 。3种方式实现MVVMdefineProperty(VUE),脏检查(angular),原生js实现(发布订阅者模式)在Angular中实现数据的观测使用的是脏检查,就是在用户进行可能改变ViewModel的操作的时候,对比以前老的ViewModel然后做出改变。vue.js 则是采用 数据劫持 结合 发布者-订阅者模式 的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。本节参考文章:MVC/MVVM14.axios优点同时支持浏览器端和服务端的请求支持promise支持请求和和数据返回的拦截转换请求返回数据,自动转换JSON数据取消请求客户端防止xsrf攻击在node端支持设置代理内部一些针对具体项目环境的二次封装本节参考文章:axios优点15.普通函数/箭头函数1、当要求动态上下文的时候,就不能够使用箭头函数,箭头函数this的固定化2、在使用=>定义函数的时候,this的指向是定义时所在的对象,而不是使用时所在的对象3、不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误4、不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替5、不可以使用yield命令,因此箭头函数不能用作Generator函数class Animal { constructor(){ this.type = ‘animal’ } says(say) { setTimeout(function () { console.log(this.type + ‘says’ + say) },1000) }}var animal = new Animal()animal.says(‘hi’) // undefined says hi我们再来看看=>的情况class Animal() { constructor() { this.type = ‘animal’ } says(say) { setTimeout(() => { console.log(this.type + ’ says ’ + say) }, 1000) }}var animal = new Animal()animal.says(‘hi’) // animal says hi本节参考文章:前端面试之ES6篇 ...

January 13, 2019 · 1 min · jiezi

前端基本功-常见概念(三)

前端基本功-常见概念(一) 点这里前端基本功-常见概念(二) 点这里前端基本功-常见概念(三) 点这里1.HTML / XML / XHTMLhtml:超文本标记语言,显示信息,不区分大小写xhtml:升级版的html,区分大小写xml:可扩展标记语言被用来传输和存储数据2.AMD/CMD/CommonJs/ES6 ModuleAMD:AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。AMD是requirejs 在推广过程中对模块定义的规范化产出,提前执行,推崇依赖前置。用define()定义模块,用require()加载模块,require.config()指定引用路径等首先我们需要引入require.js文件和一个入口文件main.js。main.js中配置require.config()并规定项目中用到的基础模块。 /** 网页中引入require.js及main.js / <script src=“js/require.js” data-main=“js/main”></script> / main.js 入口文件/主模块 / // 首先用config()指定各模块路径和引用名 require.config({ baseUrl: “js/lib”, paths: { “jquery”: “jquery.min”, //实际路径为js/lib/jquery.min.js “underscore”: “underscore.min”, } }); // 执行基本操作 require([“jquery”,“underscore”],function($,){ // some code here });引用模块的时候,我们将模块名放在[]中作为reqiure()的第一参数;如果我们定义的模块本身也依赖其他模块,那就需要将它们放在[]中作为define()的第一参数。 // 定义math.js模块 define(function () { var basicNum = 0; var add = function (x, y) { return x + y; }; return { add: add, basicNum :basicNum }; }); // 定义一个依赖underscore.js的模块 define([‘underscore’],function(){ var classify = function(list){ _.countBy(list,function(num){ return num > 30 ? ‘old’ : ‘young’; }) }; return { classify :classify }; }) // 引用模块,将模块放在[]内 require([‘jquery’, ‘math’],function($, math){ var sum = math.add(10,20); $("#sum").html(sum); });CMD:seajs 在推广过程中对模块定义的规范化产出,延迟执行,推崇依赖就近require.js在申明依赖的模块时会在第一之间加载并执行模块内的代码: define([“a”, “b”, “c”, “d”, “e”, “f”], function(a, b, c, d, e, f) { // 等于在最前面声明并初始化了要用到的所有模块 if (false) { // 即便没用到某个模块 b,但 b 还是提前执行了 b.foo() } });CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。 / CMD写法 / define(function(require, exports, module) { var a = require(’./a’); //在需要时申明 a.doSomething(); if (false) { var b = require(’./b’); b.doSomething(); } }); / sea.js / // 定义模块 math.js define(function(require, exports, module) { var $ = require(‘jquery.js’); var add = function(a,b){ return a+b; } exports.add = add; }); // 加载模块 seajs.use([‘math.js’], function(math){ var sum = math.add(1+2); });CommonJs:Node.js是commonJS规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块。 // 定义模块math.js var basicNum = 0; function add(a, b) { return a + b; } module.exports = { //在这里写上需要向外暴露的函数、变量 add: add, basicNum: basicNum } // 引用自定义的模块时,参数包含路径,可省略.js var math = require(’./math’); math.add(2, 5); // 引用核心模块时,不需要带路径 var http = require(‘http’); http.createService(…).listen(3000);commonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。ES6 Module:ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。/ 定义模块 math.js /var basicNum = 0;var add = function (a, b) { return a + b;};export { basicNum, add };/ 引用模块 /import { basicNum, add } from ‘./math’;function test(ele) { ele.textContent = add(99 + basicNum);}如上例所示,使用import命令的时候,用户需要知道所要加载的变量名或函数名。其实ES6还提供了export default命令,为模块指定默认输出,对应的import语句不需要使用大括号。这也更趋近于ADM的引用写法。/ export default **///定义输出export default { basicNum, add };//引入import math from ‘./math’;function test(ele) { ele.textContent = math.add(99 + math.basicNum);}ES6的模块不是对象,import命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。ES6 模块与 CommonJS 模块的差异CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。- 编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。本节参考文章:前端模块化:CommonJS,AMD,CMD,ES63.ES5的继承/ES6的继承ES5的继承时通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。具体的:ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。ps:super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可使用this关键字,否则报错。区别:(以SubClass,SuperClass,instance为例)ES5中继承的实质是:(那种经典寄生组合式继承法)通过prototype或构造函数机制来实现,先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。先由子类(SubClass)构造出实例对象this然后在子类的构造函数中,将父类(SuperClass)的属性添加到this上,SuperClass.apply(this, arguments)子类原型(SubClass.prototype)指向父类原型(SuperClass.prototype)所以instance是子类(SubClass)构造出的(所以没有父类的[[Class]]关键标志)所以,instance有SubClass和SuperClass的所有实例属性,以及可以通过原型链回溯,获取SubClass和SuperClass原型上的方法ES6中继承的实质是:先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this先由父类(SuperClass)构造出实例对象this,这也是为什么必须先调用父类的super()方法(子类没有自己的this对象,需先由父类构造)然后在子类的构造函数中,修改this(进行加工),譬如让它指向子类原型(SubClass.prototype),这一步很关键,否则无法找到子类原型(注,子类构造中加工这一步的实际做法是推测出的,从最终效果来推测)然后同样,子类原型(SubClass.prototype)指向父类原型(SuperClass.prototype)所以instance是父类(SuperClass)构造出的(所以有着父类的[[Class]]关键标志)所以,instance有SubClass和SuperClass的所有实例属性,以及可以通过原型链回溯,获取SubClass和SuperClass原型上的方法静态方法继承实质上只需要更改下SubClass.__proto__到SuperClass即可本节参考文章:链接4.HTTP request报文/HTTP response报文请求报文响应报文请求行 请求头 空行 请求体状态行 响应头 空行 响应体HTTP request报文结构是怎样的首行是Request-Line包括:请求方法,请求URI,协议版本,CRLF首行之后是若干行请求头,包括general-header,request-header或者entity-header,每个一行以CRLF结束请求头和消息实体之间有一个CRLF分隔根据实际请求需要可能包含一个消息实体 一个请求报文例子如下:GET /Protocols/rfc2616/rfc2616-sec5.html HTTP/1.1Host: www.w3.orgConnection: keep-aliveCache-Control: max-age=0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36Referer: https://www.google.com.hk/Accept-Encoding: gzip,deflate,sdchAccept-Language: zh-CN,zh;q=0.8,en;q=0.6Cookie: authorstyle=yesIf-None-Match: “2cc8-3e3073913b100"If-Modified-Since: Wed, 01 Sep 2004 13:24:52 GMTname=qiu&age=25请求报文HTTP response报文结构是怎样的首行是状态行包括:HTTP版本,状态码,状态描述,后面跟一个CRLF首行之后是若干行响应头,包括:通用头部,响应头部,实体头部响应头部和响应实体之间用一个CRLF空行分隔最后是一个可能的消息实体 响应报文例子如下:HTTP/1.1 200 OKDate: Tue, 08 Jul 2014 05:28:43 GMTServer: Apache/2Last-Modified: Wed, 01 Sep 2004 13:24:52 GMTETag: “40d7-3e3073913b100"Accept-Ranges: bytesContent-Length: 16599Cache-Control: max-age=21600Expires: Tue, 08 Jul 2014 11:28:43 GMTP3P: policyref=“http://www.w3.org/2001/05/P3P/p3p.xml"Content-Type: text/html; charset=iso-8859-1{“name”: “qiu”, “age”: 25}响应报文5.面向对象的工厂模式/构造函数工厂模式集中实例化了对象,避免实例化对象大量重复问题//工厂模式function createObject(a,b){ var obj = new Object(); //集中实例化 obj.a = a; obj.b = b; obj.c = function () { return this.a + this.b; }; return obj; //返回实例化对象}var box = createObject(‘abc’,10);var box1 = createObject(‘abcdef’,20);alert(box.c()); //返回abc10alert(box1.c()); //返回abcdef20//构造函数function Create(a,b) { this.a =a; this.b =b; this.c = function () { return this.a + this.b; };}var box = new Create(‘abc’,10);alert(box.run()); //返回abc10构造函数相比工厂模式:没有集中实例化没有返回对象实例直接将属性和方法赋值给this解决了对象实例归属问题构造函数编写规范:构造函数也是函数,但是函数名的第一个字母大写必须使用new运算符 + 函数名(首字母大写)例如:var box = new Create();构造函数和普通函数的区别:普通函数,首字母无需大写构造函数,用普通函数调用方式无效查看归属问题,要创建两个构造函数:function Create(a,b) { this.a =a; this.b =b; this.c = function () { return this.a + this.b; };}function DeskTop(a,b) { this.a =a; this.b =b; this.c = function () { return this.a + this.b; };}var box = new Create(‘abc’,10);var box1 = new DeskTop(‘def’,20);alert(box instanceof Object);//这里要注意:所有的构造函数的对象都是Object.alert(box instanceof Create); //truealert(box1 instanceof Create); //falsealert(box1 instanceof DeskTop); //true6. new Promise / Promise.resolve()Promise.resolve()可以生成一个成功的PromisePromise.resolve()语法糖例1:Promise.resolve(‘成功’)等同于new Promise(function(resolve){resolve(‘成功’)})例2:var resolved = Promise.resolve(‘foo’);resolved.then((str) => console.log(str);//foo)相当于var resolved = new Promise((resolve, reject) => { resolve(‘foo’)});resolved.then((str) => console.log(str);//foo)Promise.resolve方法有下面三种形式:Promise.resolve(value);Promise.resolve(promise);Promise.resolve(theanable);这三种形式都会产生一个新的Promise。其中:第一种形式提供了自定义Promise的值的能力,它与Promise.reject(reason)对应。两者的不同,在于得到的Promise的状态不同。第二种形式,提供了创建一个Promise的副本的能力。第三种形式,是将一个类似Promise的对象转换成一个真正的Promise对象。它的一个重要作用是将一个其他实现的Promise对象封装成一个当前实现的Promise对象。例如你正在用bluebird,但是现在有一个Q的Promise,那么你可以通过此方法把Q的Promise变成一个bluebird的Promise。实际上第二种形式可以归在第三种形式中。本节参考文章:ES6中的Promise.resolve()推荐阅读:性感的Promise…7.伪类 / 伪元素伪类伪类 用于当已有元素处于的某个状态时,为其添加对应的样式,这个状态是根据用户行为而动态变化的。当用户悬停在指定的元素时,我们可以通过 :hover 来描述这个元素的状态。虽然它和普通的 CSS 类相似,可以为已有的元素添加样式,但是它只有处于 DOM 树无法描述的状态下才能为元素添加样式,所以将其称为伪类。伪元素伪元素 用于创建一些不在文档树中的元素,并为其添加样式。我们可以通过 :before 来在一个元素前增加一些文本,并为这些文本添加样式。虽然用户可以看到这些文本,但是这些文本实际上不在文档树中。本节参考文章:前端面试题-伪类和伪元素、总结伪类与伪元素8.DOMContentLoaded / loadDOM文档加载的步骤为:解析HTML结构。DOM树构建完成。//DOMContentLoaded加载外部脚本和样式表文件。解析并执行脚本代码。加载图片等外部文件。页面加载完毕。//load触发的时机不一样,先触发DOMContentLoaded事件,后触发load事件。原生js// 不兼容老的浏览器,兼容写法见jQuery中ready与load事件,或用jQuerydocument.addEventListener(“DOMContentLoaded”, function() { // …代码…}, false);window.addEventListener(“load”, function() { // …代码…}, false);jQuery// DOMContentLoaded$(document).ready(function() { // …代码…});//load$(document).load(function() { // …代码…});head 中资源的加载head 中 js 资源加载都会停止后面 DOM 的构建,但是不影响后面资源的下载。css资源不会阻碍后面 DOM 的构建,但是会阻碍页面的首次渲染。body 中资源的加载body 中 js 资源加载都会停止后面 DOM 的构建,但是不影响后面资源的下载。css 资源不会阻碍后面 DOM 的构建,但是会阻碍页面的首次渲染。DomContentLoaded 事件的触发上面只是讲了 html 文档的加载与渲染,并没有讲 DOMContentLoaded 事件的触发时机。直截了当地结论是,DOMContentLoaded 事件在 html文档加载完毕,并且 html 所引用的内联 js、以及外链 js 的同步代码都执行完毕后触发。大家可以自己写一下测试代码,分别引用内联 js 和外链 js 进行测试。load 事件的触发当页面 DOM 结构中的 js、css、图片,以及 js 异步加载的 js、css 、图片都加载完成之后,才会触发 load 事件。注意:页面中引用的js 代码如果有异步加载的 js、css、图片,是会影响 load 事件触发的。video、audio、flash 不会影响 load 事件触发。推荐阅读:再谈 load 与 DOMContentLoaded本节参考文章:DOMContentLoaded与load的区别、事件DOMContentLoaded和load的区别9. 为什么将css放在头部,将js文件放在尾部因为浏览器生成Dom树的时候是一行一行读HTML代码的,script标签放在最后面就不会影响前面的页面的渲染。那么问题来了,既然Dom树完全生成好后页面才能渲染出来,浏览器又必须读完全部HTML才能生成完整的Dom树,script标签不放在body底部是不是也一样,因为dom树的生成需要整个文档解析完毕。我们再来看一下chrome在页面渲染过程中的,绿色标志线是First Paint的时间。纳尼,为什么会出现firstpaint,页面的paint不是在渲染树生成之后吗?其实现代浏览器为了更好的用户体验,渲染引擎将尝试尽快在屏幕上显示的内容。它不会等到所有HTML解析之前开始构建和布局渲染树。部分的内容将被解析并显示。也就是说浏览器能够渲染不完整的dom树和cssom,尽快的减少白屏的时间。假如我们将js放在header,js将阻塞解析dom,dom的内容会影响到First Paint,导致First Paint延后。所以说我们会 将js放在后面,以减少First Paint的时间,但是不会减少DOMContentLoaded被触发的时间。本节参考文章:DOMContentLoaded与load的区别10.clientheight / offsetheightclientheight:内容的可视区域,不包含border。clientheight=padding+height-横向滚动轴高度。这里写图片描述offsetheight,它包含padding、border、横向滚动轴高度。 offsetheight=padding+height+border+横向滚动轴高度scrollheight,可滚动高度,就是将滚动框拉直,不再滚动的高度,这个很好理解。 It includes the element’s padding, but not its border or margin.本节参考文章:css clientheight、offsetheight、scrollheight详解11.use strict 有什么意义和好处使调试更加容易。那些被忽略或默默失败了的代码错误,会产生错误或抛出异常,因此尽早提醒你代码中的问题,你才能更快地指引到它们的源代码。防止意外的全局变量。如果没有严格模式,将值分配给一个未声明的变量会自动创建该名称的全局变量。这是JavaScript中最常见的错误之一。在严格模式下,这样做的话会抛出错误。消除 this 强制。如果没有严格模式,引用null或未定义的值到 this 值会自动强制到全局变量。这可能会导致许多令人头痛的问题和让人恨不得拔自己头发的bug。在严格模式下,引用 null或未定义的 this 值会抛出错误。不允许重复的属性名称或参数值。当检测到对象中重复命名的属性,例如:var object = {foo: “bar”, foo: “baz”};)或检测到函数中重复命名的参数时,例如:function foo(val1, val2, val1){})严格模式会抛出错误,因此捕捉几乎可以肯定是代码中的bug可以避免浪费大量的跟踪时间。使 eval() 更安全。在严格模式和非严格模式下, eval() 的行为方式有所不同。最显而易见的是,在严格模式下,变量和声明在 eval() 语句内部的函数不会在包含范围内创建(它们会在非严格模式下的包含范围中被创建,这也是一个常见的问题源)。在 delete 使用无效时抛出错误。 delete 操作符(用于从对象中删除属性)不能用在对象不可配置的属性上。当试图删除一个不可配置的属性时,非严格代码将默默地失败,而严格模式将在这样的情况下抛出异常。本节参考文章:经典面试题(4)12.常见 JavaScript 内存泄漏意外的全局变量JavaScript 处理未定义变量的方式比较宽松:未定义的变量会在全局对象创建一个新变量。在浏览器中,全局对象是 window 。function foo(arg) { bar = “this is a hidden global variable”;}真相是:function foo(arg) { window.bar = "this is an explicit global variable";}函数 foo 内部忘记使用 var ,意外创建了一个全局变量。此例泄漏了一个简单的字符串,无伤大雅,但是有更糟的情况。另一种意外的全局变量可能由 this 创建:function foo() { this.variable = "potential accidental global";}// Foo 调用自己,this 指向了全局对象(window)// 而不是 undefinedfoo();在 JavaScript 文件头部加上 ‘use strict’,可以避免此类错误发生。启用严格模式解析 JavaScript ,避免意外的全局变量。被遗忘的计时器或回调函数在 JavaScript 中使用 setInterval 非常平常。一段常见的代码:var someResource = getData();setInterval(function() { var node = document.getElementById(‘Node’); if(node) { // 处理 node 和 someResource node.innerHTML = JSON.stringify(someResource)); }}, 1000);此例说明了什么:与节点或数据关联的计时器不再需要,node 对象可以删除,整个回调函数也不需要了。可是,计时器回调函数仍然没被回收(计时器停止才会被回收)。同时,someResource 如果存储了大量的数据,也是无法被回收的。对于观察者的例子,一旦它们不再需要(或者关联的对象变成不可达),明确地移除它们非常重要。老的 IE 6 是无法处理循环引用的。如今,即使没有明确移除它们,一旦观察者对象变成不可达,大部分浏览器是可以回收观察者处理函数的。观察者代码示例:var element = document.getElementById(‘button’);function onClick(event) { element.innerHTML = ’text’;}element.addEventListener(‘click’, onClick);对象观察者和循环引用注意事项老版本的 IE 是无法检测 DOM 节点与 JavaScript 代码之间的循环引用,会导致内存泄漏。如今,现代的浏览器(包括 IE 和 Microsoft Edge)使用了更先进的垃圾回收算法,已经可以正确检测和处理循环引用了。换言之,回收节点内存时,不必非要调用 removeEventListener 了。脱离 DOM 的引用有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除。var elements = { button: document.getElementById(‘button’), image: document.getElementById(‘image’), text: document.getElementById(’text’)};function doStuff() { image.src = ‘http://some.url/image'; button.click(); console.log(text.innerHTML); // 更多逻辑}function removeButton() { // 按钮是 body 的后代元素 document.body.removeChild(document.getElementById(‘button’)); // 此时,仍旧存在一个全局的 #button 的引用 // elements 字典。button 元素仍旧在内存中,不能被 GC 回收。}此外还要考虑 DOM 树内部或子节点的引用问题。假如你的 JavaScript 代码中保存了表格某一个 <td> 的引用。将来决定删除整个表格的时候,直觉认为 GC 会回收除了已保存的 <td> 以外的其它节点。实际情况并非如此:此 <td> 是表格的子节点,子元素与父元素是引用关系。由于代码保留了 <td> 的引用,导致整个表格仍待在内存中。保存 DOM 元素引用的时候,要小心谨慎。闭包闭包是 JavaScript 开发的一个关键方面:匿名函数可以访问父级作用域的变量。避免滥用本节参考文章:4类 JavaScript 内存泄漏及如何避免13.引用计数 / 标记清除js垃圾回收有两种常见的算法:引用计数和标记清除。引用计数就是跟踪对象被引用的次数,当一个对象的引用计数为0即没有其他对象引用它时,说明该对象已经无需访问了,因此就会回收其所占的内存,这样,当垃圾回收器下次运行就会释放引用数为0的对象所占用的内存。标记清除法是现代浏览器常用的一种垃圾收集方式,当变量进入环境(即在一个函数中声明一个变量)时,就将此变量标记为“进入环境”,进入环境的变量是不能被释放,因为只有执行流进入相应的环境,就可能会引用它们。而当变量离开环境时,就标记为“离开环境”。垃圾收集器在运行时会给储存在内存中的所有变量加上标记,然后会去掉环境中的变量以及被环境中的变量引用的变量的标记,当执行完毕那些没有存在引用 无法访问的变量就被加上标记,最后垃圾收集器完成清除工作,释放掉那些打上标记的变量所占的内存。 function problem() { var A = {}; var B = {}; A.a = B; B.a = A;}引用计数存在一个弊端就是循环引用问题(上边)标记清除不存在循环引用的问题,是因为当函数执行完毕之后,对象A和B就已经离开了所在的作用域,此时两个变量被标记为“离开环境”,等待被垃圾收集器回收,最后释放其内存。分析以下代码: function createPerson(name){ var localPerson = new Object(); localPerson.name = name; return localPerson; } var globalPerson = createPerson(“Junga”); globalPerson = null;//手动解除全局变量的引用在这个????中,变量globalPerson取得了createPerson()函数的返回的值。在createPerson()的内部创建了一个局部变量localPerson并添加了一个name属性。由于localPerson在函数执行完毕之后就离开执行环境,因此会自动解除引用,而对于全局变量来说则需要我们手动设置null,解除引用。不过,解除一个值的引用并不意味着自动回收该值所占用的内存,解除引用真正的作用是让值脱离执行环境,以便垃圾收集器下次运行时将其收回。本节参考文章:JavaScript的内存问题14.前后端路由差别1.后端每次路由请求都是重新访问服务器2.前端路由实际上只是JS根据URL来操作DOM元素,根据每个页面需要的去服务端请求数据,返回数据后和模板进行组合。本节参考文章:2018前端面试总结…15.window.history / location.hash通常 SPA 中前端路由有2种实现方式:window.historylocation.hash下面就来介绍下这两种方式具体怎么实现的一.history1.history基本介绍window.history 对象包含浏览器的历史,window.history 对象在编写时可不使用 window 这个前缀。history是实现SPA前端路由是一种主流方法,它有几个原始方法:history.back() - 与在浏览器点击后退按钮相同history.forward() - 与在浏览器中点击按钮向前相同history.go(n) - 接受一个整数作为参数,移动到该整数指定的页面,比如go(1)相当于forward(),go(-1)相当于back(),go(0)相当于刷新当前页面如果移动的位置超出了访问历史的边界,以上三个方法并不报错,而是静默失败在HTML5,history对象提出了 pushState() 方法和 replaceState() 方法,这两个方法可以用来向历史栈中添加数据,就好像 url 变化了一样(过去只有 url 变化历史栈才会变化),这样就可以很好的模拟浏览历史和前进后退了,现在的前端路由也是基于这个原理实现的。2.history.pushStatepushState(stateObj, title, url) 方法向历史栈中写入数据,其第一个参数是要写入的数据对象(不大于640kB),第二个参数是页面的 title, 第三个参数是 url (相对路径)。stateObj :一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null。title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。关于pushState,有几个值得注意的地方:pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应,只有当触发前进后退等事件(back()和forward()等)时浏览器才会刷新这里的 url 是受到同源策略限制的,防止恶意脚本模仿其他网站 url 用来欺骗用户,所以当违背同源策略时将会报错3.history.replaceStatereplaceState(stateObj, title, url) 和pushState的区别就在于它不是写入而是替换修改浏览历史中当前纪录,其余和 pushState一模一样4.popstate事件定义:每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。注意:仅仅调用pushState方法或replaceState方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用JavaScript调用back、forward、go方法时才会触发。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。用法:使用的时候,可以为popstate事件指定回调函数。这个回调函数的参数是一个event事件对象,它的state属性指向pushState和replaceState方法为当前URL所提供的状态对象(即这两个方法的第一个参数)。5.history实现spa前端路由代码<a class=“api a”>a.html</a><a class=“api b”>b.html</a> // 注册路由 document.querySelectorAll(’.api’).forEach(item => { item.addEventListener(‘click’, e => { e.preventDefault(); let link = item.textContent; if (!!(window.history && history.pushState)) { // 支持History API window.history.pushState({name: ‘api’}, link, link); } else { // 不支持,可使用一些Polyfill库来实现 } }, false) }); // 监听路由 window.addEventListener(‘popstate’, e => { console.log({ location: location.href, state: e.state }) }, false)popstate监听函数里打印的e.state便是history.pushState()里传入的第一个参数,在这里即为{name: ‘api’}二.Hash1.Hash基本介绍url 中可以带有一个 hash http://localhost:9000/#/a.htmlwindow 对象中有一个事件是 onhashchange,以下几种情况都会触发这个事件:直接更改浏览器地址,在最后面增加或改变#hash;通过改变location.href或location.hash的值;通过触发点击带锚点的链接;浏览器前进后退可能导致hash的变化,前提是两个网页地址中的hash值不同。2.Hash实现spa前端路由代码 // 注册路由 document.querySelectorAll(’.api’).forEach(item => { item.addEventListener(‘click’, e => { e.preventDefault(); let link = item.textContent; location.hash = link; }, false) }); // 监听路由 window.addEventListener(‘hashchange’, e => { console.log({ location: location.href, hash: location.hash }) }, false)本节参考文章:vue 单页应用(spa)前端路由实现原理 ...

January 13, 2019 · 5 min · jiezi

前端基本功-常见概念(二)

前端基本功-常见概念(一) 点这里前端基本功-常见概念(二) 点这里前端基本功-常见概念(三) 点这里1.let、const/varlet 是更完美的var,不是全局变量,具有块级函数作用域,大多数情况不会发生变量提升。const 定义常量值,不能够重新赋值,如果值是一个对象,可以改变对象里边的属性值var存在的问题var有作用域问题(会污染全局作用域)var可已重复声明var会变量提升预解释var不能定义常量let、const特性let、const不可以重复声明let、const不会声明到全局作用域上let、const不会预解释变量const做常量声明(一般常量名用大写)let声明的变量具有块级作用域let声明的变量不能通过window.变量名进行访问形如for(let x..)的循环是每次迭代都为x创建新的绑定下面是 var 带来的不合理场景var a = []for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i) }}a5 // 10在上述代码中,变量i是var声明的,在全局范围类都有效。所以每一次循环,新的i值都会覆盖旧值,导致最后输出都是10而如果对循环使用let语句的情况,那么每次迭代都是为x创建新的绑定代码如下var a = []for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i) }}a5 // 5重温一下闭包和立即函数两种方法闭包的方法function showNum(i) { return function () { console.log(i) }}var a = []for (var i = 0; i < 5; i++) { a[i] = showNum(i)}立即函数的方法var a = []for (var i = 0; i < 5; i++) { a[i] = (function (i) { return function () { console.log(i) } })(i)}a2本节参考文章:前端面试之ES6篇2.重排/重绘在讨论重排(回流)与重绘之前,我们要知道:浏览器使用流式布局模型 (Flow Based Layout)。浏览器会把HTML成DOM,把CSS解析成CSSOM,DOM和CSSOM合并就产生了Render Tree。有了RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。由于浏览器使用流式布局,对Render Tree的计算通常只需要遍历一次就可以完成,但table及其内部元素除外,他们可能需要多次计算,通常要花3倍于同等元素的时间,这也是为什么要避免使用table布局的原因之一。一句话:重排必将引起重绘,重绘不一定会引起重排。重排 (Reflow)当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为重排。会导致重排的操作:页面首次渲染浏览器窗口大小发生改变元素尺寸或位置发生改变元素内容变化(文字数量或图片大小等等)元素字体大小变化添加或者删除可见的DOM元素激活CSS伪类(例如::hover)查询某些属性或调用某些方法一些常用且会导致重排的属性和方法:clientWidth、clientHeight、clientTop、clientLeftoffsetWidth、offsetHeight、offsetTop、offsetLeftscrollWidth、scrollHeight、scrollTop、scrollLeftscrollIntoView()、scrollIntoViewIfNeeded()getComputedStyle()getBoundingClientRect()scrollTo()重绘 (Repaint)当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。性能影响回流比重绘的代价要更高。有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。现代浏览器会对频繁的回流或重绘操作进行优化:浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。当你访问以下属性或方法时,浏览器会立刻清空队列:clientWidth、clientHeight、clientTop、clientLeftoffsetWidth、offsetHeight、offsetTop、offsetLeftscrollWidth、scrollHeight、scrollTop、scrollLeftwidth、heightgetComputedStyle()getBoundingClientRect()因为队列中可能会有影响到这些属性或方法返回值的操作,即使你希望获取的信息与队列中操作引发的改变无关,浏览器也会强行清空队列,确保你拿到的值是最精确的。如何避免CSS避免使用table布局。尽可能在DOM树的最末端改变class。避免设置多层内联样式。将动画效果应用到position属性为absolute或fixed的元素上。避免使用CSS表达式(例如:calc())。JavaScript避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。本节参考文章:浏览器的回流与重绘 (Reflow & Repaint)3.函数节流(throttle)与函数去抖(debounce)Debounce:一部电梯停在某一个楼层,当有一个人进来后,20秒后自动关门,这20秒的等待期间,又一个人按了电梯进来,这20秒又重新计算,直到电梯关门那一刻才算是响应了事件。Throttle:好比一台自动的饮料机,按拿铁按钮,在出饮料的过程中,不管按多少这个按钮,都不会连续出饮料,中间按钮的响应会被忽略,必须要等这一杯的容量全部出完之后,再按拿铁按钮才会出下一杯。4.强缓存/协商缓存浏览器缓存分为强缓存和协商缓存,优先读取强制缓存。当客户端请求某个资源时,获取缓存的流程如下:先根据这个资源的一些 http header 判断它是否命中强缓存,如果命中,则直接从本地获取缓存资源,不会发请求到服务器;当强缓存没有命中时,客户端会发送请求到服务器,服务器通过另一些request header验证这个资源是否命中协商缓存,称为http再验证,如果命中,服务器将请求返回,但不返回资源,而是告诉客户端直接从缓存中获取,客户端收到返回后就会从缓存中获取资源;强缓存和协商缓存共同之处在于,如果命中缓存,服务器都不会返回资源;区别是,强缓存不对发送请求到服务器,但协商缓存会。当协商缓存也没命中时,服务器就会将资源发送回客户端。当 ctrl+f5 强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存;当 f5 刷新网页时,跳过强缓存,但是会检查协商缓存;强缓存Expires(该字段是 http1.0 时的规范,值为一个绝对时间的 GMT 格式的时间字符串,代表缓存资源的过期时间)Cache-Control:max-age(该字段是 http1.1 的规范,强缓存利用其 max-age 值来判断缓存资源的最大生命周期,它的值单位为秒)协商缓存协商缓存是利用的是【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】这两对Header来管理的Last-Modified,If-Modified-SinceLast-Modified 表示本地文件最后修改日期,浏览器会在request header加上If-Modified-Since(上次返回的Last-Modified的值),询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETagETag、If-None-MatchEtag就像一个指纹,资源变化都会导致ETag变化,跟最后修改时间没有关系,ETag可以保证每一个资源是唯一的If-None-Match的header会将上次返回的Etag发送给服务器,询问该资源的Etag是否有更新,有变动就会发送新的资源回来ETag的优先级比Last-Modified更高一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);某些服务器不能精确的得到文件的最后修改时间。推荐必读:http协商缓存VS强缓存、浏览器缓存知识小结及应用、缓存(二)——浏览器缓存机制:强缓存、协商缓存5.原始类型 / 引用类型JavaScript中的内存分为栈内存和堆内存。栈内存和堆内存区别:栈内存运行效率比堆内存高,空间相对堆内存来说较小。区别:值类型属于不可变类型, 由于具有固定长度大小, 其地址和具体内容都存在与栈内存中而引用类型属于可变类型, 一个对象可以赋予多个属性及值,属性值又可以为一个新的引用对象。其地址存在栈内存,其具体内容存在堆内存中。6.cookie/tokentoken和cookie一样都是首次登陆时,由服务器下发,都是当交互时进行验证的功能,作用都是为无状态的HTTP提供的持久机制。token存在哪儿都行,localstorage或者cookie。token和cookie举例,token就是说你告诉我你是谁就可以。cookie 举例:服务员看你的身份证,给你一个编号,以后,进行任何操作,都出示编号后服务员去看查你是谁。token 举例:直接给服务员看自己身份证,服务器不需要去查看你是谁,不需要保存你的会话。当用户logout的时候,cookie和服务器的session都会注销;但是当logout时候token只是注销浏览器信息,不查库。token优势在于,token由于服务器端不存储会话,所以可扩展性强,token还可用于APP中。小结:Token 完全由应用管理,所以它可以避开同源策略Token 可以避免 CSRF 攻击Token 可以是无状态的,可以在多个服务间共享如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token,如果之上自己的那就无所谓了。 本节参考文章:cookie和token的五点区别推荐必读:前后端常见的几种鉴权方式7.cookie/sessionStorage/localStorage1.cookiecookie分为cookie机制和session机制(请大神判断正确性)Session: 是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中,通过在服务器端记录信息确定用户身份Cookie: 是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式,通过在客户端记录信息确定用户身份如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。cookie机制cookie可以通过设置domain属性值,可以不同二级域名下共享cookie,而Storage不可以,比如http://image.baidu.com的cookie http://map.baidu.com是可以访问的,前提是Cookie的domain设置为.http://baidu.com,而Storage是不可以的session机制当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识—称为session id,如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。比较session 在服务器端,cookie 在客户端(浏览器)session保存在服务器,客户端不知道其中的信息;反之,cookie保存在客户端,服务器能够知道其中的信息session会在一定时间内保存在服务器上,当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookiesession中保存的是对象,cookie中保存的是字符串cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。用户验证这种场合一般会用 session用户验证这种场合一般会用 session,因此,维持一个会话的核心就是客户端的唯一标识,即 session idsession 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)session不能区分路径,同一个用户在访问一个网站期间,所有的session在任何一个地方都可以访问到,而cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的JavaScript原生的用法。Cookie 以名/值对形式存储例如username=John Doe,这里的数据是string类型,如要是其他格式注意进行格式转换。JavaScript 可以使用 document.cookie 属性来创建 、读取、及删除 cookie。JavaScript 中,创建 cookie 如下所示:document.cookie=“username=John Doe”;您还可以为 cookie 添加一个过期时间(以 UTC 或 GMT 时间)。默认情况下,cookie 在浏览器关闭时删除:document.cookie=“username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 GMT”;您可以使用 path 参数告诉浏览器 cookie 的路径。默认情况下,cookie 属于当前页面。document.cookie=“username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 GMT; path=/";设置cookiefunction setCookie(cname,cvalue,exdays){ var SetTime = new Date(); //设置过期时间 SetTime.setTime(SetTime.getTime()+(exdays2460601000)); //设置过期时间 var expires = “expires="+SetTime.toGMTString(); //设置过期时间 document.cookie = cname + “=” + cvalue + “; " + expires; //创建一个cookie}读取cookiefunction getCookie(c_name){if (document.cookie.length>0) { c_start=document.cookie.indexOf(c_name + “=”) if (c_start!=-1){ c_start=c_start + c_name.length+1 c_end=document.cookie.indexOf(”;",c_start) if (c_end==-1) c_end=document.cookie.length return unescape(document.cookie.substring(c_start,c_end)) } }return “"}删除cookie将cookie的有效时间改成昨天。使用jquery.cookies.2.2.0.min.js插件添加/修改cookie并设定过期时间:$.cookies.set(‘cookie_id’, ‘cookie_value’, { hoursToLive: 10 });这里设置的是过期时间是10小时, 还可以这样设置过期时间:expireDate = new Date();expireDate.setTime( expireDate.getTime() + ( 10 * 60 * 60 * 1000 ) );$.cookies.set(‘cookie_id’, ‘cookie_value’, {expiresAt:expireDate});获取cookie$.cookies.get(‘cookie_id’);删除cookie$.cookies.del(‘cookie_id’);2.Storage:localStorage、sessionStorage大小:官方建议是5M存储空间类型:只能操作字符串,在存储之前应该使用JSON.stringfy()方法先进行一步安全转换字符串,取值时再用JSON.parse()方法再转换一次存储的内容: 数组,图片,json,样式,脚本。。。(只要是能序列化成字符串的内容都可以存储)区别:sessionStorage将数据临时存储在session中,浏览器关闭,数据随之消失,localStorage将数据存储在本地,理论上来说数据永远不会消失,除非人为删除注意:数据是明文存储,毫无隐私性可言,绝对不能用于存储基础操作API保存数据localStorage.setItem( key, value );sessionStorage.setItem(keyName,value); // 将value存储到key字段中//或者sessionStorage.keyName=‘value’;读取数据localStorage.getItem( key );sessionStorage.getItem(keyName); //获取指定key的本地存储的值//或者var keyName=sessionStorage.key;删除单个数据localStorage.removeItem( key );sessionStorage.removeItem( key );删除全部数据localStorage.clear( );sessionStorage.clear( );获取索引的keylocalStorage.key( index );sessionStorage.key( index );监听storage事件可以通过监听 window 对象的 storage 事件并指定其事件处理函数,当页面中对 localStorage 或 sessionStorage 进行修改时,则会触发对应的处理函数window.addEventListener(‘storage’,function(e){ console.log(‘key=’+e.key+’,oldValue=’+e.oldValue+’,newValue=’+e.newValue);})localstorage是浏览器多个标签共用的存储空间,可以用来实现多标签之间的通信(ps:session是会话级的存储空间,每个标签页都是单独的触发事件的时间对象(e 参数值)有几个属性:key : 键值。oldValue : 被修改前的值。newValue : 被修改后的值。url : 页面url。storageArea : 被修改的 storage 对象。3.对比共同点:都是保存在浏览器端、且同源的,都受同源策略的制约。区别:cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的本节参考文章:缓存(三)——数据存储…其他阅读:关于Cookie、session和Web Storage8.js事件1.事件事件指可以被 JavaScript 侦测到的行为。即鼠标点击、页面或图像载入、鼠标悬浮于页面的某个热点之上、在表单中选取输入框、确认表单、键盘按键等操作。事件通常与函数配合使用,当事件发生时函数才会执行。事件名称:click/mouseover/blur(“不带on”)事件处理程序(事件侦听器):响应某个事件的函数,名称为:onclick/onmouseove/onblur,例如<button onclick=“alert(‘hello’)"></button>2.DOM事件模型:冒泡和捕获冒泡:往上捕获:向下3.事件流事件流指从页面中接收事件的顺序,也可理解为事件在页面中传播的顺序。DOM2级事件规定的事件流包括三个阶段:(1)事件捕获阶段(2)处于目标阶段(3)事件冒泡阶段。当事件发生时,最先得到通知的是window,然后是document,由上至下逐级依次而入,直到真正触发事件的那个元素(目标元素)为止,这个过程就是捕获。接下来,事件会从目标元素开始起泡,由下至上逐级依次传播,直到window对象为止,这个过程就是冒泡。所以捕获比冒泡先执行。希望注册在DOM元素上的事件处理程序在捕获阶段还是在冒泡阶段触发,取决于 addEventListener() 方法的第三个参数为 true 还是 false 。其中DOM3级事件在DOM2的基础之上添加了更多的事件类型。描述DOM事件捕获的具体流程window–>document–>html(document.documentElement)–>body(document.body)…4.DOM级别/DOM事件DOM级别一共可以分为4个级别:DOM0级,DOM1级,DOM2级和DOM3级,而DOM事件分为3个级别:DOM0级事件处理,DOM2级事件处理和DOM3级事件处理。其中1级DOM标准中并没有定义事件相关的内容,所以没有所谓的1级DOM事件模型。DOM0:element.onclick = function(){}DOM2:element.addEventlistenter(‘click’,function(){},flase)DOM3:element.addEventlistenter(‘keyup’,function(){},flase)HTML事件处理程序<button onclick=“alert(‘hello’)"></button><button onclick=“doSomething()"></button><button onclick=“try{doSomething();}catch(err){}"></button>DOM0级事件处理程序 btn.onclick=function(){ alert(“hello”); }btn.onclick = null;//来删除指定的事件处理程序。如果我们尝试给事件添加两个事件,如:<button id=“btn”>点击</button> <script> var btn=document.getElementById(“btn”); btn.onclick=function(){ alert(“hello”); } btn.onclick=function(){ alert(“hello again”); }</script>输出,hello again,很明显,第一个事件函数被第二个事件函数给覆盖掉了, 所以,DOM0级事件处理程序不能添加多个,也不能控制事件流到底是捕获还是冒泡。DOM2级事件处理程序addEventListener() —添加事件侦听器函数均有3个参数, 第一个参数是要处理的事件名(不带on前缀的才是事件名) 第二个参数是作为事件处理程序的函数 第三个参数是一个boolean值,默认false表示使用冒泡机制,true表示捕获机制。<button id=“btn”>点击</button> <script> var btn=document.getElementById(“btn”); btn.addEventListener(‘click’,hello,false); btn.addEventListener(‘click’,helloagain,false); function hello(){ alert(“hello”); } function helloagain(){ alert(“hello again”); }</script> removeEventListener() //删除事件侦听器可以绑定多个事件处理程序,但是注意,如果定义了一摸一样时监听方法,是会发生覆盖的,即同样的事件和事件流机制下相同方法只会触发一次,事件触发的顺序是添加的顺序// 为了兼容IE浏览器和标准的浏览器,我们需要编写通用的方法来处理:var EventUtil = { addHandler: function (element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else if (element.attachEvent) { element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }, removeHandler: function (element, type, handler) { if (element.removeEventListener()) { element.removeEventListener(type, handler, false); } else if (element.detachEvent) { element.detachEvent("on" + type, handler); } else { element["on" + type] = null; } }};5.事件对象事件对象是用来记录一些事件发生时的相关信息的对象,但事件对象只有事件发生时才会产生,并且只能是事件处理函数内部访问,在所有事件处理函数运行结束后,事件对象就被销毁!//currentTarget、eventPhase 一个例子:<button id=“btn”>点击</button> <script> var btn=document.getElementById(“btn”); btn.ddEventListener(‘click’, doCurrent, true); // 判断事件的属性 function doCurrent(event) { //获取当前事件触发的div var target = event.currentTarget; //通过判断事件的event.eventPhase属性返回事件传播的当前阶段 //1:捕获阶段、2:正常事件派发和3:起泡阶段。 //得到当前阶段和id值并输出 var msg = (event.eventPhase == 1 ? ‘捕获阶段:’ : ‘冒泡阶段:’)+ target.attributes[“id”].value;; console.log(msg); }</script>当然,事件对象也存在一定的兼容性问题,在IE8及以前本版之中,通过设置属性注册事件处理程序时,调用的时候并未传递事件对象,需要通过全局对象window.event来获取。解决方法如下:function getEvent(event) { event = event || window.event;}在IE浏览器上面是event事件是没有preventDefault()这个属性的,所以在IE上,我们需要设置的属性是returnValuewindow.event.returnValue=falsestopPropagation()也是,所以需要设置cancelBubble,cancelBubble是IE事件对象的一个属性,设置这个属性为true能阻止事件进一步传播。event.cancelBubble=true常见属性解析event.preventDefault()阻止默认行为event.stopPropagation()阻止冒泡。使用了stopPropagation()之后,事件就不能进一步传播了,同时如果是同一个div上有捕获和冒泡两种事件监听,在捕获阶段传播阻止后冒泡阶段事件监听也不会触发。event.stopImmediatePropagation()使用了stopImmediatePropagation()之后,绑定的后续事件监听都会忽略。event.currentTarget当前绑定的事件event.target事件代理时 点击的元素关于捕获和冒泡:理解 addEventListener、捕获和冒泡6.自定义事件jq // 添加一个适当的事件监听器$(’#foo’).on(‘custom’, function(event, param1, param2) { alert(param1 + “\n” + param2);});$(’#foo’).trigger(‘custom’, [‘Custom’, ‘Event’]);原生Event:var eve = new Event(‘custome’)element.addEventListenter(‘custome’,function(){ console.log(‘custome’)})element.dispatchEvent(eve)原生CustomEvent// 添加一个适当的事件监听器obj.addEventListener(“custom”, function(e) { console.log(JSON.stringify(e.detail)); })// 创建并分发事件var event = new CustomEvent(“custom”, {“detail”:{“Custom”:true}});obj.dispatchEvent(event)本节参考文章:一个能拖动,能调整大小,能更新bind值的vue指令-vuedragx7.事件委托(代理)事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。案例一: <button id=“btnAdd”>添加</button> <ul id=“ulList”> <li>1</li> <li>2</li> <li>3</li> </ul> <script> var btnAdd = document.getElementById(‘btnAdd’); var ulList = document.getElementById(‘ulList’); var list = document.getElementsByTagName(’li’); var num = 3; btnAdd.onclick = function () { num++; var li = document.createElement(’li’); li.innerHTML = num; ulList.appendChild(li) } for (i = 0; i < list.length; i++) { list[i].onclick = function(){ alert(this.innerHTML); } } </script> //例子说明,我们为ul添加新的li, //其中对li标签元素绑定了click事件, //但是发现,后增加的元素没有办法触发我们的click事件。解决方法:事件委托 <button id=“btnAdd”>添加</button> <ul id=“ulList”> <li class=“class-1”>1</li> <li class=“class-1”>2</li> <li class=“class-1”>3</li> </ul> <script> var btnAdd = document.getElementById(‘btnAdd’); var ulList = document.getElementById(‘ulList’); var num = 3; ulList.onclick = function(event){ var event = event || window.event; var target = event.target || event.srcElement; // if (target.matches(’li.class-1’)) //#ulList 元素下的 li 元素(并且它的 class 为 class-1) if(target.nodeName.toLowerCase() == ’li’){ alert(target.innerHTML); } } btnAdd.onclick = function () { num++; var li = document.createElement(’li’); li.innerHTML = num; ulList.appendChild(li); } </script>案例二:点击“添加”按钮添加一个按钮,点击添加的按钮移除这个按钮<div class=“wrap” id=“wrap”> <div class=“btn” data-type=“btn” data-feat=“add”>添加</div> <div class=“btn” data-type=“btn” data-feat=“delete”>绘画</div> <div class=“btn” data-type=“btn” data-feat=“delete”>散步</div> <div class=“btn” data-type=“btn” data-feat=“delete”>静坐</div></div>document.getElementById(‘wrap’).addEventListener(‘click’, function(e){ var target = e.target; while(target !== this){ var type = target.dataset.type; if(type == ‘btn’){ var feat = target.dataset.feat; switch(feat){ case ‘add’: this.innerHTML += ‘<div class=“btn” data-type=“btn” data-feat=“delete”>静坐</div>’ return; case ‘delete’: target.parentNode.removeChild(target); return; } } target = target.parentNode; }}, false);适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。推荐阅读:JavaScript 事件委托详解本节参考文章:前端小知识–JavaScript事件流9.link / @import两者都是外部引用 CSS 的方式,但是存在一定的区别:link是XHTML标签,除了能够加载CSS,还可以定义RSS等其他事务;而@import属于CSS范畴,只可以加载CSS。link引用CSS时,在页面载入时同时加载;@import需要页面完全载入以后再加载。link是XHTML标签,无兼容问题;@import则是在CSS2.1提出的,低版本的浏览器不支持。link方式的样式的权重 高于@import的权重.link支持使用Javascript控制DOM改变样式;而@import不支持。本节参考文章:前端面试题-url、href、src10.异步编程的实现方式1.回调函数优点:简单、容易理解缺点:不利于维护,代码耦合高2.事件监听(采用时间驱动模式,取决于某个事件是否发生):优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数缺点:事件驱动型,流程不够清晰3.发布/订阅(观察者模式)类似于事件监听,但是可以通过‘消息中心’,了解现在有多少发布者,多少订阅者4.Promise对象优点:可以利用then方法,进行链式写法;可以书写错误时的回调函数;缺点:编写和理解,相对比较难5.Generator函数优点:函数体内外的数据交换、错误处理机制缺点:流程管理不方便6.async函数优点:内置执行器、更好的语义、更广的适用性、返回的是Promise、结构清晰。缺点:错误处理机制11.documen.write/ innerHTML的区别document.write只能重绘整个页面innerHTML可以重绘页面的一部分12.isPrototypeOf()/instanceofisPrototypeOf() 与 instanceof 运算符不同。在表达式 “object instanceof AFunction"中,object 的原型链是针对 AFunction.prototype 进行检查的,而不是针对 AFunction 本身。isPrototypeOf() 方法允许你检查一个对象是否存在于另一个对象的原型链上。function Foo() {}function Bar() {}function Baz() {}Bar.prototype = Object.create(Foo.prototype);Baz.prototype = Object.create(Bar.prototype);var baz = new Baz();//isPrototypeOfconsole.log(Baz.prototype.isPrototypeOf(baz)); // trueconsole.log(Bar.prototype.isPrototypeOf(baz)); // trueconsole.log(Foo.prototype.isPrototypeOf(baz)); // trueconsole.log(Object.prototype.isPrototypeOf(baz)); // trueif (Foo.prototype.isPrototypeOf(baz)) { // do something safe}//instanceofconsole.log(baz instanceof Baz); // trueconsole.log(baz instanceof Bar); // trueconsole.log(baz instanceof Foo); // trueconsole.log(baz instanceof Object); // truevar obj1 = { name: ’esw’}var obj2 = Object.create(obj1)// isPrototypeOf()方法Object.prototype.isPrototypeOf(obj1) // trueobj1.isPrototypeOf(obj2) // trueObject.prototype.isPrototypeOf(obj2) // true13.constructor、proto__与prototype在javascript中我们每创建一个对象,该对象都会获得一个__proto__属性(该属性是个对象),该属性指向创建该对象的构造函数的原型即prototype,同时__proto__对象有一个constructor属性指向该构造函数。这里我们需要注意的是只有函数才有prototype,每个对象(函数也是对象)都有__proto,Object本身是个构造函数。举例来说:var obj = new Object()// 也可以使用对象字面量创建,但使用Object.create()情况会不一样// Object本身是个构造函数Object instanceof Function // trueobj.proto === Object.prototype // trueobj.proto.constructor === Object // true// 我们一般习惯这样写obj.constructor === Object // true当我们访问obj.constructor的时候,obj本身是没有constructor属性的,但属性访问会沿着__proto__向上查找,即在obj.__proto__里面寻找constructor属性,如果找到了就返回值,如果未找到则继续向上查找直到obj.proto.proto…(proto) === null为止,没有找到则返回undefined。这样由__proto__构成的一条查找属性的线称为‘原型链’。本节参考文章:重新认识javascript对象(三)——原型及原型链、一篇文章带你进一步了解object属性14.浅拷贝/深拷贝1.浅拷贝只能复制值类型的属性。对于引用类型的属性,复制前后的两个对象指向同一内存地址,操作其中一个对象的引用类型属性,另一个对象也会相应发生改变;也就是说只有改变值类型的属性两个对象才不会相互影响。2.深拷贝不仅可以复制值类型的属性,还可以复制引用类型的属性,无论两个对象怎么改变都不会相互影响。浅复制var obj = { a : 1, b: { c: 2 }}// 浅复制function lowerClone(obj){ var newObj=obj.constructor === Array ? [] : {}; for(var i in obj){ newObj[i]=obj[i] } return newObj;}var objClone = lowerClone(obj)objClone.a // 1obj.a // 1objClone.a = 100// 改变复制对象的值类型属性,值类型属性的值不变obj.a // 1objClone.b.c = 200// 改变复制对象的引用类型的属性,引用类型的属性值改变obj.b.c //200深复制var obj = { a : 1, b: { c: 2 }}function deepClone(obj){ if( typeof obj != ‘object’){ return obj; } var newObj=obj.constructor === Array ? [] : {}; for(var i in obj){ newObj[i]=deepClone(obj[i]); } return newObj;}var objClone = deepClone(obj)objClone.a // 1obj.a // 1objClone.a = 100// 改变复制对象的值类型属性,值类型属性的值不变obj.a // 1objClone.b.c = 200// 改变复制对象的引用类型的属性,引用类型的属性值不变obj.b.c // 2本节参考文章:javascript浅复制与深复制15.apply/call/bindapply 、 call 、bind 三者都是用来改变函数的this对象的指向的; apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文; apply 、 call 、bind 三者都可以利用后续参数传参; bind是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。call apply 的区别是他们指定参数的方式不同。案例function fn(a,b){ console.log(this); console.log(a); console.log(b);}// bind(this,args…)bf = fn.bind(“Bind this”,10); // 没有任何输出,也就是说没有执行这个函数bf(); // “Bind this”,10,undefinedbf(20);// “Bind this”,10,20// 原函数不受影响fn(1,2); //window, 1,2bf2 = fn.bind(“Bind this”,1,2);bf2(); // “Bind this”,1,2// call(this,args…)fn.call(“Call this”,1) // “Call this”,1,undefinedfn.call(“Call this”,1,2) // “Call this”,1,2// apply(this,[args])fn.apply(“Apply this”,[1]) // “Apply this”,1,undefinedfn.apply(“Apply this”,[1,2]) // “Apply this”,1,2 ...

January 13, 2019 · 5 min · jiezi

前端基本功-示例代码(一)

1.ajaxvar xhr = new XMLHttpRequest(); // 声明一个请求对象// 前端设置是否带cookiexhr.withCredentials = true;xhr.open(‘GET’, ‘xxxx’);//xhr.open(‘post’, ‘http://www.domain2.com:8080/login', true);// 如何设置请求头? xhr.setRequestHeader(header, value);xhr.setRequestHeader(‘Content-Type’, ‘application/json’);xhr.onreadystatechange = function(){ if(xhr.readyState === 4){ // readyState 4 代表已向服务器发送请求 if(xhr.status === 200){ // status 200 代表服务器返回成功 console.log(xhr.responseText); // 这是返回的文本 } else{ console.log(“Error: “+ xhr.status); // 连接失败的时候抛出错误 } }}xhr.send(null); //xhr.send(‘user=admin’);// get方法 send null(亦或者不传,则直接是传递 header) ,post 的 send 则是传递值2.jsonp1.)原生实现: <script> var script = document.createElement(‘script’); script.type = ’text/javascript’; // 传参并指定回调执行函数为onBack script.src = ‘http://www.domain2.com:8080/login?user=admin&callback=onBack'; document.head.appendChild(script); // 回调执行函数 function onBack(res) { alert(JSON.stringify(res)); } </script>服务端返回如下(返回时即执行全局函数):onBack({“status”: true, “user”: “admin”})2.)jquery ajax:$.ajax({ url: ‘http://www.domain2.com:8080/login', type: ‘get’, dataType: ‘jsonp’, // 请求方式为jsonp jsonpCallback: “onBack”, // 自定义回调函数名 data: {}});3.)vue.js:this.$http.jsonp(‘http://www.domain2.com:8080/login', { params: {}, jsonp: ‘onBack’}).then((res) => { console.log(res); })4.)npm包jsonp:npm install jsonp –saveimport originJSONP from ‘jsonp’ //引入jsonp//进行封装并exportexport default function jsonp(url,data,option) { url += (url.indexOf(’?’)<0? ‘?’ : ‘&’)+param(data) return new Promise((resolve,reject)=>{ originJSONP(url,option,(err,data)=>{ if(!err){ resolve(data) }else{ reject(err) } }) })}//对data进行处理,并encodeURIComponent()进行转码。function param(data) { let url = ’’ for(var k in data) { let value = data[k] !== undefined? data[k] : ’’ url += ‘&’ + k + ‘=’ + encodeURIComponent(value) } return url ? url.substring(1) : ‘’}本节参考文章: vue项目中jsonp跨域获取qq音乐首页推荐3.实现一个简单的PromisePromise对象调用let p =new Promise(function(resolve, reject){ if(/* 异步操作成功 /){ resolve(data) }else{ reject(err) }})p.then((res)=>{ console.log(res)},(err)=>{ console.log(err)})实现一个简单的Promisefunction Promise(fn){ var status = ‘pending’ function successNotify(){ status = ‘fulfilled’//状态变为fulfilled toDoThen.apply(undefined, arguments)//执行回调 } function failNotify(){ status = ‘rejected’//状态变为rejected toDoThen.apply(undefined, arguments)//执行回调 } function toDoThen(){ setTimeout(()=>{ // 保证回调是异步执行的 if(status === ‘fulfilled’){ for(let i =0; i< successArray.length;i ++) { successArray[i].apply(undefined, arguments)//执行then里面的回掉函数 } }else if(status === ‘rejected’){ for(let i =0; i< failArray.length;i ++) { failArray[i].apply(undefined, arguments)//执行then里面的回掉函数 } } }) } var successArray = [] var failArray = [] fn.call(undefined, successNotify, failNotify) return { then: function(successFn, failFn){ successArray.push(successFn) failArray.push(failFn) return undefined // 此处应该返回一个Promise } }}解题思路:Promise中的resolve和reject用于改变Promise的状态和传参,then中的参数必须是作为回调执行的函数。因此,当Promise改变状态之后会调用回调函数,根据状态的不同选择需要执行的回调函数。本节参考文章:面向面试题和实际使用谈promise示例2const PENDING = “pending”; //等待const FULFILLED = “fulfilled”; //已完成const REJECTED = “rejected”; // 已拒绝function Promise(executor) { let self = this; self.status = PENDING; self.value; self.reason; function resolve(value) { if (self.status === PENDING) { self.status = FULFILLED; self.value = value; } } function reject(reason) { if (self.status === PENDING) { self.status = REJECTED; self.reason = reason; } } try { // 规范提到,执行器抛异常会reject executor(resolve, reject); } catch(e) { reject(e) }}// then方法实现Promise.prototype.then = function (onFulfilled, onRjected) { let self = this; /* * onFulfilled 和 onRejected 都是可选参数。 * 如果 onFulfilled 不是函数,其必须被忽略 * 如果 onRejected 不是函数,其必须被忽略 / onFulfilled = typeof onFulfilled === ‘function’ ? onFulfilled : function(value) { return value; }; onRjected = typeof onRjected === ‘function’ ? onRjected : function(reason) { throw reason; } if (self.status === FULFILLED) { onFulfilled(self.value); } if (self.status === REJECTED) { onRjected(self.reason); }}本节参考文章:Javascript Promise学习过程总结4.闭包var fn = function() { var divs = document.querySelectorAll(‘div’); for (var i = 0; i < 3; i++) { divs[i].onclick = (function(i) { return function() { alert(i); }; })(i); }};fn();或者如下的写法:var fn = function() { var divs = document.querySelectorAll(‘div’); for (var i = 0; i < 3; i++) { (function(i) { divs[i].onclick = function() { alert(i); }; })(i); }};fn();for (var i = 0; i < 3; i++) { setTimeout((function(i) { return function() { console.log(i); }; })(i), 0); console.log(i);}5.事件代理事件代理(Event Delegation),又称之为事件委托。是 JavaScript 中常用绑定事件的常用技巧。“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。减少事件注册,节省内存占用,提高性能可以实现当新增子对象时无需再次对其绑定 <div class=“wrap” id=“wrap”> <div class=“btn” data-type=“btn” data-feat=“add”>添加</div> <div class=“btn” data-type=“btn” data-feat=“delete”>绘画</div> <div class=“btn” data-type=“btn” data-feat=“delete”>散步</div> <div class=“btn” data-type=“btn” data-feat=“delete”>静坐</div> </div> <script type=“text/javascript”> var n = 0 document.getElementById(‘wrap’).addEventListener(‘click’, function(e) { var target = e.target; var type = target.dataset.type; var feat = target.dataset.feat; if (type == ‘btn’) { switch (feat) { case ‘add’: this.innerHTML += &lt;div class="btn" data-type="btn" data-feat="delete"&gt;静坐${n}&lt;/div&gt; n++ return; case ‘delete’: target.parentNode.removeChild(target); return; } } }, false);</script> 6.封装dom查询(面向对象)function Elem(id){ this.elem = document.getElementById(id)} Elem.prototype.html = function(val){ var elem = this.elem if(val) { elem.innerHTML = val return this //链式 } else { return elem.innerHTML } } Elem.prototype.on = function(type,fn){ var elem = this.elem elem.addEventListener(type, fn) return this //链式 }//调用var div = new Elem(‘id’)div.html(’<p>hello</p>’).on(‘click’,function(){ console.log(‘suceess’)})7.DOM劫持 function nodeToFragment (node) { var flag = document.createDocumentFragment(); var child; // 首先,所有表达式必然会返回一个值,赋值表达式亦不例外 // 理解了上面这一点,就能理解 while (child = node.firstChild) 这种用法 // 其次,appendChild 调用以后 child 会从原来 DOM 中移除 // 所以,第二次循环时,node.firstChild 已经不再是之前的第一个子元素了 while (child = node.firstChild) { flag.appendChild(child); // 将子节点劫持到文档片段中 } return flag }8.添加calssName// 为元素添加类名export function addClass(el, className) { // 先判断一下元素是否含有需要添加的类名,有则直接 return if(hasClass(el, className)) { return } // 把该元素含有的类名以空格分割 let newClass = el.className.split(’ ‘) // 把需要的类名 push 进来 newClass.push(className) // 最后以空格拼接 el.className = newClass.join(’ ‘)}// 判断是否有要查看的 className,有则返回true,否则返回 falseexport function hasClass(el, className) { let reg = new RegExp(’(^|\s)’ + className + ‘(\s|$)’) return reg.test(el.className)}9.自动添加游览器前缀let elementStyle = document.createElement(‘div’).style// 主流浏览器内核let vendor = (() => { let transfromNames = { webkit: ‘webkitTransform’, Moz: ‘MozTransform’, ms: ‘msTransform’, O: ‘OTransform’, standard: ’transform’ } for(let key in transfromNames) { if(elementStyle[transfromNames[key]] !== undefined) { return key } } return false})()// 添加样式的浏览器前缀export function prefixStyle(style) { if(vendor === false) { return false } if(vendor === ‘standard’) { return style } return vendor + style.charAt(0).toUpperCase() + style.substr(1)}10.图片懒加载定义:延迟加载也称为惰性加载,即在长网页中延迟加载图像。用户滚动到它们之前,视口外的图像不会加载。这与图像预加载相反,在长网页上使用延迟加载将使网页加载更快。在某些情况下,它还可以帮助减少服务器负载。<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title>Lazyload 1</title> <style> img { display: block; margin-bottom: 50px; height: 200px; } </style></head><body> <img src=“images/loading.gif” data-src=“images/1.png”> <img src=“images/loading.gif” data-src=“images/2.png”> <img src=“images/loading.gif” data-src=“images/3.png”> <img src=“images/loading.gif” data-src=“images/4.png”> <img src=“images/loading.gif” data-src=“images/5.png”> <img src=“images/loading.gif” data-src=“images/6.png”> <img src=“images/loading.gif” data-src=“images/7.png”> <img src=“images/loading.gif” data-src=“images/8.png”> <img src=“images/loading.gif” data-src=“images/9.png”> <img src=“images/loading.gif” data-src=“images/10.png”> <img src=“images/loading.gif” data-src=“images/11.png”> <img src=“images/loading.gif” data-src=“images/12.png”> <!– <script> var num = document.getElementsByTagName(‘img’).length; var img = document.getElementsByTagName(“img”); var n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历 lazyload(); //页面载入完毕加载可是区域内的图片 window.onscroll = lazyload; function lazyload() { //监听页面滚动事件 var seeHeight = document.documentElement.clientHeight; //可见区域高度 var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度 for (var i = n; i < num; i++) { if (img[i].offsetTop < seeHeight + scrollTop) { if (img[i].getAttribute(“src”) == “images/loading.gif”) { img[i].src = img[i].getAttribute(“data-src”); } n = i + 1; } } } </script>–> //对比一下上下两种代码,一个变量是全局变量,一个是函数的局部作用域, <script> function lazyload() { var images = document.getElementsByTagName(‘img’); var len = images.length; var n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历 return function() { var seeHeight = document.documentElement.clientHeight; var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; for(var i = n; i < len; i++) { if(images[i].offsetTop < seeHeight + scrollTop) { if(images[i].getAttribute(‘src’) === ‘images/loading.gif’) { images[i].src = images[i].getAttribute(‘data-src’); } n = n + 1; } } } } var loadImages = lazyload(); loadImages(); //初始化首页的页面图片 window.addEventListener(‘scroll’, loadImages, false);</script></body></html>jQuery<script> var n = 0, imgNum = $(“img”).length, img = $(‘img’); lazyload(); $(window).scroll(lazyload); function lazyload(event) { for (var i = n; i < imgNum; i++) { if (img.eq(i).offset().top < parseInt($(window).height()) + parseInt($(window).scrollTop())) { if (img.eq(i).attr(“src”) == “default.jpg”) { var src = img.eq(i).attr(“data-src”); img.eq(i).attr(“src”, src); n = i + 1; } } } }</script>11.img加载图片失败时,使用默认图片img标签自带onError属性,当图片加载失败时,触发error事件:<img src=“image.png” onError=‘this.src=“http://ww.jpg”’ />jQuery的error事件$(‘img’).error(function(){ $(this).attr(‘src’,“http://ww.jpg”);});jQuery的one绑定使用onerror或者jQuery的error事件时,如果默认图片也发生加载失败,则会形成死循环,最好的办法是使用one绑定事件,只执行一次$(“img”).one(“error”, function(e){ $(this).attr(“src”, “http://ww.jpg”);});12.图片按比例响应式缩放、裁剪html部分:<div class=“zoomImage” style=“background-image:url(images/test1.jpg)"></div>css部分:.zoomImage{ width:100%; height:0; padding-bottom: 100%; overflow:hidden; //padding为百分比的时候根据他父层的宽度来进行计算 background-position: center center; background-repeat: no-repeat; -webkit-background-size:cover; -moz-background-size:cover; //把背景图像扩展至完全覆盖背景区域 background-size:cover;}总结:你所需要的比例,就是width与padding-bottom的比例 用的时候,直接把.zoomImage当成img标签来用就可以了。思维扩展很多轮播的插件本来是响应式的, 但可能有两个问题: 1.这个轮播图你必须要给他一个高度,但高度不是固定死的,是需要按比例的…2.轮播图里的图片不是需要的比例…所以我们可以用刚刚上面的padding方法拿swiper轮播图插件举例优化前优化后本节详细:如何让图片按比例响应式缩放、并自动裁剪的css技巧13.选项卡切换html的结构和样式:<style type=“text/css”> #div1 div{ width: 200px; height:200px; border: 1px #000 solid; display: none; } .active{ background: red; }</style><body> <div id=“div1”> <button class=“active”>1</button> <button>2</button> <button>3</button> <div style=“display: block;">111</div> <div>222</div> <div>333</div> </div></body>//过程式的编程思想window.onload=function(){ //获取元素 var oParent=document.getElementById(‘div1’); var btns=oParent.getElementsByTagName(‘button’); var divs=oParent.getElementsByTagName(‘div’); //通过循环给每个btn添加点击事件 for (var i = 0; i < btns.length; i++) { btns[i].index=i;//存储当前btn的下标 btns[i].onclick=function(){ for (var i = 0; i < btns.length; i++) { btns[i].className=’’; divs[i].style.display=‘none’; } this.className=‘active’; divs[this.index].style.display=‘block’;//让对应当前btn的div显示 } }}//面向对象window.onload = function(){ var t1=new Tab(); t1.init();}; function Tab() { this.btns=oParent.getElementsByTagName(‘button’); this.divs=oParent.getElementsByTagName(‘div’); } Tab.prototype.init=function(){ var This=this; for (var i = 0; i < this.btns.length; i++) { this.btns[i].index=i; this.btns[i].onclick=function(){ This.change(this); } }}Tab.prototype.change=function(btn) { for (var i = 0; i < this.btns.length; i++) { this.btns[i].className=’’; this.divs[i].style.display=‘none’; } btn.className=‘active’; this.divs[btn.index].style.display=‘block’;};14.元素拖拽#div1{ width: 100px; height: 100px; background: red; position: absolute;}<body> <div id=‘div1’></div></body>//过程式的编程思想window.onload=function(){ var oDiv=document.getElementById(‘div1’); var disX=0; var disY=0; oDiv.onmousedown=function(ev){ var ev=ev || window.event; disX=ev.clientX-oDiv.offsetLeft; disY=ev.clientY-oDiv.offsetTop; document.onmousemove=function(ev){ var ev=ev || window.event; oDiv.style.left=ev.clientX-disX+‘px’; oDiv.style.top=ev.clientY-disY+‘px’; }; document.onmouseup=function(){ document.onmousemove=null; document.onmouseup=null; } return false; }}//面向对象window.onload = function() { var d1 = new Drag(‘div1’); d1.init();};function Drag(id) { this.oDiv = document.getElementById(id); this.disX = 0; this.disY = 0;}Drag.prototype.init = function() { var This = this; this.oDiv.onmousedown = function(ev) { var ev = ev || window.event; This.fnDown(ev); return false; };};Drag.prototype.fnDown = function(ev) { var This = this; this.disX = ev.clientX - this.oDiv.offsetLeft; this.disY = ev.clientY - this.oDiv.offsetTop; document.onmousemove = function(ev) { var ev = ev || window.event; This.fnMove(ev); }; document.onmouseup = function() { This.fnUp(); }};Drag.prototype.fnMove = function(ev) { this.oDiv.style.left = ev.clientX - this.disX + ‘px’; this.oDiv.style.top = ev.clientY - this.disY + ‘px’;};Drag.prototype.fnUp = function() { document.onmousemove = null; document.onmouseup = null;};15.函数节流(throttle)//fn 要执行的函数//delay 延迟//atleast 在time时间内必须执行一次function throttle(fn, delay, atleast) { var timeout = null, startTime = new Date(); return function() { var curTime = new Date(); clearTimeout(timeout); // 如果达到了规定的触发时间间隔,触发 handler if(curTime - startTime >= atleast) { fn(); startTime = curTime; }else { // 没达到触发间隔,重新设定定时器 timeout = setTimeout(fn, delay); } }} // 实际想绑定在 scroll 事件上的 handlerfunction lazyload(event) { console.log(‘触发了’)}// 采用了节流函数window.addEventListener(‘scroll’,throttle(lazyload,500,1000));16.函数去抖(debounce)// debounce函数用来包裹我们的事件function debounce(fn, delay) { // 持久化一个定时器 timer let timer = null; return function() { // 如果事件被触发,清除 timer 并重新开始计时 clearTimeout(timer); timer = setTimeout(function() { fn(); }, delay); }}// 实际想绑定在 scroll 事件上的 handlerfunction lazyload(event) { console.log(‘触发了’)}// 采用了去抖函数window.addEventListener(‘scroll’,debounce(lazyload,500));17.分时函数如果一次获得了很多数据(比如有10W数据),然后在前端渲染的时候会卡到爆,所以在处理这么多数据的时候,我们可以选择分批进行。function timeChunk(data, fn, count = 1, wait) { let obj, timer; function start() { let len = Math.min(count, data.length); for (let i = 0; i < len; i++) { val = data.shift(); // 每次取出一个数据,传给fn当做值来用 fn(val); } } return function() { timer = setInterval(function() { if (data.length === 0) { // 如果数据为空了,就清空定时器 return clearInterval(timer); } start(); }, wait); // 分批执行的时间间隔 }}// 测试用例let arr = [];for (let i = 0; i < 100000; i++) { // 这里跑了10万数据 arr.push(i);}let render = timeChunk(arr, function(n) { // n为data.shift()取到的数据 let div = document.createElement(‘div’); div.innerHTML = n; document.body.appendChild(div);}, 8, 20);render();参考文章:高阶函数,你怎么那么漂亮呢!18.惰性载入函数假如你要写一个函数,里面有一些判断语句function foo(){ if(a != b){ console.log(‘aaa’) }else{ console.log(‘bbb’) }}如果你的a和b是不变的,那么这个函数不论执行多少次,结果都是不变的,但是每次执行还要进行if判断,这就造成了不必要的浪费。惰性载入表示函数执行的分支只会发生一次,这里有两种解决方式。// 常见的例子if (window.addEventListener) { ele.addEventListener(type, fn, false);} else if (window.attachEvent) { ele.attachEvent(‘on’ + type, fn);}在函数被调用时再处理函数function foo(){ if(a != b){ foo = function(){ console.log(‘aaa’) } }else{ foo = function(){ console.log(‘bbb’) } } return foo();}这样进入每个分支后都会对foo进行赋值,覆盖了之前的函数,之后每次调用foo就不会再执行if判断在声明函数时就指定适当的函数var foo = (function foo(){ if(a != b){ return function(){ console.log(‘aaa’) } }else{ return function(){ console.log(‘bbb’) } }})();本节参考文章:JS高级技巧(简洁版)19.实现once函数function test(){ alert(‘hello’);}var once = function (fn) { var isFirst = true; return function () { if (isFirst) { isFirst = !isFirst; fn(); } };};once(test);once(test);20.requirejs架子require.js的诞生,就是为了解决这两个问题: 实现js文件的异步加载,避免网页失去响应;管理模块之间的依赖性,便于代码的编写和维护。/* 网页中引入require.js及main.js /<script src=“js/require.js” data-main=“js/main”></script>/ main.js 入口文件/主模块 **/// 首先用config()指定各模块路径和引用名require.config({ baseUrl: “js/lib”, paths: { “jquery”: “jquery.min”, //实际路径为js/lib/jquery.min.js “underscore”: “underscore.min”, }});// 执行基本操作require([“jquery”,“underscore”],function($,){ // some code here});引用模块的时候,我们将模块名放在[]中作为reqiure()的第一参数;如果我们定义的模块本身也依赖其他模块,那就需要将它们放在[]中作为define()的第一参数。// 定义math.js模块define(function () { var basicNum = 0; var add = function (x, y) { return x + y; }; return { add: add, basicNum :basicNum };});// 定义一个依赖underscore.js的模块define([‘underscore’],function(){ var classify = function(list){ .countBy(list,function(num){ return num > 30 ? ‘old’ : ‘young’; }) }; return { classify :classify };})// 引用模块,将模块放在[]内require([‘jquery’, ‘math’],function($, math){ var sum = math.add(10,20); $("#sum”).html(sum);});加载非规范的模块理论上,require.js加载的模块,必须是按照AMD规范、用define()函数定义的模块。但是实际上,虽然已经有一部分流行的函数库(比如jQuery)符合AMD规范,更多的库并不符合。那么,require.js是否能够加载非规范的模块呢?这样的模块在用require()加载之前,要先用require.config()方法,定义它们的一些特征。举例来说,underscore和backbone这两个库,都没有采用AMD规范编写。如果要加载它们的话,必须先定义它们的特征。require.config({ shim: { ‘underscore’:{ exports: ‘’ }, ‘backbone’: { deps: [‘underscore’, ‘jquery’], exports: ‘Backbone’ } }});require.config()接受一个配置对象,这个对象除了有前面说过的paths属性之外,还有一个shim属性,专门用来配置不兼容的模块。具体来说,每个模块要定义(1)exports值(输出的变量名),表明这个模块外部调用时的名称;(2)deps数组,表明该模块的依赖性。比如,jQuery的插件可以这样定义:shim: { ‘jquery.scroll’: { deps: [‘jquery’], exports: ‘jQuery.fn.scroll’ }}require.js插件require.js还提供一系列插件,实现一些特定的功能。domready插件,可以让回调函数在页面DOM结构加载完成后再运行。require([‘domready!’], function (doc){ // called once the DOM is ready}); text和image插件,则是允许require.js加载文本和图片文件。define([ ’text!review.txt’, ‘image!cat.jpg’ ], function(review,cat){ console.log(review); document.body.appendChild(cat); } ); 类似的插件还有json和mdown,用于加载json文件和markdown文件。本节参考文章:require.js的用法 ...

January 13, 2019 · 8 min · jiezi

前端常用代码片段(四)

前端常用代码片段(一) 点这里前端常用代码片段(二) 点这里前端常用代码片段(三) 点这里前端常用代码片段(四) 点这里前端常用代码片段(五) 点这里前端常用代码片段(六) 点这里1.简述一下你对HTML语义化的理解?并写出一段语义化的HTML?语义化是指根据内容的结构化(内容语义化),选择合适的标签(代码语义化),便于开发者阅读和写出更优雅的代码的同时,让浏览器的爬虫和机器很好的解析。用正确的标签做正确的事情html语义化让页面的内容结构化,结构更清晰,便于对浏览器、搜索引擎解析语义化的HTML在没有CSS的情况下也能呈现较好的内容结构与代码结构搜索引擎的爬虫也依赖于HTML标记来确定上下文和各个关键字的权重,利于SEO;使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解HTML5增加了许多语义化标签如:header footer nav article ……语义化HTML示例:<!– 这是开放的 –><header> <h1>header</h1></header><section class=“main”> main</section><aside>aside</aside><footer> footer</footer>2. HTML5有哪些新特性、移除了那些元素?如何处理HTML5新标签的浏览器兼容问题?如何区分 HTML 和 HTML5?HTML5 是定义 HTML 标准的最新的版本。 该术语表示两个不同的概念:它是一个新版本的HTML语言,具有新的元素,属性和行为,它有更大的技术集,允许更多样化和强大的网站和应用程序。这个集合有时称为HTML5和朋友,通常缩写为HTML5。HTML5新特性:HTML5 现在已经不是 SGML 的子集,主要是关于图像,位置,存储,多任务等功能的增加绘画 canvas;用于媒介回放的 video 和 audio 元素;本地离线存储 localStorage 长期存储数据,浏览器关闭后数据不丢失sessionStorage 的数据在浏览器关闭后自动删除语意化更好的内容元素,比如 article、footer、header、nav、section表单控件(input type),calendar、date、time、email、url、search新的技术webworker, websocket, Geolocation移除元素:纯表现的元素basefont ,big,center,font, s,strike,tt,u对可用性产生负面影响的元素:frame,frameset,noframes处理HTML5新标签的浏览器兼容问题:IE8/IE7/IE6支持通过document.createElement方法产生的标签,可以利用这一特性让这些浏览器支持HTML5新标签,浏览器支持新标签后,还需要添加标签默认的样式。可直接使用成熟的框架、比如html5shiv<!–[if lt IE 9]> <script src=“html5shiv.js”></script><![endif]–>如何区分 HTML 和 HTML5:DOCTYPE声明新增元素3. 为什么要初始化CSS样式(reset css)?因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面显示差异最简单粗暴的* { margin: 0; padding: 0;}更好的选择Normalize.css 相比于传统的CSS reset,Normalize.css是一种现代的、为HTML5准备的优质替代方案Reset CSS:只选对的,不选"贵"的,因根据具体项目来做选择权衡,不应该滥用css定义的权重?页面显示样式的优先级取决于其“特殊性”’,特殊性越高,就显示最高的,当特殊性相等时,显示后者特殊性表述为4个部分:0,0,0,0一个选择器的特殊性如下确定:对于选择器是#id的属性值,特殊性值为:0,1,0,0对于属性选择器,class或伪类,特殊性值为:0,0,1,0对于标签选择器或伪元素,特殊性值为:0,0,0,1通配符‘’对特殊性值为:0,0,0,0内联样式特殊性值为:1,0,0,04. 讲讲position的值relative和absolute的区别?absolute:生成绝对定位的元素,相对于值不为 static的第一个父元素进行定位relative:生成相对定位的元素,相对于其正常位置进行定位5. 如何水平垂直居中div?(至少给出2种解决方法)1.absolute + transform:<div class=“parent”> <div class=“child”>Demo</div></div><style> .parent { position: relative; } .child { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }</style>2.inline-block + text-align + table-cell + vertical-align<div class=“parent”> <div class=“child”>Demo</div></div><style> .parent { text-align: center; display: table-cell; vertical-align: middle; } .child { display: inline-block; }</style>3.flex + justify-content + align-items<div class=“parent”> <div class=“child”>Demo</div></div><style> .parent { display: flex; justify-content: center; / 水平居中 */ align-items: center; /垂直居中/ }</style>6. 渐进增强 VS 优雅降级,你怎么看?渐进增强(Progressive Enhancement):一开始就针对低版本浏览器进行构建页面,完成基本的功能,然后再针对高级浏览器进行效果、交互、追加功能达到更好的体验优雅降级(Graceful Degradation):一开始就构建站点的完整功能,然后针对浏览器测试和修复。比如一开始使用 CSS3 的特性构建了一个应用,然后逐步针对各大浏览器进行 hack 使其可以在低版本浏览器上正常浏览7. JavaScript 数组去重?(简述思路即可)遍历数组法: 这应该是最简单的去重方法(实现思路:新建一新数组,遍历数组,值不在新数组就加入该新数组中)// 遍历数组去重法function unique(arr){ var _arr = [] //遍历当前数组 for(var i = 0; i < arr.length; i++){ //如果当前数组的第i已经保存进了临时数组,那么跳过, //否则把当前项push到临时数组里面 if (_arr.indexOf(arr[i]) == -1) _arr.push(arr[i]) } return _arr}注意点:indexOf 为 ES5 的方法,注意浏览器兼容,需要自己实现 indexOf对象键值对(hash) 法:速度快,高效,占用更大的内存换取更快的时间,用 JavaScript 中的 Object 对象来当做哈希表,hash去重的核心是构建了一个 hash 对象来替代 indexOf// hash 去重法function unique(arr){ var _arr = [], hash = {} for (var i = 0; i < arr.length; i++) { var item = arr[i] var key = typeof(item) + item // 对象的键值只能是字符串, typeof(item) + item来去分1和'1’的情况 if(hash[key] !== 1){ _arr.push(item) hash[key] = 1 } } return _arr}炫酷的 es6 Set数据结构: ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值function unique(arr){ return Array.from(new Set(arr)) // Array.from方法用于将两类对象转为真正的数组: // 类似数组的对象(array-like object)和可遍历(iterable)的对象}8. 使用原生ajax获取 Linus Torvalds 的GitHub信息(API:api.github.com/users/torva…),并将JSON字符串解析为JSON对象,并讲讲对JSON的了解这是对 ajax与json的考察ajax的全称:Asynchronous Javascript And XML,异步传输+js+xml 现在差不多都用JSON创建XMLHttpRequest对象,也就是创建一个异步调用对象创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息设置响应HTTP请求状态变化的函数发送HTTP请求获取异步调用返回的数据数据处理下面就来贴代码吧:var api = ‘https://api.github.com/users/torvalds'var xhr = new XMLHttpRequest() // 创建XMLHttpRequest对象if(window.XMLHttpRequest){ // 兼容处理 xhr = new XMLHttpRequest()}else{ xhr = new ActiveXObject(‘Microsoft.XMLHTTP’)// 兼容ie6以下下}xhr.open(‘get’,api,true) //设置请求信息 xhr.send() //提交请求//等待服务器返回内容xhr.onreadystatechange = function() { if ( xhr.readyState == 4 && xhr.status == 200 ) { console.log(JSON.parse(xhr.responseText)) // 使用JSON.parse解析JSON字符串 } }JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小 如:{“age”:“12”, “name”:“back”}JSON.parse() 方法解析一个JSON字符串JSON.stringify() 方法将一个JavaScript值转换为一个JSON字符串9. 简单谈谈前端性能优化减少http请求次数:CSS Sprites, JS、CSS源码压缩、图片大小控制合适;网页Gzip,CDN托管,data缓存 ,图片服务器。减少DOM操作次数,优化javascript性能。少用全局变量、缓存DOM节点查找的结果。减少IO读取操作。尽量避免使用CSS Expression(css表达式)又称Dynamic properties(动态属性)。图片预加载,将样式表放在顶部,将脚本放在底部。10. 费波纳茨数组就是当前项等于前两项的和var arr=[];for(var i=0;i<10;i++ ){ i<=1?arr.push(1):arr.push(arr[i-1]+arr[i-2]);}console.log(arr)11.数据排列执行num(1,5),返回'123454321’执行num(2,5),返回'23456765432’方法1:var num = function(n,m){ var arr = [] var len=(m-n)*2+1 for(var i=0;i<len;i++){ n<m?(arr.push(n++)):(arr.push(m–)) } return arr.join()}num(2,5)方法2:var num = function (n,m) { let arr = [m] for(let i = m - 1; i >= n; i–){ arr.push(i); arr.unshift(i) } return arr.join()}num(2,5)12.翻转一个字符串let a=“hello word”;let b=[…str].reverse().join("");//drow olleh13.setInterval 时间是否会有误差?产生误差的原因?其原理是什么?setInterval异步函数,异步执行,js被解析的时候,碰到他,先不解析他,放他在一旁,先去解析同步的,等资源空闲下来的才去解析他,这样一来,解析其他代码肯定需要时间,这不就有延误嘛。然后解析setInterval内部函数不也一样需要耗时,函数简单些还好写,你要是写了一大堆,可能产生的延误就不是一点点的;14.布局方式弹性布局固定布局流体布局混合布局绝对定位布局15.清除浮动的方式:父级div定义height最后一个浮动元素后加空div标签 并添加样式clear:both。包含浮动元素的父标签添加样式overflow为hidden或auto。父级div定义zoom16.怎么判断两个对象相等?obj={ a:1, b:2}obj2={ a:1, b:2}obj3={ a:1, b:2}JSON.stringify(obj)==JSON.stringify(obj2);//trueJSON.stringify(obj)==JSON.stringify(obj3);//false17.ES6强制参数ES6提供了默认参数的概念,当函数的参数未传入或者传入值为 undefined 时,会应用参数的默认值。默认值可以是个表达式,所以我们可以将默认值设置为一个执行函数,如果该参数没有传值,就会执行我们的默认函数:const required = () => {throw new Error(‘Missing parameter’)};//The below function will throw an error if either “a” or “b” is missing.const add = (a = required(), b = required()) => a + b;add(1, 2) //3add(1) // Error: Missing parameter.18. 强大的 reduce之前只是用过reduce做过数组求和,现在发现一些新的用法,原来 reduce 这么强大。基础部分reduce()方法接收一个函数callbackfn作为累加器(accumulator),数组中的每个值(从左到右)开始合并,最终为一个值。语法array.reduce(callbackfn,[initialValue])reduce()方法接收callbackfn函数,而这个函数包含四个参数:function callbackfn(preValue,curValue,index,array){}preValue: 上一次调用回调返回的值,或者是提供的初始值(initialValue)curValue: 数组中当前被处理的数组项index: 当前数组项在数组中的索引值array: 调用 reduce()方法的数组而initialValue作为第一次调用 callbackfn函数的第一个参数。1.没有initialValue初始值得情况var arr = [0,1,2,3,4];arr.reduce(function(preValue,curValue,index,array){ return preValue + curValue; }); // 10示例中的回调函数被执行四次,每次参数和返回的值如下:2.有initialValue初始值得情况var arr = [0,1,2,3,4];arr.reduce(function (preValue,curValue,index,array) { return preValue + curValue;}, 5); //15reduce()方法会执行五次回调,每次参数和返回的值如下:基础部分截取自 大漠 - JavaScript学习笔记… 全部内容可点击链接查看实例部分1.使用 reduce 替代 map + filter设想你有这么个需求:要把数组中的值进行计算后再滤掉一些值,然后输出新数组。很显然我们一般使用 map 和 filter 方法组合来达到这个目的,但这也意味着你需要迭代这个数组两次。来看看我们如何使用 reduce 只迭代数组一次,来完成同样的结果。下面这个例子我们需要把数组中的值乘 2 ,并返回大于 50 的值:const numbers = [10, 20, 30, 40];const doubledOver50 = numbers.reduce((finalList, num) => { num = num * 2; //double each number (i.e. map) //filter number > 50 if (num > 50) { finalList.push(num); } return finalList;}, []);doubledOver50; // [60, 80]2.使用 reduce 检测括号是否对齐封闭下面这个例子我们用 reduce 来检测一段 string 中的括号是否前后对应封闭。思路是定义一个名为 counter 的变量,它的初始值为 0 ,然后迭代字符串,迭代过程中碰到(就加 1,碰到)就减 1,如果括号前后对应的话,最终couter的值会是 0。//Returns 0 if balanced.const isParensBalanced = (str) => { return str.split(’’).reduce((counter, char) => { if(counter < 0) { //matched “)” before “(” return counter; } else if(char === ‘(’) { return ++counter; } else if(char === ‘)’) { return –counter; } else { //matched some other char return counter; } }, 0); //<– starting value of the counter}isParensBalanced(’(())’) // 0 <– balancedisParensBalanced(’(asdfds)’) //0 <– balancedisParensBalanced(’(()’) // 1 <– not balancedisParensBalanced(’)(’) // -1 <– not balanced3.使用 reduce 计算数组中的重复项如果你想计算数组中的每个值有多少重复值,reduce 也可以快速帮到你。下面的例子我们计算数组中每个值的重复数量,并输出一个对象来展示:var carsObj = cars.reduce(function (obj, name) { obj[name] = obj[name] ? ++obj[name] : 1; return obj;}, {});carsObj; // => { BMW: 2, Benz: 2, Tesla: 1, Toyota: 1 }实例部分截取自 ES6 的几个小技巧 全部内容可点击链接查看19.用对象解构移除一个对象中的某些属性有时你可能希望移除一个对象中的某些属性,我们一般会通过迭代这个对象(如 for..in 循环)来移除那些我们不想要的属性。实际上我们可以通过对象解构的方法将不想要的属性提取出来,并将想留下来的变量保存在rest 参数中。在下面的这个例子中,我们从对象中移除_internal和tooBig这两个属性:let {_internal, tooBig, …cleanObject} = { el1: ‘1’, el2: ‘2’, el3: ‘3’, tooBig:{}, _internal:“secret”};console.log(cleanObject); // {el1: ‘1’, el2: ‘2’, el3: ‘3’}拓展:1.嵌套对象解构let {model, engine: {vin,…uuu} } = { model: ‘bmw 2018’, engine: { v6: true, turbo: true, vin: 12345 }}console.log(uuu); // {v6: true, turbo: true}console.log(vin); // 12345console.log(model); // ‘bmw 2018’console.log(engine); // Uncaught ReferenceError: engine is not defined2.合并对象合并两个对象,新对象中相同的属性会被放在后面的对象覆盖:let object1 = { a:1, b:2,c:3 }let object2 = { b:30, c:40, d:50}let merged = {…object1, …object2} //spread and re-add into mergedconsole.log(merged) // {a:1, b:30, c:40, d:50}20.判断一个数是否是整数function isInteger(x) { return (x ^ 0) === x; } function isIntefer(x){ return (typeof x === ’number’) && (x % 1 === 0); //返回布尔}参考文章:1.12个常规前端面试题及小结2.ES6 的几个小技巧 ...

January 13, 2019 · 4 min · jiezi

前端常用代码片段(六)

本文总结的代码片段(六)–持续更新前端常用代码片段(一) 点这里前端常用代码片段(二) 点这里前端常用代码片段(三) 点这里前端常用代码片段(四) 点这里前端常用代码片段(五) 点这里前端常用代码片段(六) 点这里1.多彩的console.logconsole.log( ‘Nothing here %cHi Cat %cHey Bear’, // Console Message ‘color: blue’, ‘color: red’ // CSS Style);const styles = [‘color: green’, ‘background: yellow’].join(’;’);const message = ‘Some Important Message Here’;// 3. 传入styles和message变量console.log(’%c%s’, styles, message);本节参考文章:多彩的console.log2. 版本号比较9_11_1和9_2_910.11.111和10.2.2function version( v1, v2 ) { var arr1 = v1.replace(/[-]/g,’.’).split(’.’); var arr2 = v2.replace(/[-]/g,’.’).split(’.’); console.log(arr1,arr2); var len = Math.max(arr1.length, arr2.length); for ( var i = 0; i < len; i++ ) { if(parseInt(arr1[i]) == parseInt(arr2[i])) continue; return parseInt(arr1[i]) < parseInt(arr2[i]) ? true :false; } return false;}本节参考文章:如何比较版本号大小 ...

January 13, 2019 · 1 min · jiezi

【剑指offer】4.二叉树的遍历和重建

二叉树简介基本结构:function TreeNode(x) { this.val = x; this.left = null; this.right = null;}二叉树的前序、中序、后序遍历的定义:前序遍历:对任一子树,先访问跟,然后遍历其左子树,最后遍历其右子树;中序遍历:对任一子树,先遍历其左子树,然后访问根,最后遍历其右子树;后序遍历:对任一子树,先遍历其左子树,然后遍历其右子树,最后访问根。题目1 二叉树遍历1.1 题目描述给定一棵二叉树的前序遍历和中序遍历,求其后序遍历输入描述:两个字符串,其长度n均小于等于26。第一行为前序遍历,第二行为中序遍历。二叉树中的结点名称以大写字母表示:A,B,C….最多26个结点。输出描述:输入样例可能有多组,对于每组测试样例,输出一行,为后序遍历的字符串。样例:输入ABCBACFDXEAGXDEFAG输出BCAXEDGAF1.2 解题思路前序遍历:跟节点 + 左子树前序遍历 + 右子树前序遍历中序遍历:左子树中序遍历 + 跟节点 + 右字数中序遍历后序遍历:左子树后序遍历 + 右子树后序遍历 + 跟节点1.前序遍历的头部为跟节点2.中序遍历以跟节点分割,左侧为左子中序遍历,右侧为右子树中序遍历3.根据中序遍历得到的左子树右子树的长度,得到左子树的前序遍历和右子树的前序遍历1.3 代码let pre;let vin; while((pre = readline())!=null){ vin = readline(); print(getHRD(pre,vin));} function getHRD(pre, vin) { if (!pre) { return ‘’; } if (pre.length === 1) { return pre; } const head = pre[0]; const splitIndex = vin.indexOf(head); const vinLeft = vin.substring(0, splitIndex); const vinRight = vin.substring(splitIndex + 1); const preLeft = pre.substring(1, splitIndex + 1); const preRight = pre.substring(splitIndex + 1); return getHRD(preLeft, vinLeft) + getHRD(preRight, vinRight) + head;题目2 二叉树重建2.1 题目描述输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。2.2 解题思路思路和题目1相似。根据前序遍历和中序遍历的结果可以拿到:左子中序遍历和右侧为右子树中序遍历左子树的前序遍历和右子树的前序遍历然后递归左子树和右子树的完成重建。2.3 代码 function reConstructBinaryTree(pre, vin) { if(pre.length === 0){ return null; } if(pre.length === 1){ return new TreeNode(pre[0]); } const value = pre[0]; const index = vin.indexOf(value); const vinLeft = vin.slice(0,index); const vinRight = vin.slice(index+1); const preLeft = pre.slice(1,index+1); const preRight = pre.slice(index+1); const node = new TreeNode(value); node.left = reConstructBinaryTree(preLeft, vinLeft); node.right = reConstructBinaryTree(preRight, vinRight); return node; } ...

January 13, 2019 · 1 min · jiezi

三年半Java后端面试经历

经过半年的沉淀,加上对MySQL,redis和分布式这块的补齐,终于开始重拾信心去投了两家之前心水已久的公司。鹅厂面试职位:go后端开发工程师,接受从Java转语言 都知道鹅厂是cpp的主战场,而以cpp为背景的工程师大都对os,network这块要求特别高,不像是Java这种偏重业务层的语言,之前面试Java的公司侧重还是在数据结构、网络、框架、数据库和分布式。所以OS这块吃的亏比较大一面基础技术面电话面试,随便问了些技术问题,最后还问了个LeetCode里面medium级别的算法题,偏简单redis有没有用过,常用的数据结构以及在业务中使用的场景,redis的hash怎么实现的,rehash过程讲一下和JavaHashMap的rehash有什么区别?redis cluster有没有了解过,怎么做到高可用的?redis的持久化机制,为啥不能用redis做专门的持久化数据库存储?了不了解tcp/udp,说下两者的定义,tcp为什么要三次握手和四次挥手?tcp怎么保证有序传输的,讲下tcp的快速重传和拥塞机制,知不知道time_wait状态,这个状态出现在什么地方,有什么用?(参考quic)知道udp是不可靠的传输,如果你来设计一个基于udp差不多可靠的算法,怎么设计?看你项目里面用了etcd,讲解下etcd干什么用的,怎么保证高可用和一致性?既然你提到了raft算法,讲下raft算法的基本流程?raft算法里面如果出现脑裂怎么处理?有没有了解过paxos和zookeeper的zab算法,他们之前有啥区别?你们后端用什么数据库做持久化的?有没有用到分库分表,怎么做的?索引的常见实现方式有哪些,有哪些区别?MySQL的存储引擎有哪些,有哪些区别?InnoDB使用的是什么方式实现索引,怎么实现的?说下聚簇索引和非聚簇索引的区别?有没有了解过协程?说下协程和线程的区别?算法题一个,剑指offer第51题,数组中的重复数字?自己的回答情况,redis这块没啥问题,具体rehash有印象是渐进式的,但是具体原理可能答的有点出入。tcp的time_wait这块答的不是很好,之前没有了解过quic机制的实现,所以问可靠性udp的时候,基本上脑子里就照着tcp的实现在说。raft算法这个因为刚好在刷6.824(才刷到lab2。。。),答的也凑合,不过paxos确实不熟悉,直接说不会。MySQL这块很熟了,没啥说的,协程和线程,主要说了go程和Java线程的区别以及go程的调度模型。面试官提示没有提到线程的有内核态的切换,go程只在用户态调度。最后一个算法题,首先说使用HashMap来做,说空间复杂度能不能降到O(1),后面想了大概5min才想出来原地置换的思路。二面项目技术面主要针对自己最熟悉的项目,画出项目的架构图,主要的数据表结构,项目中使用到的技术点,项目的总峰值qps,时延,以及有没有分析过时延出现的耗时分别出现在什么地方,项目有啥改进的地方没有?如果请求出现问题没有响应,如何定位问题,说下思路?tcp 粘包问题怎么处理?问了下缓存更新的模式,以及会出现的问题和应对思路?除了公司项目之外,业务有没有研究过知名项目或做出过贡献?基本都没有啥问题,除了面试官说项目经验稍弱之外,其余还不错。三面综合技术面这面面的是阵脚大乱,面试官采用刨根问底的方式提问,终究是面试经验不够,导致面试的节奏有点乱。 举个例子:其中有个题是go程和线程有什么区别?答:1 起一个go程大概只需要4kb的内存,起一个Java线程需要1.5MB的内存;go程的调度在用户态非常轻量,Java线程的切换成本比较高。接着问为啥成本比较高?因为Java线程的调度需要在用户态和内核态切换所以成本高?为啥在用户态和内核态之间切换调度成本比较高?简单说了下内核态和用户态的定义。接着问,还是没有明白为啥成本高?心里瞬间崩溃,没完没了了呀,OS这块依旧是痛呀,支支吾吾半天放弃了。后面等等的面试都是这种情况,结果就是回答的节奏全无,差不多都是上面这种形式,基本都能达到一点,但是深入的OS层面就GG了。 后面问了下项目过程中遇到的最大的挑战,以及时怎么解决的? 后面还问了一个问题定位的问题,服务器CPU 100%怎么定位?可能是由于平时定位业务问题的思维定势,加之处于蒙蔽状态,随口就是:先查看监控面板看有无突发流量异常,接着查看业务日志是否有异常,针对针对100%那个时间段,取一个典型业务流程的日志查看。最后才提到使用top命令来监控看是哪个进程占用到100%。果然阵脚大乱,张口就来,捂脸。。。 本来正确的思路应该是先用top定位出问题的进程,再用top定位到出问题的线程,再打印线程堆栈查看运行情况,这个流程换平时肯定能答出来,但是,但是没有但是。还是得好好总结。最后问了一个系统设计题目(朋友圈的设计),白板上面画出系统的架构图,主要的表结构和讲解主要的业务流程,如果用户变多流量变大,架构将怎么扩展,怎样应对?这个答的也有点乱,直接上来自顾自的用了一个通用的架构,感觉毫无亮点。后面反思应该先定位业务的特点,这个业务明显是读多写少,然后和面试官沟通一期刚开始的方案的用户量,性能要求,单机目标qps是什么等,?在明确系统的特点和约束之后再来设计,而不是一开始就是用典型互联网的那种通用架构自己搞自己的。总结tcp/udp还有网络这块(各种网络模型,已经select,poll和epoll)这块一定要非常熟悉一定要有拿的出手的项目经验,而且要能够讲清楚,讲清楚项目中取舍,设计模型和数据表分布式要非常熟悉常见问题定位一定要有思路操作系统,还是操作系统,重要的事情说三遍系统设计,思路,思路,思路,一定要思路清晰,一定要总结下系统设计的流程一点很重要的心得,平时blog和专栏看的再多,如果没有自己的思考不过是过眼云烟,根本不会成为自己的东西,就像内核态和用户态,平常也看过,但是没细想,突然要自己说,还真说不出来,这就很尴尬了。勿以浮沙筑高台,基础这种东西还是需要时间去慢慢打牢,去思考去总结。系统设计相关资料:系统设计入门系统设计典型问题的思考东南亚互联网公司TODO

January 12, 2019 · 1 min · jiezi

机器学习 面试常见问题&答案 ②

欠拟合(通常代表高偏差)精度如前所述如果模型具有足够的数据,但因不够复杂而无法捕捉基本关系,则会出现偏差。这样一来,模型一直会系统地错误表示数据,从而导致预测精度低。这种现象叫做欠拟合(underfitting)。简单来说,如果模型不适当,就会出现偏差。举个例子:如果对象是按颜色和形状分类的,但模型只能按颜色来区分对象和将对象分类(模型过度简化),因而一直会错误地分类对象。或者,我们可能有本质上是多项式的连续数据,但模型只能表示线性关系。在此情况下,我们向模型提供多少数据并不重要,因为模型根本无法表示其中的基本关系,我们需要更复杂的模型。过拟合(通常代表高方差)过拟合又可以称之为维度灾难。机器学习中的维度灾难 - 红色石头的专栏 - CSDN博客 https://blog.csdn.net/red_stone1/article/details/71692444过少的数据样本与有效特征,过高的维度,会导致模型学到噪声和不必要的无效特征,这个概念叫做过拟合,是维度灾难的一个直接后果。在训练模型时,通常使用来自较大母体(训练集)的有限数量样本。如果利用选择的数据子集反复训练模型,可以预料它的预测结果会因提供给它的具体样本而异。在这里,方差(variance)用来测量预测结果对于任何给定的测试样本会出现多大的变化。出现方差是正常的,但方差过高表明模型无法将其预测结果泛化到从中抽取训练样本的较大母体。对训练集高度敏感也称为过拟合(overfitting),而且通常出现在模型过于复杂或我们没有足够的数据支持它时。通常,可以利用更多数据进行训练,以降低模型预测结果的方差并提高精度。如何改进模型的有效性我们可以看到,在给定一组固定数据时,模型不能过于简单或复杂。如果过于简单,模型无法了解数据并会错误地表示数据。但是,如果建立非常复杂的模型,则需要更多数据才能了解基本关系,否则十分常见的是,模型会推断出在数据中实际上并不存在的关系。关键在于,通过找出正确的模型复杂度来找到最大限度降低偏差和方差的最有效点。当然,数据越多,模型随着时间推移会变得越好。 要详细了解偏差和方差,建议阅读 Scott Fortmann-Roe撰写的这篇文章。http://scott.fortmann-roe.com…除了选定用来训练模型的数据子集外,您使用的哪些来自给定数据集的特征也会显著影响模型的偏差和方差。聊一下模型训练过程中的学习曲线我们根据模型通过可视化图形从数据中学习的能力来探讨偏差与方差之间的关系。机器学习中的学习曲线是一种可视化图形,能根据一系列训练实例中的训练和测试数据比较模型的指标性能。在查看数据与误差之间的关系时,我们通常会看到,随着训练点数量的增加,误差会趋于下降。由于我们尝试构建从经验中学习的模型,因此这很有意义。我们将训练集和测试集分隔开,以便更好地了解能否将模型泛化到未见过的数据而不是拟合到刚见过的数据。在学习曲线中,当训练曲线和测试曲线均达到稳定阶段,并且两者之间的差距不再变化时,则可以确认模型已尽其所能地了解数据。偏差在训练误差和测试误差收敛并且相当高时,这实质上表示模型具有偏差。无论我们向其提供多少数据,模型都无法表示基本关系,因而出现系统性的高误差。方差如果训练误差与测试误差之间的差距很大,这实质上表示模型具有高方差。与偏差模型不同的是,如果有更多可供学习的数据,或者能简化表示数据的最重要特征的模型,则通常可以改进具有方差的模型。理想的学习曲线模型的最终目标是,误差小并能很好地泛化到未见过的数据(测试数据)。如果测试曲线和训练曲线均收敛,并且误差极低,就能看到这种模型。这种模型能根据未见过的数据非常准确地进行预测。说一下你理解的信息增益(Information gain)熵:表示变量的不确定性。条件熵:在一个条件下,变量的不确定性。信息增益:熵 - 条件熵在一个条件下,信息不确定性减少的程度!例子:原来明天下雨例如信息熵是2,条件熵是0.01(因为如果是阴天就下雨的概率很大,信息就少了),这样相减后为1.99,在获得阴天这个信息后,下雨信息不确定性减少了1.99!是很多的!所以信息增益大!也就是说,阴天这个信息对下雨来说是很重要的!所以在特征选择的时候常常用信息增益,如果IG(信息增益大)的话那么这个特征对于分类来说很关键~~决策树就是这样来找特征的。说一下分类和回归的区别?两者追到本质是一样。分类模型和回归模型本质一样,分类模型可将回归模型的输出离散化,回归模型也可将分类模型的输出连续化,举几个例子:Logistic Regression 和 Linear Regression:Linear Regression: 输出一个标量wx+b,这个值是连续值,所以可以用来处理回归问题Logistic Regression:把上面的 wx+b 通过 sigmoid函数映射到(0,1)上,并划分一个阈值,大于阈值的分为一类,小于等于分为另一类,可以用来处理二分类问题更进一步:对于N分类问题,则是先得到N组w值不同的wx+b,然后归一化,比如用 softmax函数,最后变成N个类上的概率,可以处理多分类问题Support Vector Regression 和 Support Vector Machine:SVR:输出wx+b,即某个样本点到分类面的距离,是连续值,所以是回归模型SVM:把这个距离用 sign(·)函数作用,距离为正(在超平面一侧)的样本点是一类,为负的是另一类,所以是分类模型Naive Bayes 用于分类 和 回归:用于分类:y是离散的类别,所以得到离散的 p(y|x),给定 x,输出每个类上的概率用于回归:对上面离散的 p(y|x)求期望yP(y|x),就得到连续值。但因为此时y本身是连续的值,所以最地道的做法是,得到连续的概率密度函数p(y|x),然后再对y求期望。参考 http://www.cs.waikato.ac.nz/~eibe/pubs/nbr.pdf前馈神经网络(如 CNN 系列) 用于 分类 和 回归:用于回归:最后一层有m个神经元,每个神经元输出一个标量,m个神经元的输出可以看做向量v,现全部连到一个神经元上,则这个神经元输出wv+b,是一个连续值,可以处理回归问题,跟上面 Linear Regression思想一样用于N分类:现在这m个神经元最后连接到 N 个神经元,就有 N组w值不同的 wv+b,同理可以归一化(比如用 softmax )变成N个类上的概率(补充一下,如果不用 softmax,而是每个 wx+b用一个sigmoid,就变成多标签问题,跟多分类的区别在于,样本可以被打上多个标签)循环神经网络(如 RNN 系列) 用于分类 和 回归:用于回归 和 分类: 跟 CNN 类似,输出层的值 y =wv+b,可做分类可做回归,只不过区别在于,RNN的输出跟时间有关,即输出的是 {y(t),y(t+1),…}序列(关于时间序列,见下面的更新)上面的例子其实都是从 prediction 的角度举例的,如果从 training 角度来看,分类模型和回归模型的目标函数不同,分类常见的是 log loss,hinge loss, 而回归是 square loss 如文章你已看懂,点个「喜欢」即可。如若错误以及不清晰的地方,随时提出。欢迎扫一扫上面二维码加入我的个人微信号进行技术交流。 ...

January 11, 2019 · 1 min · jiezi

Construct Binary Tree from Preorder and Inorder Traversal

题目leetcode 105, https://leetcode.com/problems...Given preorder and inorder traversal of a tree, construct the binary tree.Note:You may assume that duplicates do not exist in the tree.For example, givenpreorder = [3,9,20,15,7]inorder = [9,3,15,20,7]Return the following binary tree:3/ \ 9 20/ \15 7思路前序遍历的第一个节点就是root,用这个root可以把中序遍历的列表正好分为两段,分别是左右子树。左右子树的size,正好是前序有序的两段。因此,可以根据前序第一个节点,划分出两个前序+中序的序列,递归生成子树。再将子树连接到root上。代码/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; /class Solution {public: TreeNode buildTree(vector<int>& preorder, vector<int>& inorder) { int size = preorder.size(); if (size == 0) { return NULL; } int p_idx = 0; int root_val = preorder[p_idx++]; TreeNode root = new TreeNode(root_val); vector<int> p1,p2; vector<int> i1,i2; bool in_left = true; for (int i = 0; i < size; i++) { if (inorder[i] == root_val) { in_left = false; continue; } if (in_left) { i1.push_back(inorder[i]); p1.push_back(preorder[p_idx++]); } else { i2.push_back(inorder[i]); p2.push_back(preorder[p_idx++]); } } root->left = buildTree(p1, i1); root->right = buildTree(p2, i2); return root; }}; ...

January 10, 2019 · 1 min · jiezi

面试题 LazyMan 的Rxjs实现方式

前言笔者昨天在做某公司的线上笔试题的时候遇到了最后一道关于如何实现LazyMan的试题,题目如下实现一个LazyMan,可以按照以下方式调用:LazyMan(“Hank”)输出:Hi! This is Hank!LazyMan(“Hank”).sleep(10).eat(“dinner”)输出Hi! This is Hank!//等待10秒..Wake up after 10Eat dinnerLazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出Hi This is Hank!Eat dinnerEat supper~LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出//等待5秒Wake up after 5Hi This is Hank!Eat supper以此类推。鉴于时间的原因只可惜本人当时并没写出来,我当时脑海里其实看到提意就知道要用到队列、Promise等异步操作。然后我查阅了网上的资料好像关于这个LazyMan的实现方式倒不少,就说明这道题其实蛮有意思的,但大多都是关于Promise或setTimeout的实现,并没有Rxjs的实现方式,所以我就用一些操作符实现了这个LazyManclass LazyManModel { queue: { timeout: number, fn: Function }[] = [] constructor() { setTimeout(() => { from(this.queue).pipe( map(e => { if (e.timeout) return of(e).pipe(delay(e.timeout * 1000)); return of(e) }), concatAll() ).subscribe(value => { value.fn() }) }) } sleep(time: number): this { this.queue.push({ timeout: time, fn: () => { console.log(Wake up after ${time}) } }) return this } eat(foot: string): this { this.queue.push({ timeout: null, fn: () => { console.log(Eat ${foot}~) } }) return this } sleepFirst(time: number): this { this.queue.unshift({ timeout: time, fn: () => { console.log(Wake up after ${time}) } }) return this } exported(): (name: string) => this { return (name): this => { this.queue.push({ timeout: null, fn: () => { console.log(Hi! This is ${name}!) } }) return this } }}示例const LazyMan = new LazyManModel().exported();LazyMan(‘Hank’).eat(‘foot’).eat(‘ping’).sleep(10).eat(‘pizza’).sleepFirst(5)关于setTimeout我在constructor构造函数里使用了setTimeout是因为,在调用的时候是链式的,其作用域一直都在同一堆栈,而setTimeout里则是把订阅的方法放到的最后一个栈执行 ...

January 10, 2019 · 1 min · jiezi

leetcode 148 Sort List

题目https://leetcode.com/problems…Sort a linked list in O(n log n) time using constant space complexity.Example 1:Input: 4->2->1->3Output: 1->2->3->4Example 2:Input: -1->5->3->4->0Output: -1->0->3->4->5思路类似快排的思路,进行递归partition。主要是注意处理一些边界条件。每次对一个子段进行sort了之后,要处理好其和原先整链的首尾重新连接好。代码/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode next; * ListNode(int x) : val(x), next(NULL) {} * }; /class Solution {public: ListNode sortList(ListNode head) { return sortList(head, NULL); } private: // sort [beg, end), and return new head, and tail->next = end; ListNode* sortList(ListNodebeg, ListNode end) { if (beg == end || !beg || beg->next == end) { return beg; } // at least has two node // [head, mid, end], maybe head == mid == end // [head, mid) < mid < [mid->next, end) ListNode* head = NULL; // new head ListNode* mid = NULL; beg = partition(beg, end, &head, &mid); // sort [mid->next, end) if (mid && mid->next != end) { mid->next = sortList(mid->next, end); } // sort [head, mid) return sortList(head, mid); } ListNode* partition(ListNode* beg, ListNode* end, ListNode** p_head, ListNode** p_mid) { if (!beg || !p_head || !p_mid) { return beg; } ListNode* mid = beg; ListNode* tail1 = NULL; ListNode* tail2 = NULL; int v = mid->val; ListNode* p = mid->next; while (p != end) { if (p->val < v) { if (!*p_head) { *p_head = p; tail1 = p; } else { tail1->next = p; tail1 = p; } } else { if (!tail2) { mid->next = p; tail2 = p; } else { tail2->next = p; tail2 = p; } } p = p->next; } if (tail1) { tail1->next = mid; } else { *p_head = mid; } if (tail2) { tail2->next = end; } else { mid->next = end; } *p_mid = mid; return *p_head; }}; ...

January 10, 2019 · 2 min · jiezi

【剑指offer】3.从尾到头打印链表

题目描述输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。分析要了解链表的数据结构:val属性存储当前的值,next属性存储下一个节点的引用。要遍历链表就是不断找到当前节点的next节点,当next节点是null时,说明是最后一个节点,停止遍历。最后别忘了,从尾到头遍历链表,不要忘了将你的结果进行翻转。代码/function ListNode(x){ this.val = x; this.next = null;}/function printListFromTailToHead(head){ const result = []; let temp = head; while(temp){ result.push(temp.val); temp = temp.next; } return result.reverse();}拓展链表定义:用一组任意存储的单元来存储线性表的数据元素。一个对象存储着本身的值和下一个元素的地址。需要遍历才能查询到元素,查询慢。插入元素只需断开连接重新赋值,插入快。 function LinkList(){ function node(element){ this.value = element; this.next = null; } let length = 0; let head = null; } LinkList.prototype = { // 追加 append:function(element){ var node = new node(element); var temp = this.head; if(this.head){ //遍历找到链表的终点 while(temp.next){ temp = temp.next; } temp.next = node; }else{ this.head = node; } this.length++; }, // 插入 insert:function(element,index){ if(index <= this.length && index>0){ var node = new node(element); var currentIndex = 0; var currentNode = this.head; var preNode = null; if (currentIndex === 0) { node.next = currentNode; this.head = node; return; } while(currentIndex<index){ preNode = currentNode; currentNode = currentNode.next; currentIndex++; } preNode.next = node; node.next = currentNode; this.length++; } } }链表翻转把初始链表头当做基准点移动下一个元素到头部直到下一个元素为空 /** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } / /* * @param {ListNode} head * @return {ListNode} */ var reverseList = function (head) { let currentNode = null; let headNode = head; while (head && head.next) { // 将当前节点从链表中取出 currentNode = head.next; head.next = currentNode.next; // 将取出的节点移动到头部 currentNode.next = headNode; headNode = currentNode; } return headNode; }; ...

January 10, 2019 · 1 min · jiezi

Java运行时异常与一般异常有什么不一样?

异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。一、Error和ExceptionThrowable是所有Java程序中错误处理的父类,有两种资类:Error和Exception。Error:表示由JVM所侦测到的无法预期的错误,由于这是属于JVM层次的严重错误,导致JVM无法继续执行,因此,这是不可捕捉到的,无法采取任何恢复的操作,顶多只能显示错误信息。Exception:表示可恢复的例外,这是可捕捉到的。二、Java两类主要的异常Java提供了两类主要的异常:runtime exception和checked exception。checked异常也就是我们经常遇到的IO异常,以及SQL异常都是这种异常。对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行catch。所以,面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。但是另外一种异常:runtime exception,也称运行时异常,我们可以不处理。当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。三、Java运行异常出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果没有处理块,到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。运行时异常是Exception的子类,也有一般异常的特点,是可以被Catch块处理的。只不过往往我们不对他处理罢了。也就是说,你如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。如果不想终止,则必须扑捉所有的运行时异常,决不让这个处理线程退出。队列里面出现异常数据了,正常的处理应该是把异常数据舍弃,然后记录日志。不应该由于异常数据而影响下面对正常数据的处理。在这个场景这样处理可能是一个比较好的应用,但并不代表在所有的场景你都应该如此。如果在其它场景,遇到了一些错误,如果退出程序比较好,这时你就可以不太理会运行时异常,或者是通过对异常的处理显式的控制程序退出。异常处理的目标之一就是为了把程序从异常中恢复出来。更多请访问上海尚学堂Java培训,获取Java300集和面试全集等学习资料和免费试学机会。

January 9, 2019 · 1 min · jiezi

【面经】寒冬中的一年半前端跳槽

面试内容小米-小米应用商店(过)一面小米的面试官给人的感觉很亲切很真诚,是一个体验很不错的面试。css实现图片自适应宽高讲flex,手写出flex常用的属性,并且讲出作用BFC是什么项目里面的前端鉴权是怎么实现的?vue里面的虚拟dom是怎么回事?vue双向绑定讲一讲手写函数防抖和函数节流讲讲常用的es6语法,比如let、promise、class等等浏览器渲染过程,回流重绘等等,load、DOMContentLoaded等等事件的触发顺序从小米应用商店里面随便找了一个需求让我现场实现,写伪代码二面讲项目里面的鉴权和图片懒加载怎么实现的讲vue-lazyloader的原理,手写伪代码讲express框架的设计思想线上日志是如何处理的讲事件循环讲nodejs的eventEmitter的实现三面讲项目里面做的事情讲vue的响应式原理、依赖收集、监听数组、虚拟dom等等讲express的中间件系统是如何设计的现场从小米应用商店中找出一个需求现场实现,说思路,写关键的代码四面讲vue-lazyloader源码以及设计使用es5实现es6的classwebsocket握手过程浏览器的事件循环和nodejs事件循环的区别百思编程(过)这个公司是猎头推荐的,一家初创公司,CEO比较强势,也算比较有趣。一面跨域以及解决办法手写一段小算法JavaScript的sort方法内部使用的什么排序?二面这一面是CEO面,主要问了我的职业规划等等问题,在我没有表现出很强的要去该公司的意愿后,直接给我送走了…ponyAI-基础架构(过)一面讲项目里面干了啥vue-lazyloader怎么实现的vue的响应式系统、虚拟dom函数式编程手写了一个算法题二面这轮面试时从美国打电话过来的,事后才知道是Google的前端…讲讲项目里面做了什么vue原理,和react的区别(其实我没怎么用过react)JavaScript异步的处理方式,现场出了一个问题,使用promise实现三面讲项目模块规划、项目如何部署、如何优化等等手写函数的防抖手写一道算法题四面讲讲项目手写一道算法题洋钱罐(过)一面讲项目前端持久化的方式、区别vue-lazyloader的原理怎么配webpack手写vue双向绑定讲es6的一些特性,并且现场出了几个代码片段,说结果手写一道算法题http状态码二面讲项目vue-router的原理项目中怎么用的webpack,怎么优化讲express的设计原理手写一道算法题创新奇智(过)一面讲项目手动实现parseInt二面这一面居然遇到了前同事…写了一些笔试题,问了一些问题三面讲tcp/ip网络层、三次握手,为什么不能两次握手讲vue原理手写一道算法题猿辅导(跪)猿辅导好像总共就一面,期间一些实现方式和面试官有争议(没有冲突)一面手写vue的mixin方法手写promise的all方法现场出了一个移动端的小需求搜狐-垂直媒体部门(过)一面讲项目项目里面用nodejs做了啥抽取了哪些vue组件二面讲项目手写实现promise腾讯-地图(跪)腾讯两个部门面试都会先做一套笔试题,笔试题基本就是一些常见的前端问题以及算法题一面讲项目,对项目提了一些问题怎么判断一个点是否在圆形内、正方形内对笔试题腾讯-天天快报(跪)也是先做了一套笔试题,但是令人尴尬的是,面试官觉得我快排写错了,然而我只是在原地快排没有申请额外空间…对笔试题没了百度-百度云(过)这个部门今年据说升为一级部门了,好像还挺不错的一面讲项目vue响应式原理,什么是mvvmes6使用过的特性flex常见的属性css选择器的优先级抽取过哪些vue组件二面讲项目express设计原理,面试官对动态路由匹配一直追问下去,但是这里的源码设计我确实是忘了,一路讨论下去扯到了字符串的前缀树…实现一个事件发布订阅类,其实就是eventEmitter三面三面是山大老学长,聊了一些业务上的事情搜狗-手机搜狗(过)搜狗一面的体验比较差,面试官给人的感觉不太好…一面讲项目事件循环回调函数的坏处vue里面哪儿不会用到双向绑定二面忘了…快手-商业化(过)一面讲项目如何抽取公共组件的vue的响应式原理如何实现一个可设置过期时间的localStorage实现一个发布订阅系统,包括on、emit、off等等二面一道智力题软件工程思想、设计模式等等async/await代码片段,说输出结果今日头条-广告系统(过)一面讲项目讲lazyloader实现用docker做了什么用webpack做了什么手写一个算法题讲flexvue响应式原理es6二面JavaScript异步优化项目vue原理,包括计算属性、依赖收集等等用JavaScript的异步实现sleep函数算法题三面手写快排,时间复杂度,优化手写实现jsonp项目部署,线上问题等等websocket握手过程四面对vuex的理解,单向数据流设计一个单点登录的系统,类似阿里系那种手写一个算法五面实现一个联想搜索组件手写函数防抖和节流OPPO成都研发中心(过)一面讲项目讲vue的 响应式系统,讲了好久,从渲染watcher到虚拟dom,面试官还跟我讨论了好久忘了二面讲项目忘了百词斩(跪)首先会在线做一道算法题,挺简单的,百词斩感觉挂的稀里糊涂的…一面websocket握手过程tcp/ip网络层,http的特点http强行使用udp能实现吗?vue原理webpack热更新原理,使用过的插件原型、闭包、跨域手写了一道算法题为什么面这么多公司因为我是实习直接转正的,也没参加过秋招,所以对自己在市场上是个怎样的实力没有一个清晰的了解,而且我也想多了解一下其他公司在做什么,于是就尽量的多面,不过说实话面试确实挺累的。如何准备首先前端基础要过关,可以参考前端工程师手册、合格前端系列第九弹-前端面试那些事、2018前端面试押题(讲义)、Interview Book。其次,因为我是在小厂,所以可能项目复杂度没那么高,我就尽量把手头上用到过的东西都搞明白设计原理,比如vue、express、vue-lazyloader、promise等等,平时也会造一些轮子,其实这些对业务开发都是有好处的,起码可以让你知道自己在写啥。最后,计算机基础也是抛不开的,LeetCode刷200题左右,常见的排序、搜索、树遍历算法都要会,而且以这几个为基础的变形也要能看出来。

January 9, 2019 · 1 min · jiezi

【前端面试】原型和原型链

1.题目如何准确判断一个变量是数组写一个原型链继承的例子继承实现的其他方式es6 实现继承的底层原理是什么描述new一个对象的过程zepto及其他源码中如何使用原型链2.知识点2.1 构造函数特点:以大写字母开头function Foo(name,age){ //var obj = {} //this = {} this.name = name; this.age = age; this.class = ‘class1’ // return this}var f1 = new Foo(’liming’,19);扩展var o = {} 是 var o = new Object() 的语法糖var a = [] 是 var a = new Array() 的语法糖function Foo(){} 相当于 var Foo = new Function(){}2.2 原型规则五条规则:1.所有引用类型(对象,数组,函数)都具有对象特性,即可以自由扩展属性2.所有引用类型(对象,数组,函数)都具有一个__proto__(隐式原型)属性,是一个普通对象3.所有的函数都具有prototype(显式原型)属性,也是一个普通对象4.所有引用类型(对象,数组,函数)proto__值指向它构造函数的prototype5.当试图得到一个对象的属性时,如果变量本身没有这个属性,则会去他的__proto__中去找for (var key in object) { //高级浏览器中已经屏蔽了来自原型的属性 //建议加上判断保证程序的健壮性 if (object.hasOwnProperty(key)) { console.log(object[key]); }}2.3 原型链obj. proto . proto . proto __ … Object.prototype === nullinstanceof 用于判断引用类型属于哪个构造函数obj instanceob Foo实际意义:判断 Foo.prototype 在不在 obj的原型链上3.题目解答3.1 如何准确判断一个变量是数组arr instanceof Array3.2 写一个原型链继承的例子封装dom查询function Elem(id){ this.elem = document.getElementById(id);};Elem.prototype.html = function(val){ var elem = this.elem; if (val) { elem.innerHTML = val; return this; }else{ return elem.innerHTML; }}Elem.prototype.on = function(type,fun){ var elem = this.elem; elem.addEventListener(type,fun); return this;}var div1 = new Elem(‘id1’);div1.html(“test”).on(‘click’,function(){ console.log(‘点击’);})3.3 继承实现的其他方式3.3.1 原型继承 var obj = { 0:‘a’, 1:‘b’, arr:[1] } function Foo(arr2){ this.arr2 = [1] } Foo.prototype = obj; var foo1 = new Foo(); var foo2 = new Foo(); foo1.arr.push(2); foo1.arr2.push(2); console.log(foo2.arr); //[1,2] console.log(foo2.arr2); //[1]优点:实现简单缺点:1.无法向父类构造函数传参2.同时new两个对象时改变一个对象的原型中的引用类型的属性时,另一个对象的该属性也会修改。因为来自原型对象的引用属性是所有实例共享的。3.3.2 构造继承 function Super(b){ this.b = b; this.fun = function(){} } function Foo(a,b){ this.a = a; Super.call(this,b); } var foo1 = new Foo(1,2); console.log(foo1.b);优点:可以向父类传参,子类不会共享父类的引用属性缺点:无法实现函数复用,每个子类都有新的fun,太多了就会影响性能,不能继承父类的原型对象。3.3.3 组合继承function Super(){ // 只在此处声明基本属性和引用属性 this.val = 1; this.arr = [1];}// 在此处声明函数Super.prototype.fun1 = function(){};Super.prototype.fun2 = function(){};//Super.prototype.fun3…function Sub(){ Super.call(this); // 核心 // …}Sub.prototype = new Super(); 优点:不存在引用属性共享问题,可传参,函数可复用缺点:父类的属性会被实例化两次,获取不到真正实例父类(无法区分实例是父类创建还是父类创建的)优化: function Super(b){ this.b = b; this.fun = function(){} } Super.prototype.c = function(){console.log(1111)} function Foo(a,b){ this.a = a; Super.call(this,b); } Foo.prototype = Super.prototype; //修复构造函数: var foo1 = new Foo(1,2);缺点:无法区分实例是父类创建还是子类创建的3.3.4 寄生组合继承 function Super(b){ this.b = b; } Super.prototype.c = function(){console.log(1111)} function Foo(a,b){ this.a = a; Super.call(this,b); } var f = new Function(); f.prototype = Super.prototype; Foo.prototype = new f(); //等同于 Foo.prototype = Object.create(Super.prototype); var foo1 = new Foo(1,2);对父类的prototype进行一次寄生,即包装成一个空对象的prototype,再把这个对象实例化出来作为子类的peototype。缺点:无法区分实例是父类创建还是子类创建的可以添加以下代码:Foo.prototype.constructor = Foo这种解决方法不能用于上面的组合优化方法,因为子类父类引用的是同一个原型对象,修改会同时修改。总结:继承主要是实现子类对父类方法,属性的复用。来自原型对象的引用属性是所有实例共享的,所以我们要避免从原型中继承属性。在构造函数中通过call函数可以继承父类构造函数的属性和方法,但是通过这种方式实例化出来的实例会将父类方法多次存储,影响性能。通过组合继承我们使用call继承属性,使用原型继承方法,可以解决以上两个问题,但是通过这种方式实例化出来的对象会存储两份父类构造函数中的属性。用父类的原型构造一个新对象作为子类的原型,就解决了多次存储的问题,所以最终的寄生组合继承就是最佳继承方式,它的缺点就是书写起来比较麻烦。3.3.6 node源码中的继承实现function inherits(ctor, superCtor) { ctor.super_ = superCtor; ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } });}; function Stream(){ //…}function OutgoingMessage() { Stream.call(this); //…}inherits(OutgoingMessage, Stream);OutgoingMessage.prototype.setTimeout = …以上是寄生组合继承的一个实例。1.在OutgoingMessage构造函数中通过call继承Stream构造中的属性。2.调用inherits方法继承Stream原型中的属性。3.扩展OutgoingMessage自身原型的函数。inherits方法中使用了Object.create方法,该方法的作用是通过指定的原型对象和属性创建一个新的对象。ctor.prototype=Object.create(superCtor.prototype,{…..});该方法实际上就做了我们上面寄生组合继承中的工作var f = new Function();f.prototype =superCtor.prototype;return new f();后面的参数是给原型对象添加属性,可选属性(非必填),即把自身作为新创建对象的构造函数。value: 表示constructor 的属性值;writable: 表示constructor 的属性值是否可写;[默认为: false]enumerable: 表示属性constructor 是否可以被枚举;[默认为: false]configurable: 表示属性constructor 是否可以被配置,例如 对obj.a做 delete操作是否允许;[默认为: false]3.4 es6继承的实现方式参考我这篇文章:https://segmentfault.com/a/11…3.5 描述new一个对象的过程创建一个对象{}.proto = 构造函数.prototypethis指向这个对象执行代码即对this赋值返回this3.6 zepto及其他源码中如何使用原型链var Zepto = (function(){ var $,zepto = {} // …省略N行代码… $ = function(selector, context){ return zepto.init(selector, context) } zepto.init = function(selector, context) { var dom // 针对参数情况,分别对dom赋值 // 最终调用 zepto.Z 返回的数据 return zepto.Z(dom, selector) } fnction Z(dom, selector) { var i, len = dom ? dom.length : 0 for (i = 0; i < len; i++) this[i] = dom[i] this.length = len this.selector = selector || ’’ } zepto.Z = function(dom, selector) { return new Z(dom, selector) } $.fn = { // 里面有若干个工具函数 } zepto.Z.prototype = Z.prototype = $.fn // …省略N行代码… return $})()window.Zepto = Zeptowindow.$ === undefined && (window.$ = Zepto) ...

January 8, 2019 · 3 min · jiezi

机器学习 面试常见问题&答案 ①

给定卷积神经网络输入,卷积核大小,padding,步长,求输出的shape?各个激活函数的优缺点Sigmod优点输出值0-1(很重大的优点)其余的和其他众多激活函数比起来,感觉没有什么优点,方便入门理解缺点容易梯度消失x的可变值区域太小,极其容易陷入级值的状况(-0.9~0.9)指数exp计算复杂Tanh优点和sigmod比起来,是零均值化处理。(零均值化可以加快模型的收敛)缺点和sigmod一样的缺点Relu优点计算复杂度低(只有一个if>0判断,大于0则激活值为1),部分区域线性递增,没有幂运算与指数运算缺点x小于0时无法产生激活值训练到后期可能权重参数更新太大Leakly ReLu优点相对于relu来说,激活值必然可以产生缺点Relu的其他缺点一概继承下来了ELU优点相对于Leaky relu来说,激活值更平滑缺点其他的Leaky relu的缺点一并继承下来了。如何选择激活函数?Relu-小心设置learningrate(因为x>0的情况下,导数均为1),最好是设置一个比较小的值。不要使用sigmod(缺点太多,计算复杂)如何初始化CNN?(理论)不要全部把超参数设置为0(单层网络可以)容易梯度消失如何初始化CNN?(实践)Xavier-tanh(不太适合relu)fan_in输出通道数Fan_out输出通道数代码Np.randon.rand(fan_in,fan_out)/np.sqrt(fan_in/2)如何分析初始化参数结果好坏?查看初始化后各层的激活值分布是否在固定的,稳定的,同一个区间的均匀分布比较好的初始化结果均值为0,方差为0.02如tanh,relu函数什么叫梯度消失,梯度爆炸当网络层数过多时,前面层由于求导过程乘积运算,出现weight与bias变得异常大与异常小的情况左下角的内容清楚的说明了梯度爆炸和梯度消失的场景BN是什么,为什么能提高收敛速度批归一化是什么?标准化处理,特征缩放的一个方式,将数据规整到一定范围内。如上图所示,BN步骤主要分为4步:求每一个训练批次数据的均值求每一个训练批次数据的方差使用求得的均值和方差对该批次的训练数据做归一化,获得0-1分布。其中是为了避免除数为0时所使用的微小正数。尺度变换和偏移:将xixi乘以调整数值大小,再加上增加偏移后得到yiyi,这里的是尺度因子,是平移因子。这一步是BN的精髓,由于归一化后的xixi基本会被限制在正态分布下,使得网络的表达能力下降。为解决该问题,我们引入两个新的参数:,。 和是在训练时网络自己学习得到的。为什么能提高收敛速度?解决internal covariate shift问题。特征没有消失,而是归一到一定范围内,加快学习速度因为最终都是映射到归一化范围内,所以前一层的权重调整对后一层的影响程度都会降低,不用重新适应新的分布,从而让模型学的更快,避免完全从头学习TipsBN不用于输入层和输出层(经验论)BN(实践)每次batch传入时都做BN各个优化器的优缺点优化器分两种固定学习率的优化算法SGD随机梯度下降优点只随机采样一个样本来计算梯度,加快学习效率,并且避免了普通GD一次性参数更新的坏处(导致模型无法收敛)缺点选择合适的学习率较为困难Momentum动量优点动量梯度下降,动力火车,惯性火车,这一次梯度下降的值,会影响下一次梯度下降的值,相对于简单的梯度下降来说,Momentum动量带有延续性相对于简单的梯度下降来说,减少梯度震荡缺点和SGD一样,选择合适的学习率较为困难自适应学习率的优化算法Adagrad优点更新参数时,会针对原梯度值乘上一个变量,即其所有梯度历史平均值总和的平方根(如上图)这样在训练初期,分母较小,学习率较大,学习比较快,后期时,学习会逐渐减慢缺点从训练开始就积累梯度方差会导致有效学习率过早和过量的减小只能解决凸问题,当应用于非凸函数训练神经网络时,学习可能会到达一个局部是凸碗的区域RMSProp优点能够解决凸问题由累计平方梯度变成和平均梯度缺点缺少Momentum动量元素Adam(结合了动量和RMSProp,通用方案)结合了Momentum和RMSProp的优点手画一下LSTM梯度裁剪介绍一下残差网络ResNet 基于VGG没解决深层网络下出现性能[梯度消失,导致学不到东西]与效率下降[反向传播运算成本大]的问题,优化出来的一个新的神经网络结构,如图所示,两条路一起走,最终线性激活输入值f(x)+x,然后将f(x)+x传递给激活函数[假设为relu]。那么在反向传播的时候,后面层的梯度更加“无损”的直接传递到前面层,前面层的参数因此也能继续更新。为什么残差网络会有效果?□ 先验证明《深层网络效果会比浅层网络好》只要有理想的训练方式,更深的网络肯定会比较浅的网络效果要好。证明过程也很简单:假设在一种网络A的后面添加几层形成新的网络B,如果增加的层级只是对A的输出做了个恒等映射(identity mapping),即A的输出经过新增的层级变成B的输出后没有发生变化,这样网络A和网络B的错误率就是相等的,也就证明了加深后的网络不会比加深前的网络效果差。当层数比较多时,容易导致模型学不到东西,甚至出现反效果,然而deep layers又确实是能使模型效果变好的,所以出现残差网络。效率不影响,层数增加,可以低成本高效率的学到更多非线性的特征。解决梯度弥漫问题如上所说,关键点在于反向传播的时候,梯度可以沿着shortcut无损进行回传,避免梯度弥漫问题。解决模型退化问题避免了过渡训练后,导致模型准确率反而降低的情况。paper中称为degration。经过实验后发现确实能解决这个问题。本人对于这个问题的确切原因并不清楚,但是猜测这个原因很可能是由于梯度消失以及众多其他原因所造成的。Q:既然说中间层是不必要的,那么为什么不直接把这些层去掉呢?可事实上,ResNet的结果比浅层网络的结果好的多,这应该怎么解释呢?加入中间层主要是为了说明会产生退化的效果。 ResNet效果好是因为解决了退化问题,,梯度爆炸/梯度弥散,调节了网络结构,简化了反向传播求导运算的流程。这和砍掉深一点的层是不一样的思想风格转换的原理图像风格转换由风格特征与内容特征共同计算得出风格特征风格的抽象度(越往后层,加入了越多内容的元素,更加具像)内容特征内容的相似度(越往后层,加入了越多风格的元素,和原图越不像)## 怎么解决过拟合简化模型正则化(包含dropout)数据增强集成学习早停减少特征数或使用较少的特征组合## 怎么解决欠拟合增加特征数或者使用较多的特征组合减小正则权重增加模型复杂度使用boosting集成学习如何提高学习算法性能的指导方针->低可拟合偏差更大的模型,更深的层更好的优化器方案探索更合适的超参数->低方差找寻更多的数据正则化,dropout对抗神经网络探索更合适的超参数->清晰的正交化方案……思路逻辑清晰的调试数据预处理一般步骤有哪些?指定原始数据的文件列表 -> 创建文件列表队列 ->从文件中读取数据 -> 数据预处理 -> 整理成batch作为神经网络输入如何用指标和方案去评判一个优秀的模型?train/Validation/Test 准确率/召回率方差偏差CNN模型加速与压缩汇总1. 合理设计模型2. 权值剪枝(编程稀疏矩阵)3. 权值量化(聚类)4. 二值化(BWN,XNorNet)5. 霍夫曼编码6. 奇异值分解(projection层)7. 1x1卷积的恰当使用减少通道量8. 卷积分解为deepwise Conv和pointwiseConv可大幅度减小计算量和参数量1/(Dk^2)9. Group Conv(可节省1/g计算量)10. Channel Shuffle11. 蒸馏法12. 低秩分解13. 模型裁剪# 怎么选择超参数神经网路中的超参数主要包括1. 学习率 ,2. 正则化参数 ,3. 神经网络的层数 L4. 每一个隐层中神经元的个数 j5. 学习的回合数Epoch6. 小批量数据 minibatch 的大小由神经网络的机理进行选择7. 输出神经元的编码方式8. 代价函数的选择9. 权重初始化的方法10. 神经元激活函数的种类11 . 宽泛策略的核心在于简化和监控12. 参加训练模型数据的规模如文章你已看懂,点个「喜欢」即可。如若错误以及不清晰的地方,随时提出。欢迎扫一扫上面二维码加入我的个人微信号进行技术交流。

January 8, 2019 · 1 min · jiezi

【剑指offer】2.替换空格

题目描述请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。题目说的不太严谨:1.能不能允许连续出现多个空格?2.若有可能连续多个空格,用多个还是单个20%进行替换?分三种情况解答1.不会出现连续多个空格:直接用空格将字符串切割成数组,在用20%进行连接。function replaceSpace(str){ return str.split(’ ‘).join(’%20’);}2.允许出现多个空格,每个空格均用一个20%替换:用正则表达式找到所有空格依次替换function replaceSpace(str){ return str.replace(/\s/g,’%20’);}3.允许出现多个空格,多个空格用一个20%替换:用正则表达式找到连续空格进行替换function replaceSpace(str){ return str.replace(/\s+/g,’%20’);}

January 8, 2019 · 1 min · jiezi

【剑指offer】二维数组查找

题目在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。基本思路二维数组是有序的,比如下面的数据:1 2 34 5 67 8 9可以直接利用左下角数字开始查找:大于:比较上移小于:比较右移代码思路将二维数组看作平面坐标系从左下角(0,arr.length-1)开始比较:目标值大于坐标值—x坐标+1目标值小于坐标值—y坐标-1注意:二维数组arri中j代表x坐标i代表y坐标代码 function Find(target, array) { let i = array.length - 1; // y坐标 let j = 0; // x坐标 return compare(target, array, i, j); } function compare(target, array, i, j) { if (array[i] === undefined || array[i][j] === undefined) { return false; } const temp = array[i][j]; if (target === temp) { return true; } else if (target > temp) { return compare(target, array, i, j+1); } else if (target < temp) { return compare(target, array, i-1, j); } }拓展:二分查找二分查找的条件是必须有序。和线性表的中点值进行比较,如果小就继续在小的序列中查找,如此递归直到找到相同的值。 function binarySearch(data, arr, start, end) { if (start > end) { return -1; } var mid = Math.floor((end + start) / 2); if (data == arr[mid]) { return mid; } else if (data < arr[mid]) { return binarySearch(data, arr, start, mid - 1); } else { return binarySearch(data, arr, mid + 1, end); } } ...

January 7, 2019 · 1 min · jiezi

「前端面试题系列3」伪类与伪元素的区别及实战

前言面试前端候选人的时候,我经常会问这样一个有关CSS的问题:你知道伪类与伪元素么,它们的分别是什么?这时,能回答上来的很少。换一种问法,你知道 :hover, :active, :focus, :visited么?这时,基本都能回答上来,这不就是a标签的四种状态么。嗯,ok。然后继续问,那么 ::before 和 ::after,听说过么?这时,能听到的回答是,嗯,我看到过,偶尔会用。伪类与伪元素,都有一个“伪”字,那它们有什么区别么?这时,回应我的,是一片沉默。。。从回答上来分析,虽然伪类和伪元素平时都有接触,但在概念上,都比较模糊。今天,我们就来说说伪类与伪元素的区别,以及使用场景。伪类,不是只有a标签的四种状态。伪元素,也不是只有 ::before 与 ::after。更多的伪类与伪元素,详见文末附录。概念上的区别从概念上来区分,大致有以下几点:伪类,更多的定义的是状态。常见的伪类有 :hover,:active,:focus,:visited,:link,:not,:first-child,:last-child等等。伪元素,不存在于DOM树中的虚拟元素,它们可以像正常的html元素一样定义css,但无法使用JavaScript获取。常见伪元素有 ::before,::after,::first-letter,::first-line等等。CSS3明确规定了,伪类用一个冒号(:)来表示,而伪元素则用两个冒号(::)来表示。但目前因为兼容性的问题,它们的写法可以是一致的,都用一个冒号(:)就可以了,所以非常容易混淆。实战场景——伪类表单校验表单的校验中,常会用到 :required、:valid 和 :invalid 这三个伪类。先来看看它们所代表的含义。:required,指定具有 required属性 的表单元素:valid,指定一个 匹配指定要求 的表单元素:invalid,指定一个 不匹配指定要求 的表单元素看下面这个例子:<p>input中类型为email的校验</p><p>符合email校验规则</p><input type=“email” required placeholder=“请输入” value=“24238477@qq.com” /><br><br><p>不符合email校验规则</p><input type=“email” required placeholder=“请输入” value=“lalala” /><br><br><p>有required标识,但未填写</p><input type=“email” required placeholder=“请输入” value="" />input { &:valid { border-color: green; box-shadow: inset 5px 0 0 green; } &:invalid { border-color: red; box-shadow: inset 5px 0 0 red; } &:required { border-color: red; box-shadow: inset 5px 0 0 red; }}效果如下:折叠面板过去,要实现折叠面板的显示或隐藏,只能用JavaScript来搞定。但是现在,可以用伪类 :target 来实现。 :target 是文档的内部链接,即 URL 后面跟有锚名称 #,指向文档内某个具体的元素。看下面这个例子:<div class=“t-collapse”> <!– 在url最后添加 #modal1,使得target生效 —> <a class=“collapse-target” href="#modal1">target 1</a> <div class=“collapse-body” id=“modal1”> <!– 将url的#modal1 变为 #,使得target失效 —> <a class=“collapse-close” href="#">target 1</a> <p>…</p> </div></div>.t-collapse { >.collapse-body { display: none; &:target { display: block; } }}元素的index当我们要指定一系列标签中的某个元素时,并不需要用JavaScript获取。可以用 :nth-child(n) 与 :nth-of-type(n) 来找到,并指定样式。但它们有一些小区别,需要注意。首先,它们的n可以是大于零的数字,或者类似2n+1的表达式,再或者是 even / odd。另外,还有2个区别::nth-of-type(n) 除了关注n之外,还需要关注最前面的类型,也就是标签。:nth-child(n) 它关注的是:其父元素下的第n个孩子,与类型无关。看下面这个例子,注意两者的差异:<h1>这是标题</h1><p>第一个段落。</p><p>第二个段落。</p><p>第三个段落。</p><p>第四个段落。</p><p>第五个段落。</p>实战场景——伪元素antd的彩蛋事件还记得2018年圣诞节的“彩蛋事件”,在整个前端圈,轰动一时。因为按钮上的一朵云,导致不少前端er提前回家过年了。当时,彩蛋事件出现的第一时间,就吓得我赶快打开工程看了一眼,果然也中招了。为了保住饭碗,得赶紧把云朵去掉。查看了生成的html,发现原来是 button 下藏了一个 ::before。所以,赶紧把样式覆盖掉,兼容代码如下:.ant-btn { &::before { display: none !important; }}美化选中的文本在网页中,默认的划词效果是,原字色保持不变,划过时的背景变为蓝底色。其实,这是可以用 ::selection 来进行美化的。看下面这个例子:<p>Custom text selection color</p>::selection { color: red; background-color: yellow;}效果如下:划过的部分美化为:红色的字体,并且底色变为了黄色。总结CSS也可以实现动态的交互,并非只有JavaScript才能实现。书写的时候,要尊重规范。写伪类的时候用 :,而写伪元素的时候用 ::。兼容性的问题,交给postcss去做。本文并未涉及兼容性的写法,包括前缀问题,可以交给autoprefixer去做。附录CSS3中的伪类:root 选择文档的根元素,等同于 html 元素:empty 选择没有子元素的元素:target 选取当前活动的目标元素:not(selector) 选择除 selector 元素意外的元素:enabled 选择可用的表单元素:disabled 选择禁用的表单元素:checked 选择被选中的表单元素:nth-child(n) 匹配父元素下指定子元素,在所有子元素中排序第n:nth-last-child(n) 匹配父元素下指定子元素,在所有子元素中排序第n,从后向前数:nth-child(odd) 、 :nth-child(even) 、 :nth-child(3n+1):first-child 、 :last-child 、 :only-child:nth-of-type(n) 匹配父元素下指定子元素,在同类子元素中排序第n:nth-last-of-type(n) 匹配父元素下指定子元素,在同类子元素中排序第n,从后向前数:nth-of-type(odd) 、 :nth-of-type(even) 、 :nth-of-type(3n+1):first-of-type 、 :last-of-type 、 :only-of-typeCSS3中的伪元素::after 已选中元素的最后一个子元素::before 已选中元素的第一个子元素::first-letter 选中某个款级元素的第一行的第一个字母::first-line 匹配某个块级元素的第一行::selection 匹配用户划词时的高亮部分PS:欢迎关注我的公众号 “超哥前端小栈”,交流更多的想法与技术。 ...

January 6, 2019 · 1 min · jiezi

一道有意思的面试算法题

新年第一篇文章,先祝大家新年快乐!!那么接下来进入正文。前言前阵子突发奇想,突然开始刷leetcode。其中刷到了一道有意思的题目,发现这道题是当时秋招的时候,腾讯面试官曾经问过我的题目。于是分享给大家看下。题目描述给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。初步解法这道题第一眼看过去,思路挺简单的,我们只需要维护一个对象来记录每一个元素出现的次数,使用元素的值作为key,元素出现的次数作为value。之后再遍历这个对象,找到value为1的key。对应的key就是那个元素。代码如下:function singleNumber(nums) { const obj = {}; for (let i = 0; i < nums.length; i++) { obj[nums[i]] = obj[nums[i]] ? obj[nums[i]] + 1 : 1; } for (let key in obj) { if (obj[key] === 1) { return Number(key); // 由于 key 是 string ,因此我们这里需要转化下 } }}console.log(singleNumber([2, 2, 1, 4, 4, 5, 5, 1, 8])); // 8增加限制是吧,这道题很简单,那我为什么要说它有意思呢?因为题目里面其实还有一个限制:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?重点在于不使用额外空间。我们上面那种解法,创建了一个新的对象来储存结果,明显是不行的。那么有没有办法可以只使用原来的数组来实现这个功能呢?最终解法我们可以思考下,一个数组里,所有的数字都出现两次,除了一个我们要找的数字只出现一次。那么,我们有没有办法将两个相同的数字给过滤掉呢?好啦,不卖关子了,之前有了解过的人应该就知道解决方案了,如果之前没了解过这方面东西的人,可以继续往下看。解决方案:异或操作异或运算是对于二进制数字而言的,比如说一个有两个二进制a、b,如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。而javascript的按位异或(即^操作)操作,则会对两个数字相应的每一对比特位执行异或操作。比如说 1 ^ 2,本质上其实是1和2的每一对比特位执行异或操作,等价于下面 00000000000000000000000000000001 // 数字1对应的二进制^ 00000000000000000000000000000010 // 数字2对应的二进制= 00000000000000000000000000000011 // 数字3对应的二进制因此1^2的结果就为3啦。那么如果两个相同的数字进行异或操作,结果就可想而知,答案为0啦。如果是0和任何一个数字异或呢?结果是数字本身。这样一来的话,我们是不是有了这个问题的解决办法了?我们只需要遍历数组,将所有的值取异或,最终剩下的值,就是那个只出现一次的数字。代码如下:/** * 只存在一次的数字 * https://leetcode-cn.com/explore/interview/card/top-interview-questions-easy/1/array/25/ * @param {number[]} nums * @return {number} */function singleNumber(nums) { for (let i = 1; i < nums.length; i++) { nums[0] ^= nums[i]; } return nums[0];};console.log(singleNumber([2, 2, 1, 4, 4, 5, 5, 1, 8]));结语这道面试题主要考验面试者对异或的理解,以及能不能活学活用,将这道题与异或联系在一起。当然,最重要的还是多学习、多刷题、多看书。这样才能不断进步。本文地址在->本人博客地址, 欢迎给个 start 或 follow ...

January 4, 2019 · 1 min · jiezi

【前端面试】变量和类型计算

1.题目1.JS使用typeof能得到哪些类型=== 和 == 的选择JS中有哪些内置函数JS变量按存储方式分为哪些类型,并描述其特点如何理解JSON2.知识点2.1 值类型和引用类型值类型(boolean,string,number,null,undefined)var a = 10;var b = a;a = 20;console.log(b); //10引用类型(对象,数组,函数)var a = {x:10}var b = a;a.x = 20;console.log(b); //20值类型直接把值存储在堆中,把a赋值给b在内存中是又给b开辟了一块新的空间,存储了同样的值。引用类型分两块存储,先在堆中存储一个实际的值,再在栈中存储一个堆中值的引用地址,指向堆中的对象。把a赋值给b是在栈中重新开辟一块空间存储的还是相同对象的引用地址,a和b存储的地址相同,指向的对象也相同。当对象值发生改变时,两者会同时改变。引用类型的值一般都比较大,采用此种存储方式可以节省内存空间。2.2 typeof运算符typeof ‘abc’ //stringtypeof 123 //numbertypeof true //booleantypeof undefined //undefinedtypeof null //objecttypeof {a:10} //objecttypeof [1,2,3] //objecttypeof console.log() //function2.3 类型转换强类型转换:通过String(),Number(),Boolean(),parseInt()函数强制转换可能发生隐式类型转换的场景字符串拼接使用==if语句逻辑循环一、首先看双等号前后有没有NaN,如果存在NaN,一律返回false。二、再看双等号前后有没有布尔,有布尔就将布尔转换为数字。(false是0,true是1)三、接着看双等号前后有没有字符串, 有三种情况:1、对方是对象,对象使用toString()或者valueOf()进行转换;2、对方是数字,字符串转数字;(前面已经举例)3、对方是字符串,直接比较;4、其他返回false四、如果是数字,对方是对象,对象取valueOf()或者toString()进行比较, 其他一律返回false五、null, undefined不会进行类型转换, 但它们俩相等上面的转换顺序一定要牢记,面试的时候,经常会出现类型的问题。‘100’==100 //转换成字符串’’==0 //转换成falseundefined == null; // true1 == true; // true2 == true; // false0 == false; // true0 == ’ ‘; // trueNaN == NaN; // false[] == false; // true[] == ![]; // true//在if中转换成false的:nullundefined’‘NaN0false10 && 0 //0 10转换成true’’ || ‘abc’ //abc ‘‘转换成false!window.abc //true 2.4 null和undefined的区别null:是被赋值过的对象,刻意把一个对象赋值为null,故意表示其为空,不应有值,所以对象为null是正常的,typeof null 返回 ‘object’ ,null可以转换为0undefined 表示“缺少值”,即此处应有一个值,但还没有定义;转为数值时为NaN(非数字值的特殊值) typeof undefined 返回 ‘undefined'3.题目解答3.1 JS使用typeof能得到哪些类型typeof ‘abc’ //stringtypeof 123 //numbertypeof true //booleantypeof undefined //undefinedtypeof null //objecttypeof {a:10} //objecttypeof [1,2,3] //objecttypeof console.log() //function3.2 === 和 == 的选择jquery源码中的写法:除了以下方式其他全部使用 ===if(obj.a == null){ //相当于 obj.a === undefined || obj.a === null}3.3 JS中有哪些内置函数单纯作为语言来说,不考虑node和浏览器webObjectArrayBooleanNumberStringFunctionDateRegExpError内置对象:Math,JSON3.4 JS变量按存储方式分为哪些类型,并描述其特点值类型何引用类型3.5 如何理解JSONJSON是JS中的一个内置对象区别JS对象 {x:10}JSON对象 {‘x’:10}JSON串 “{‘x’:10}”//将JS对象转换成json串JSON.stringify({x:10});//将json字符串转换成json对象JSON.parse("{‘x’:10}");3.6 严格模式目的消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为; 消除代码运行的一些不安全之处,保证代码运行的安全;提高编译器效率,增加运行速度;为未来新版本的Javascript做好铺垫。特性 “use strict”;可以选择放在一个函数中或自定义作用域中。禁止this指向全局对象 function f(){ return !this; } // 返回false,因为"this"指向全局对象,"!this"就是false function f(){ “use strict”; return !this; } // 返回true,因为严格模式下,this的值为undefined,所以"!this"为true。创设eval作用域正常模式下,Javascript语言有两种变量作用域(scope):全局作用域和函数作用域。严格模式创设了第三种作用域:eval作用域。正常模式下,eval语句的作用域,取决于它处于全局作用域,还是处于函数作用域。严格模式下,eval语句本身就是一个作用域,不再能够生成全局变量了,它所生成的变量只能用于eval内部。 “use strict”; var x = 2; console.info(eval(“var x = 5; x”)); // 5 console.info(x); // 2全局变量显式声明 v = 1; // 报错,v未声明 for(i = 0; i < 2; i++) { // 报错,i未声明 }禁止删除变量严格模式下无法删除变量。只有configurable设置为true的对象属性,才能被删除。 “use strict”; var x; delete x; // 语法错误 var o = Object.create(null, {‘x’: { value: 1, configurable: true }}); delete o.x; // 删除成功函数不能有重名的参数保留字为了向将来Javascript的新版本过渡,严格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。使用这些词作为变量名将会报错。 function package(protected) { // 语法错误 “use strict”; var implements; // 语法错误 }3.7 eval1.没有必须使用的应用场景2.不容易调试,可读性不好3.在旧的浏览器中如果你使用了eval,性能会下降10倍。4.容易xss ...

January 3, 2019 · 2 min · jiezi

盘点一下Github上开源的Java面试/学习相关的仓库,看完弄懂薪资至少增加10k

该文已加入开源项目:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识的文档类项目,Star 数接近 16k)。地址:https://github.com/Snailclimb…最近浏览 Github ,收藏了一些还算不错的 Java面试/学习相关的仓库,分享给大家,希望对你有帮助。我暂且按照目前的 Star 数量来排序。本文由 SnailClimb 整理,如需转载请联系作者。1. interviewsGithub地址: https://github.com/kdn251/interviews/blob/master/README-zh-cn.mdstar: 31k介绍: 软件工程技术面试个人指南。概览:2. JCSproutGithub地址:https://github.com/crossoverJie/JCSproutstar: 17.7k介绍: Java Core Sprout:处于萌芽阶段的 Java 核心知识库。概览:3. JavaGuideGithub地址: https://github.com/Snailclimb/JavaGuidestar: 17.4k介绍: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。概览:4. technology-talkGithub地址: https://github.com/aalansehaiyang/technology-talkstar: 4.2k介绍: 汇总java生态圈常用技术框架、开源中间件,系统架构、项目管理、经典架构案例、数据库、常用三方库、线上运维等知识。5. fullstack-tutorialGithub地址: https://github.com/frank-lam/fullstack-tutorialstar: 2.8k介绍: Full Stack Developer Tutorial,后台技术栈/全栈开发/架构师之路,秋招/春招/校招/面试。 from zero to hero。概览:6. java-bibleGithub地址:https://github.com/biezhi/java-biblestar: 1.9k介绍: 这里记录了一些技术摘要,部分文章来自网络,本项目的目的力求分享精品技术干货,以Java为主。概览:7. EasyJobGithub地址:https://github.com/it-interview/EasyJobstar: 1.9k介绍: 互联网求职面试题、知识点和面经整理。8. advanced-javaGithub地址:https://github.com/doocs/advanced-javastar: 1k介绍: 互联网 Java 工程师进阶知识完全扫盲9. 3yGithub地址:https://github.com/ZhongFuCheng3y/3ystar: 0.4 k介绍: Java 知识整合。除了这九个仓库,再推荐几个不错的学习方向的仓库给大家。Star 数高达 4w+的 CS 笔记-CS-Notes:https://github.com/CyC2018/CS-Notes后端(尤其是Java)程序员的 Linux 学习仓库-Linux-Tutorial:https://github.com/judasn/Linux-Tutorial( Star:4.6k)两个算法相关的仓库,刷 Leetcode 的小伙伴必备:①awesome-java-leetcode:https://github.com/awangdev/LintCode;②LintCode:https://github.com/awangdev/LintCodeThoughtWorks准入职Java工程师。专注Java知识分享!开源 Java 学习指南——JavaGuide(17k+ Star)的作者。公众号多篇文章被各大技术社区转载。公众号后台回复关键字“1”可以领取一份我精选的Java资源哦!

January 2, 2019 · 1 min · jiezi

算是目前看到过最好的面试手册了

扫码购买享优惠

January 2, 2019 · 1 min · jiezi

面试题重点突出

什么是跨域?跨域请求资源的方法有哪些?1、什么是跨域?由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。存在跨域的情况:网络协议不同,如http协议访问https协议。端口不同,如80端口访问8080端口。域名不同,如qianduanblog.com访问baidu.com。子域名不同,如abc.qianduanblog.com访问def.qianduanblog.com。域名和域名对应ip,如www.a.com访问20.205.28.90.2、跨域请求资源的方法:(1)、porxy代理定义和用法:proxy代理用于将请求发送给后台服务器,通过服务器来发送请求,然后将请求的结果传递给前端。实现方法:通过nginx代理;注意点:1、如果你代理的是https协议的请求,那么你的proxy首先需要信任该证书(尤其是自定义证书)或者忽略证书检查,否则你的请求无法成功。(2)、CORS 【Cross-Origin Resource Sharing】定义和用法:是现代浏览器支持跨域资源请求的一种最常用的方式。使用方法:一般需要后端人员在处理请求数据的时候,添加允许跨域的相关操作。如下:res.writeHead(200, { “Content-Type”: “text/html; charset=UTF-8”, “Access-Control-Allow-Origin”:‘http://localhost’, ‘Access-Control-Allow-Methods’: ‘GET, POST, OPTIONS’, ‘Access-Control-Allow-Headers’: ‘X-Requested-With, Content-Type’});(3)、jsonp定义和用法:通过动态插入一个script标签。浏览器对script的资源引用没有同源限制,同时资源加载到页面后会立即执行(没有阻塞的情况下)。特点:通过情况下,通过动态创建script来读取他域的动态资源,获取的数据一般为json格式。实例如下:<script> function testjsonp(data) { console.log(data.name); // 获取返回的结果 }</script><script> var _script = document.createElement(‘script’); _script.type = “text/javascript”; _script.src = “http://localhost:8888/jsonp?callback=testjsonp”; document.head.appendChild(_script);</script>缺点: 1、这种方式无法发送post请求(这里) 2、另外要确定jsonp的请求是否失败并不容易,大多数框架的实现都是结合超时时间来判定。介绍一下 JS 的基本数据类型。Undefined、Null、Boolean、Number、String如何利用JavaScript实现一个自定义事件,存在on,off,emit三个方法?这个题目的意义在哪里?我想,应该是对于一些特定的业务逻辑,比如在注册一个“通知”的事件,在与Native交互之后,假设这个交互是在入口级别的页面里,那么如何发送给具体某个业务呢?事件应该是最简单的一种方式,在某个具体的业务中注册一个事件,然后在与Native交互完,拿到某些数据后,然后触发这个事件。我们来一步一步实现一个最简单的事件类Event,不考虑任何其他复杂的情况。假设在这个Event类的内部有一个this._events = [] 数组来维系整个事件系统,我们分别实现on,off,emit三个方法即可。on(注册一个事件):Event.prototype.on = function(type,fun){ let cbs = this._events[type]; cbs ? cbs.push(fun) : this._events[type] = []; if (!cbs) { this._events[type].push(fun) } }这里为什么要将this._events设计为二维数组?因为事件可以是多个,但是事件名可能相同。这个逻辑意图非常的明显,根据type参数从this._events中获取是否存在。如果不存在,创建一个type为key的数组,并将事件句柄程序push到数组中。off(注销一个事件):Event.prototype.off = function(type,fun){ let cbs = this._events[type]; //事件列队中无事件 if (!cbs) { return this; } //删除所有的事件 if (!event && !fun) { this._events = {}; return this; } //只有事件名称时 if (event && !fun) { this._events[type] = null; return this; } //删除某个事件队列中的某个事件 let cb; let i = cbs.length; while(i–){ cb = cbs[i]; if (cb === fun || cb.fun === fun) { cbs.splice(i,1); break; } }}虽然注销事件方法的逻辑可能相比之下稍许多了些,但它的实现也非常简单,只要只存在事件组key名的情况,或者删除某个事件队列中的某个事件句柄程序即可。emit(触发一个事件):Event.prototype.emit = function(type){ let cbs = this._events[type]; let args = tools.toArray(arguments,1); if (cbs) { let i = 0; let j = cbs.length; for(;i<j;i++){ let cb = cbs[i]; cb.apply(this,args); } }}逻辑依然非常简单,通过事件名从this._events获取相应的事件句柄程序数组,然后将arguments转成数组,(这里考虑的是可能会传入参数)如果事件句柄程序数组存在,进行循环,再讲args参数apply给每一个取出来的事件句柄程序。请描述一个网页从开始请求道最终显示的完整过程?一个网页从请求到最终显示的完整过程一般可以分为如下7个步骤:(1)在浏览器中输入网址;(2)发送至DNS服务器并获得域名对应的WEB服务器IP地址;(3)与WEB服务器建立TCP连接;(4)浏览器向WEB服务器的IP地址发送相应的HTTP请求;(5)WEB服务器响应请求并返回指定URL的数据,或错误信息,如果设定重定向,则重定向到新的URL地址;(6)浏览器下载数据后解析HTML源文件,解析的过程中实现对页面的排版,解析完成后在浏览器中显示基础页面;(7)分析页面中的超链接并显示在当前页面,重复以上过程直至无超链接需要发送,完成全部数据显示。请描述一下 cookies,sessionStorage 和 localStorage 的区别?Web Storage有两种形式:LocalStorage(本地存储)和sessionStorage(会话存储)。这两种方式都允许开发者使用js设置的键值对进行操作,在在重新加载不同的页面的时候读出它们。这一点与cookie类似。(1)与cookie不同的是:Web Storage数据完全存储在客户端,不需要通过浏览器的请求将数据传给服务器,因此x相比cookie来说能够存储更多的数据,大概5M左右。(2)LocalStorage和sessionStorage功能上是一样的,但是存储持久时间不一样。LocalStorage:浏览器关闭了数据仍然可以保存下来,并可用于所有同源(相同的域名、协议和端口)窗口(或标签页);sessionStorage:数据存储在窗口对象中,窗口关闭后对应的窗口对象消失,存储的数据也会丢失。注意:sessionStorage 都可以用localStorage 来代替,但需要记住的是,在窗口或者标签页关闭时,使用sessionStorage 存储的数据会丢失。(3)使用 local storage和session storage主要通过在js中操作这两个对象来实现,分别为window.localStorage和window.sessionStorage. 这两个对象均是Storage类的两个实例,自然也具有Storage类的属性和方法。 ...

January 1, 2019 · 1 min · jiezi

『前端好文整理』2019你值得拥有

年初按照惯例,是应该立下flag的时候了。把2018年积累的一些碎片化的好文都梳理了一遍,知识体系化后学习会变得更加有目的性,从而提升学习效率(github地址)。JS篇数据类型JS基本数据类型和引用数据类型的区别及深浅拷贝JS的第七种数据类型JS中typeof与instanceof的区别typeof null 为什么等于 object?为什么用Object.prototype.toString.call(obj)检测对象类型?JS显性数据类型转换和隐性数据类型转换理解Object.defineProperty的作用this深入理解 js this 绑定 ( 无需死记硬背,尾部有总结和面试题解析 )前端基础进阶(五):全方位解读thisthis、apply、call、bindJavaScript中的call、apply、bind深入理解作用域链与闭包JavaScript中作用域和作用域链的简单理解(变量提升)JavaScript作用域、上下文、执行期上下文、作用域链、闭包前端基础进阶(四):详细图解作用域链与闭包JavaScript 闭包入门(译文)JavaScript深入之闭包JavaScript 闭包浏览器是怎么看闭包的。原型与原型链白话原型和原型链前端基础进阶(九):详解面向对象、构造函数、原型与原型链最详尽的 JS 原型与原型链终极详解,没有「可能是」。(一)最详尽的 JS 原型与原型链终极详解,没有「可能是」。(二)最详尽的 JS 原型与原型链终极详解,没有「可能是」。(三)JS执行底层前端基础进阶(一):内存空间详细图解前端基础进阶(二):执行上下文详细图解前端基础进阶(十二):深入核心,详解事件循环机制js中的事件委托或是事件代理详解ES6/ES7..ES6 系列之 let 和 const前端基础进阶(十四):es6常用基础合集ES6 系列之箭头函数JavaScript初学者必看“箭头函数”Promise之你看得懂的PromiseES6 系列之我们来聊聊 PromisePromise原理讲解 && 实现一个Promise对象 (遵循Promise/A+规范)web前端-js继承的理解js 深拷贝 vs 浅拷贝深拷贝的终极探索(90%的人都不知道)理解 async/awaitES6 系列之我们来聊聊 Async除此之外强烈推荐冴羽老师的ES6系列文章,深入骨髓的理解ES6中的核心。TypeScript深入理解 TypeScriptTypeScript体系调研报告TypeScript 实践NodeNode入门谈谈Node中的常见概念Node & Express 入门指南Express使用手记:核心入门node进阶——之事无巨细手写koa源码带你走进 koa2 的世界(koa2 源码浅谈)fly.js—Node下增强的APIHTML/CSS篇CSS 常见布局方式【整理】CSS布局方案CSS查漏补缺[布局概念] 关于CSS-BFC深入理解[译]这些 CSS 命名规范将省下你大把调试时间CSS知识总结前端开发规范:命名规范、html规范、css规范、js规范HTTPHTTP状态码(HTTP Status Code)面试 – 网络 HTTPHTTP最强资料大全我知道的HTTP请求性能&优化篇浏览器的回流与重绘 (Reflow & Repaint)浏览器缓存浏览器前端优化浏览器渲染引擎JavaScript 浏览器事件解析前端性能——监控起步javascript性能优化浏览器性能优化-渲染性能浏览器渲染过程与性能优化现代浏览器性能优化-CSS篇浏览器工作原理及web 性能优化Webpack篇webpack详解Webpack4优化之路webpack4之高级篇webpack4-用之初体验,一起敲它十一遍????免费的渐进式教程:Webpack4的16篇讲解和16份代码手写一个webpack4.0配置React篇五星推荐的系列文章清单胡子大哈React.js 小书TypeScript 2.8下的终极React组件模式面试篇HTML&&css面试题Excuse me?这个前端面试在搞事!80% 应聘者都不及格的 JS 面试题2019年前端面试都聊啥?一起来看看一篇文章搞定前端面试如何轻松拿到淘宝前端 offer | 掘金技术征文腾讯前端面试篇(一)腾讯前端面试篇(二)一点感悟在大前端的时代,要学的知识越来越多,唯有不断的学习不断的积累才能走的更远!本文也会不断的更新下去,希望能帮助到更多的前端爱好者。当然如果你有好文章推荐的话,不妨留言评论,大家一起分享,一起成长。Learn and live!奉上github。

January 1, 2019 · 1 min · jiezi

服务端开发学习路径图,心疼小哥哥们

关注微信公众号《小姐姐味道》获取更多~~在github上看到一种图的表现形式很不错(https://github.com/kamranahme… ),迫不及待的自己做了一张:服务端开发学习路径图,表现力还是很强的。我们从选择一门开发语言说起,经历了摸索阶段、集群阶段和进阶阶段,希望你最后都能以架构师的思路去思考。可以看到后端工程师还是很辛苦很悲催的,每一个方块,都能写一本书啊,一个书橱都装不下啊啊啊。心疼小哥哥们。这张图,还不包括大数据、运维开发、性能优化、故障排查等零散的知识点。到了服务集群化以后,思路就要随之转变。这也是一个初级程序员向高级蜕变的必经之路。要做到高并发高可用,能够平滑的横向扩容,是一件及其复杂的事情,充满了各种权衡和挑战(包括技术和人)。路漫漫其修远兮,继续去探索吧。

December 29, 2018 · 1 min · jiezi

刷面试题之<<携程 地面业务 前端面试经历>>

原文地址 https://www.jianshu.com/p/e6e…在简述看到这篇面试题,结合作者的答案和个人的理解做了一下,因个人水平有限,如果个人做的有什么不对的欢迎指出来,共同交流作者:诗和元芳链接:https://www.jianshu.com/p/e6e…來源:简书简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。笔试题目一<div id=“d”><div id = “a”></div><div id = “b”></div><div id = “c”></div></div><script>var a = [document.getElementById(‘a’),document.getElementById(‘b’),document.getElementById(‘c’)];var b = [document.getElementById(’d’).getElementsByTagName(‘div’)];</script>问a 和 b 的区别。

December 26, 2018 · 1 min · jiezi

面试官问:JS的this指向

面试官经常会出很多考题,都会考察this指向,也是看候选人对JS基础知识是否扎实。附上之前写文章写过的一段话:已经有很多关于this的文章,为什么自己还要写一遍呢。学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景。你不一定能看到别人看到的风景,体会到别人的心情。只有自己去登山,才能看到不一样的风景,体会才更加深刻。函数的this在调用时绑定的,完全取决于函数的调用位置(也就是函数的调用方法)。为了搞清楚this的指向是什么,必须知道相关函数是如何调用的。全局上下文非严格模式和严格模式中this都是指向顶层对象(浏览器中是window)。this === window // true’use strict’this === window;this.name = ‘轩辕Rowboat’;console.log(this.name); // 轩辕Rowboat函数上下文普通函数调用模式// 非严格模式var name = ‘window’;var doSth = function(){ console.log(this.name);}doSth(); // ‘window’你可能会误以为window.doSth()是调用的,所以是指向window。虽然本例中window.doSth确实等于doSth。name等于window.name。上面代码中这是因为在ES5中,全局变量是挂载在顶层对象(浏览器是window)中。事实上,并不是如此。// 非严格模式let name2 = ‘window2’;let doSth2 = function(){ console.log(this === window); console.log(this.name2);}doSth2() // true, undefined这个例子中let没有给顶层对象中(浏览器是window)添加属性,window.name2和window.doSth都是undefined。严格模式中,普通函数中的this则表现不同,表现为undefined。// 严格模式’use strict’var name = ‘window’;var doSth = function(){ console.log(typeof this === ‘undefined’); console.log(this.name);}doSth(); // true,// 报错,因为this是undefined看过的《你不知道的JavaScript》上卷的读者,应该知道书上将这种叫做默认绑定。对call,apply熟悉的读者会类比为:doSth.call(undefined);doSth.apply(undefined);效果是一样的,call,apply作用之一就是用来修改函数中的this指向为第一个参数的。第一个参数是undefined或者null,非严格模式下,是指向window。严格模式下,就是指向第一个参数。后文详细解释。经常有这类代码(回调函数),其实也是普通函数调用模式。var name = ‘轩辕Rowboat’;setTimeout(function(){ console.log(this.name);}, 0);// 语法setTimeout(fn | code, 0, arg1, arg2, …)// 也可以是一串代码。也可以传递其他函数// 类比 setTimeout函数内部调用fn或者执行代码code。fn.call(undefined, arg1, arg2, …);对象中的函数(方法)调用模式var name = ‘window’;var doSth = function(){ console.log(this.name);}var student = { name: ‘轩辕Rowboat’, doSth: doSth, other: { name: ‘other’, doSth: doSth, }}student.doSth(); // ‘轩辕Rowboat’student.other.doSth(); // ‘other’// 用call类比则为:student.doSth.call(student);// 用call类比则为:student.other.doSth.call(student);但往往会有以下场景,把对象中的函数赋值成一个变量了。这样其实又变成普通函数了,所以使用普通函数的规则(默认绑定)。var studentDoSth = student.doSth;studentDoSth(); // ‘window’// 用call类比则为:studentDoSth.call(undefined);call、apply、bind 调用模式上文提到call、apply,这里详细解读一下。先通过MDN认识下call和applyMDN 文档:Function.prototype.call()语法fun.call(thisArg, arg1, arg2, …)thisArg在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。arg1, arg2, …指定的参数列表返回值返回值是你调用的方法的返回值,若该方法没有返回值,则返回undefined。apply和call类似。只是参数不一样。它的参数是数组(或者类数组)。根据参数thisArg的描述,可以知道,call就是改变函数中的this指向为thisArg,并且执行这个函数,这也就使JS灵活很多。严格模式下,thisArg是原始值是值类型,也就是原始值。不会被包装成对象。举个例子:var doSth = function(name){ console.log(this); console.log(name);}doSth.call(2, ‘轩辕Rowboat’); // Number{2}, ‘轩辕Rowboat’var doSth2 = function(name){ ‘use strict’; console.log(this); console.log(name);}doSth2.call(2, ‘轩辕Rowboat’); // 2, ‘轩辕Rowboat’虽然一般不会把thisArg参数写成值类型。但还是需要知道这个知识。之前写过一篇文章:面试官问:能否模拟实现JS的call和apply方法就是利用对象上的函数this指向这个对象,来模拟实现call和apply的。感兴趣的读者思考如何实现,再去看看笔者的实现。bind和call和apply类似,第一个参数也是修改this指向,只不过返回值是新函数,新函数也能当做构造函数(new)调用。MDN Function.prototype.bindbind()方法创建一个新的函数, 当这个新函数被调用时this键值为其提供的值,其参数列表前几项值为创建时指定的参数序列。语法:fun.bind(thisArg[, arg1[, arg2[, …]]])参数:thisArg调用绑定函数时作为this参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用bind在setTimeout中创建一个函数(作为回调提供)时,作为thisArg传递的任何原始值都将转换为object。如果没有提供绑定的参数,则执行作用域的this被视为新函数的thisArg。arg1, arg2, …当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。返回值返回由指定的this值和初始化参数改造的原函数拷贝。之前也写过一篇文章:面试官问:能否模拟实现JS的bind方法就是利用call和apply指向这个thisArg参数,来模拟实现bind的。感兴趣的读者思考如何实现,再去看看笔者的实现。构造函数调用模式function Student(name){ this.name = name; console.log(this); // {name: ‘轩辕Rowboat’} // 相当于返回了 // return this;}var result = new Student(‘轩辕Rowboat’);使用new操作符调用函数,会自动执行以下步骤。创建了一个全新的对象。这个对象会被执行[[Prototype]](也就是__proto__)链接。生成的新对象会绑定到函数调用的this。通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。由此可以知道:new操作符调用时,this指向生成的新对象。特别提醒一下,new调用时的返回值,如果没有显式返回对象或者函数,才是返回生成的新对象。function Student(name){ this.name = name; // return function f(){}; // return {};}var result = new Student(‘轩辕Rowboat’);console.log(result); {name: ‘轩辕Rowboat’}// 如果返回函数f,则result是函数f,如果是对象{},则result是对象{}很多人或者文章都忽略了这一点,直接简单用typeof判断对象。虽然实际使用时不会显示返回,但面试官会问到。之前也写了一篇文章面试官问:能否模拟实现JS的new操作符,是使用apply来把this指向到生成的新生成的对象上。感兴趣的读者思考如何实现,再去看看笔者的实现。原型链中的调用模式function Student(name){ this.name = name;}var s1 = new Student(‘轩辕Rowboat’);Student.prototype.doSth = function(){ console.log(this.name);}s1.doSth(); // ‘轩辕Rowboat’会发现这个似曾相识。这就是对象上的方法调用模式。自然是指向生成的新对象。如果该对象继承自其它对象。同样会通过原型链查找。上面代码使用ES6中class写法则是:class Student{ constructor(name){ this.name = name; } doSth(){ console.log(this.name); }}let s1 = new Student(‘轩辕Rowboat’);s1.doSth();babel es6转换成es5的结果’use strict’;var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (“value” in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); } }var Student = function () { function Student(name) { _classCallCheck(this, Student); this.name = name; } _createClass(Student, [{ key: ‘doSth’, value: function doSth() { console.log(this.name); } }]); return Student;}();var s1 = new Student(‘轩辕Rowboat’);s1.doSth();由此看出,ES6的class也是通过构造函数模拟实现的,是一种语法糖。箭头函数调用模式先看箭头函数和普通函数的重要区别:1、没有自己的this、super、arguments和new.target绑定。2、不能使用new来调用。3、没有原型对象。4、不可以改变this的绑定。5、形参名称不能重复。箭头函数中没有this绑定,必须通过查找作用域链来决定其值。如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则this的值则被设置为全局对象。比如:var name = ‘window’;var student = { name: ‘轩辕Rowboat’, doSth: function(){ // var self = this; var arrowDoSth = () => { // console.log(self.name); console.log(this.name); } arrowDoSth(); }, arrowDoSth2: () => { console.log(this.name); }}student.doSth(); // ‘轩辕Rowboat’student.arrowDoSth2(); // ‘window’其实就是相当于箭头函数外的this是缓存的该箭头函数上层的普通函数的this。如果没有普通函数,则是全局对象(浏览器中则是window)。也就是说无法通过call、apply、bind绑定箭头函数的this(它自身没有this)。而call、apply、bind可以绑定缓存箭头函数上层的普通函数的this。比如:var student = { name: ‘轩辕Rowboat’, doSth: function(){ console.log(this.name); return () => { console.log(‘arrowFn:’, this.name); } }}var person = { name: ‘person’,}student.doSth().call(person); // ‘轩辕Rowboat’ ‘arrowFn:’ ‘轩辕Rowboat’student.doSth.call(person)(); // ‘person’ ‘arrowFn:’ ‘person’DOM事件处理函数调用addEventerListener、attachEvent、onclick<button class=“button”>onclick</button><ul class=“list”> <li>1</li> <li>2</li> <li>3</li></ul><script> var button = document.querySelector(‘button’); button.onclick = function(ev){ console.log(this); console.log(this === ev.currentTarget); // true } var list = document.querySelector(’.list’); list.addEventListener(‘click’, function(ev){ console.log(this === list); // true console.log(this === ev.currentTarget); // true console.log(this); console.log(ev.target); }, false);</script>onclick和addEventerListener是指向绑定事件的元素。一些浏览器,比如IE6IE8下使用attachEvent,this指向是window。顺便提下:面试官也经常考察ev.currentTarget和ev.target的区别。ev.currentTarget是绑定事件的元素,而ev.target是当前触发事件的元素。比如这里的分别是ul和li。但也可能点击的是ul,这时ev.currentTarget和ev.target就相等了。内联事件处理函数调用<button class=“btn1” onclick=“console.log(this === document.querySelector(’.btn1’))">点我呀</button><button onclick=“console.log((function(){return this})());">再点我呀</button>第一个是button本身,所以是true,第二个是window。这里跟严格模式没有关系。当然我们现在不会这样用了,但有时不小心写成了这样,也需要了解。其实this的使用场景还有挺多,比如对象object中的getter、setter的this,new Function()、eval。但掌握以上几种,去分析其他的,就自然迎刃而解了。使用比较多的还是普通函数调用、对象的函数调用、new调用、call、apply、bind调用、箭头函数调用。那么他们的优先级是怎样的呢。优先级而箭头函数的this是上层普通函数的this或者是全局对象(浏览器中是window),所以排除,不算优先级。var name = ‘window’;var person = { name: ‘person’,}var doSth = function(){ console.log(this.name); return function(){ console.log(‘return:’, this.name); }}var Student = { name: ‘轩辕Rowboat’, doSth: doSth,}// 普通函数调用doSth(); // window// 对象上的函数调用Student.doSth(); // ‘轩辕Rowboat’// call、apply 调用Student.doSth.call(person); // ‘person’new Student.doSth.call(person);试想一下,如果是Student.doSth.call(person)先执行的情况下,那new执行一个函数。是没有问题的。然而事实上,这代码是报错的。运算符优先级是new比点号低,所以是执行new (Student.doSth.call)(person)而Function.prototype.call,虽然是一个函数(apply、bind也是函数),跟箭头函数一样,不能用new调用。所以报错了。Uncaught TypeError: Student.doSth.call is not a constructor这是因为函数内部有两个不同的方法:[[Call]]和[[Constructor]]。当使用普通函数调用时,[[Call]]会被执行。当使用构造函数调用时,[[Constructor]]会被执行。call、apply、bind和箭头函数内部没有[[Constructor]]方法。从上面的例子可以看出普通函数调用优先级最低,其次是对象上的函数。call(apply、bind)调用方式和new调用方式的优先级,在《你不知道的JavaScript》是对比bind和new,引用了mdn的bind的ployfill实现,new调用时bind之后的函数,会忽略bind绑定的第一个参数,(mdn的实现其实还有一些问题,感兴趣的读者,可以看我之前的文章:面试官问:能否模拟实现JS的bind方法),说明new的调用的优先级最高。所以它们的优先级是new 调用 > call、apply、bind 调用 > 对象上的函数调用 > 普通函数调用。总结如果要判断一个运行中函数的 this 绑定, 就需要找到这个函数的直接调用位置。 找到之后就可以顺序应用下面这四条规则来判断 this 的绑定对象。new 调用:绑定到新创建的对象,注意:显示return函数或对象,返回值不是新创建的对象,而是显式返回的函数或对象。call 或者 apply( 或者 bind) 调用:严格模式下,绑定到指定的第一个参数。非严格模式下,null和undefined,指向全局对象(浏览器中是window),其余值指向被new Object()包装的对象。对象上的函数调用:绑定到那个对象。普通函数: 在严格模式下绑定到 undefined,否则绑定到全局对象。ES6 中的箭头函数:不会使用上文的四条标准的绑定规则, 而是根据当前的词法作用域来决定this, 具体来说, 箭头函数会继承外层函数,调用的 this 绑定( 无论 this 绑定到什么),没有外层函数,则是绑定到全局对象(浏览器中是window)。 这其实和 ES6 之前代码中的 self = this 机制一样。DOM事件函数:一般指向绑定事件的DOM元素,但有些情况绑定到全局对象(比如IE6IE8的attachEvent)。一定要注意,有些调用可能在无意中使用普通函数绑定规则。 如果想“ 更安全” 地忽略 this 绑定, 你可以使用一个对象, 比如 ø = Object.create(null), 以保护全局对象。面试官考察this指向就可以考察new、call、apply、bind,箭头函数等用法。从而扩展到作用域、闭包、原型链、继承、严格模式等。这就是面试官乐此不疲的原因。读者发现有不妥或可改善之处,欢迎指出。另外觉得写得不错,可以点个赞,也是对笔者的一种支持。考题this指向考题经常结合一些运算符等来考察。看完本文,不妨通过以下两篇面试题测试一下。小小沧海:一道常被人轻视的前端JS面试题从这两套题,重新认识JS的this、作用域、闭包、对象扩展阅读你不知道的JavaScript 上卷冴羽:JavaScript深入之从ECMAScript规范解读this这波能反杀:前端基础进阶(五):全方位解读this关于作者:常以轩辕Rowboat为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。个人博客segmentfault前端视野专栏,开通了前端视野专栏,欢迎关注掘金个人主页,欢迎关注知乎前端视野专栏,开通了前端视野专栏,欢迎关注github ...

December 25, 2018 · 3 min · jiezi