前言:初心是记录面试题,慢慢由每个知识点引伸出去,逐渐查缺补漏,构建出更完善的前端知识系统。
题目来源:牛客网 gitnub
目录
- HTTP协议
- 作用域和闭包
- 说一下类的创建和继承
- 异步回调
- 事件
- 同源和跨域
- 前端性能优化
- 鼠标事件
- this指向
- JS基础
- 数组
- 字符串
- ES6中添加了哪些
- Ajax解决浏览器缓存问题
- eval是做什么的
- 浅拷贝与深拷贝
- 实现一个once函数,传入函数参数只执行一次
- 定时器
- 实现一个两列等高布局,讲讲思路
- ant-design优点和缺点
- vue的生命周期
- 简单介绍一下symbol
- 待完善
- Function._proto_(getPrototypeOf)是什么?
- 实现js中所有对象的深度克隆(包装对象,Date对象,正则对象)
- 简单实现Node的Events模块
- js怎么控制一次加载一张图片,加载完后再加载下一张
- 用setTimeout来实现setInterval
- AngularJS双向绑定原理
- JS的全排列
- 用闭包写个单例模式
- 在数组原型链上实现删除数组重复数据的方法
- 写个函数,可以转化下划线命名到驼峰命名
- 给两个构造函数A和B,如何实现A继承B?
- 如果已经有三个promise,A、B和C,想串行执行,该怎么写?
- JavaScript中的轮播实现原理?假如一个页面上有两个轮播,你会怎么实现?
- 怎么实现一个计算一年中有多少周?
- assign的深拷贝
- 怎么用原生的js实现jquery的一个特定方法
HTTP协议
get请求传参长度的误区
HTTP 协议 未规定 GET 和POST的长度限制
GET的最大长度显示是因为 浏览器和 web服务器限制了 URI的长度,不同的浏览器略有不同。如IE,则最大长度为2083byte,Chrome,则最大长度 8182byte
补充get和post请求在缓存方面的区别
get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
post不同,post做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适合于请求缓存。
get和post的区别
1、浏览器在回退时,get不会重新请求,但是post会重新请求。【重要】
2、get请求会被浏览器主动缓存,而post不会。【重要】
3、get请求的参数,会报保留在浏览器的历史记录里,而post不会。
4、get请求在url中传递的参数有大小限制,不同的浏览器略有不同。而post无限制规定。
5、get的参数是直接暴露在url上的,相对不安全。而post是放在请求体中的。
详细
重排和重绘,讲讲看
重绘(repaint或redraw):当盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来之后,浏览器便把这些原色都按照各自的特性绘制一遍,将内容呈现在页面上。重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。
触发重绘的条件:改变元素外观属性。如:color,background-color等。
注意:table及其内部元素可能需要多次计算才能确定好其在渲染树中节点的属性值,比同等元素要多花两倍时间,这就是我们尽量避免使用table布局页面的原因之一。
重排(重构/回流/reflow):当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建, 这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。
重绘和重排的关系:在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘。所以,重排必定会引发重绘,但重绘不一定会引发重排。
作用域和闭包
闭包是什么
闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用,子函数所在的父函数的作用域不会被释放。
闭包的作用
作用1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
作用2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
说一下类的创建和继承
类的创建
方式一:用构造函数模拟类(传统写法)
function Animal1() {
this.name = 'smyhvae'; //通过this,表明这是一个构造函数
}
方式二:用 class 声明(ES6的写法)
class Animal2 {
constructor() { //可以在构造函数里写属性
this.name = name;
}
}
类的继承
方式一:借助构造函数
function Parent1() {
this.name = 'parent1 的属性';
}
function Child1() {
Parent1.call(this); //【重要】此处用 call 或 apply 都行:改变 this 的指向
this.type = 'child1 的属性';
}
console.log(new Child1);
特点:可以实现多继承``
缺点:只能继承父类实例的属性和方法,不能继承原型上的属性和方法.``
方法二:通过原型链实现继承
/*
通过原型链实现继承
*/
function Parent() {
this.name = 'Parent 的属性';
}
function Child() {
this.type = 'Child 的属性';
}
Child.prototype = new Parent(); //【重要】
console.log(new Child());
特点:基于原型链,既是父类的实例,也是子类的实例。
缺点:无法实现多继承
方式三:组合的方式:构造函数 + 原型链
/*
组合方式实现继承:构造函数、原型链
*/
function Parent3() {
this.name = 'Parent 的属性';
this.arr = [1, 2, 3];
}
function Child3() {
Parent3.call(this); //【重要1】执行 parent方法
this.type = 'Child 的属性';
}
Child3.prototype = new Parent3(); //【重要2】第二次执行parent方法
var child = new Child3();
特点:可以继承实例属性/方法,也可以继承原型属性/方法
缺点:调用了两次父类构造函数,生成了两份实例
方法四:实例继承和拷贝继承
实例继承:为父类实例添加新特性,作为子类实例返回
拷贝继承:拷贝父类元素上的属性和方法
方法五:寄生组合继承
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
(function(){
// 创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
特点:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性
class
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
如何实现一个私有变量,用getName方法可以访问,不能直接访问
方式一:函数
function private(){
let a='私有变量';
this.getName=function(){
return a;
}
}
let obj=new private();
console.log(obj.a) //undefine
console.log(obj.getName()); //私有变量
方式二:类构造器
class private2 {
constructor(){
let a='class私有';
this.getName=function(){
return a;
}
}
}
let p=new private2();
console.log(p.a); //undefine
console.log(p.getName());//class私有
异步回调
如何解决异步回调地狱
promise、generator、async/await
介绍一下promise,及其底层如何实现
Promise是一个对象,保存着未来将要结束的事件,她有两个特征:
1、对象的状态不受外部影响,Promise对象代表一个异步操作,有三种状态,pending进行中,fulfilled已成功,rejected已失败,只有异步操作的结果,才可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也就是promise名字的由来
2、一旦状态改变,就不会再变,promise对象状态改变只有两种可能,从pending改到fulfilled或者从pending改到rejected,只要这两种情况发生,状态就凝固了,不会再改变,这个时候就称为定型resolved
promise+Generator+Async的使用
async/await (异步函数)概述
async/await 是在 ES8(即ES 2017)中引入的新语法,是另外一种异步编程解决方案。
本质: 是 Generator 的语法糖。
async 的返回值是 Promise 实例对象。
await 可以得到异步结果。
我们在普通的函数前面加上 async 关键字,就成了 async 函数。
async的基本用法
const request1 = function() {
const promise = new Promise(resolve => {
request('https://www.baidu.com/xxx_url', function(response) {
if (response.retCode == 200) {
// 这里的 response 是接口1的返回结果
resolve('request1 success'+ response);
} else {
reject('接口请求失败');
}
});
});
return promise;
};
async function queryData() {
const response = await request1();
});
return response;
}
queryData().then(data => {
console.log(data);
});
Promise、async...await、Generator的对比 我们在使用 Promise、async...await、Generator 的时候,返回的都是 Promise 的实例。 如果直接使用 Promise,则需要通过 then 来进行链式调用;如果使用 async...await、Generator,写起来更像同步的代码
generateor待完善
简单的实现一个promise
function myPromise(constructor){
let self=this;
self.status="pending" //定义状态改变前的初始状态
self.value=undefined;//定义状态为resolved的时候的状态
self.reason=undefined;//定义状态为rejected的时候的状态
function resolve(value){
//两个==="pending",保证了状态的改变是不可逆的
if(self.status==="pending"){
self.value=value;
self.status="resolved";
}
}
function reject(reason){
//两个==="pending",保证了状态的改变是不可逆的
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
}
}
//捕获构造异常
try{
constructor(resolve,reject);
}catch(e){
reject(e);
}
}
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
将原生的ajax封装成promise
let ajaxPromise = new Promise(resolve, reject){
let xhr = new XMLHttpRequest(); // 创建XMLHttpRequest对象
xhr.open(method , url, async); //创建请求
//method:请求方法 'GET'/'POST'
//url: 请求路径
//async:规定异步(true)或者同步(false)处理
xhr.send() // 发送请求
xhr.onreadystatechange=function(res){ //监听readyState的变化
// 0: 请求未初始化(代理被创建,但尚未调用 open() 方法)
// 1: 服务器连接已建立(open方法已经被调用)
// 2: 请求已接收(send方法已经被调用,并且头部和状态已经可获得)
// 3: 请求处理中(下载中,responseText 属性已经包含部分数据)
// 4: 请求已完成,且响应已就绪(下载操作已完成)
if(xhr.readyState==4){
resolve()
}else{
reject()
}
}
}
setTimeout和Promise的执行顺序
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function(resolve, reject) {
console.log(2)
for (var i = 0; i < 10000; i++) {
if(i === 10) {console.log(10)}
i == 9999 && resolve();
}
console.log(3)
}).then(function() {
console.log(4)
})
console.log(5);
输出答案为2 10 3 5 4 1
执行顺序为:同步执行的代码-》promise.then->settimeout
事件
说一下什么是virtual dom
用JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异 把所记录的差异应用到所构建的真正的DOM树上,视图就更新了。Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。
说说前端中的事件流
HTML中与javascript交互是通过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件onscroll等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念。
什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2级事件流包括下面几个阶段。
事件捕获阶段
处于目标阶段
事件冒泡阶段
addEventListener:addEventListener 是DOM2 级事件新增的指定事件处理程序的操作,这个方法接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
详细
DOM0级和DOM2级有什么区别,DOM的分级是什么
在事件绑定上的差异
DOM0:一个元素的一个事件只能绑定一个响应函数。如果绑定了多个响应函数,则后者会覆盖前者。
DOM2:addEventListener(高版本浏览器)一个元素的一个事件,可以绑定多个响应函数。不存在响应函数被覆盖的情况。执行顺序是:事件被触发时,响应函数会按照函数的绑定顺序执行。
attachEvent(IE8及以下版本浏览器)一个元素的一个事件,可以绑定多个响应函数。不存在响应函数被覆盖的情况。注意:执行顺序是,后绑定的先执行。
如何让事件先冒泡后捕获
在DOM标准事件模型中,是先捕获后冒泡。但是如果要实现先冒泡后捕获的效果,对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后再执行捕获事件。
说一下事件委托
事件委托指的是,不在事件的发生地(直接dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素DOM的类型,来做出不同的响应。详细
举例:最经典的就是ul和li标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在li标签上直接添加,而是在ul父元素上添加。
好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。
事件代理(委托)在捕获阶段的实际应用
可以在父元素层面阻止事件向子元素传播,也可代替子元素执行某些操作。
什么是事件监听
element.addEventListener('click', function () {
}, false);
参数解释:
参数1:事件名的字符串(注意,没有on)
参数2:回调函数:当事件触发时,该函数会被执行
参数3:true表示捕获阶段触发,false表示冒泡阶段触发(默认)。如果不写,则默认为false。【重要】
js监听对象属性的改变
我们假设这里有一个user对象,
(1)在ES5中可以通过Object.defineProperty来实现已有属性的监听
Object.defineProperty(user,'name',{
set:function(key,value){
}
})
缺点:如果id不在user对象中,则不能监听id的变化
(2)在ES6中可以通过Proxy来实现
var user = new Proxy({},{
set:function(target,key,value,receiver){
}
})
Eventloop
- 任务队列
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
总结:只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。【重要】- Event Loop
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
这样即使有属性在user中不存在,通过user.id来定义也同样可以这样监听这个属性的变化
给出以下代码,输出的结果是什么?原因? for(var i=0;i<5;i++) { setTimeout(function(){ console.log(i); },1000); } console.log(i)
在一秒后输出5个5
每次for循环的时候setTimeout都会执行,但是里面的function则不会执行被放入任务队列,因此放了5次;for循环的5次执行完之后不到1000毫秒;1000毫秒后全部执行任务队列中的函数,所以就是输出5个5。
同源和跨域
跨域的原理
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制,那么只要协议、域名、端口有任何一个不同,都被当作是不同的域。跨域原理,即是通过各种方式,避开浏览器的安全限制。
JS实现跨域
- JSONP:通过动态创建script,再请求一个带参网址实现跨域通信。document.domain + iframe跨域:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
location.hash + iframe跨域:a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。- window.name + iframe跨域:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。
- postMessage跨域:可以跨域操作的window属性之一。
- CORS:服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求,前后端都需要设置。
- 代理跨域:启一个代理服务器,实现数据的转发
前端性能优化
减少HTTP请求
使用内容发布网络(CDN)
添加本地缓存
压缩资源文件
将CSS样式表放在顶部,把javascript放在底部(浏览器的运行机制决定)
避免使用CSS表达式
减少DNS查询
使用外部javascript和CSS
避免重定向
图片lazyLoad
说一下图片的懒加载和预加载
预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。详细
两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。 懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
什么是按需加载
当用户触发了动作时才加载对应的功能。触发的动作,是要看具体的业务场景而言,包括但不限于以下几个情况:鼠标点击、输入文字、拉动滚动条,鼠标移动、窗口大小更改等。加载的文件,可以是JS、图片、CSS、HTML等。
异步加载js的方法
动态脚本加载
使用document.createElement创建一个script标签,即document.createElement(‘script’),然后把这个标签加载到body上面去。
defer
只支持IE如果您的脚本不会改变文档的内容,可将 defer 属性加入到< script >标签中,以便加快处理文档的速度。
<script src="./defer1.js" defer></script>
因为浏览器知道它将能够安全地读取文档的剩余部分而不用执行脚本,它将推迟对脚本的解释,直到文档已经显示给用户为止。
async HTmL5新增特性。
<script src="./async1.js" async></script>
并且如果在IE中,同时存在defer和async,那么defer的优先级比较高,脚本将在页面完成时执行。
defer和async的区别
js的节流和防抖
函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。如下图,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。
函数节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。如下图,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。
JS中的垃圾回收机制
- 引用计数垃圾收集
在内存管理的环境中,一个对象如果有访问另一个对象的权(隐式或者显式),叫做一个对象引用另一个对象。
引用计数算法定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。 如果没有其他对象指向它了,说明该对象已经不再需了。
引用计数方法存在一个致命的问题:循环引用。如果两个对象相互引用,尽管它们已不再使用,垃圾回收不会进行回收,导致内存泄漏。
- 标记清除垃圾收集
标记清除,简单来说,就是从根部(在js中就是全局对象)出发,定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用后。哪些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
可以解决循环引用问题。
如何理解前端模块化
前端模块化就是复杂的文件编程一个一个独立的模块,比如js文件等等,分成独立的模块有利于重用(复用性)和维护(版本迭代),这样会引来模块之间相互依赖的问题,所以有了commonJS规范,AMD,CMD规范等等,以及用于js打包(编译等处理)的工具webpack
说一下Commonjs、AMD和CMD
- 服务器端规范:
CommonJS规范
是 Node.js 使用的模块化规范。开始于服务器端的模块化,同步定义的模块化,每个模块都是一个单独的作用域,模块输出,modules.exports,模块加载require()引入模块。
- 浏览器端规范:
AMD规范
是 RequireJS 在推广过程中对模块化定义的规范化产出。
- 异步加载模块;
- 依赖前置、提前执行:require([`foo`,`bar`],function(foo,bar){}); //也就是说把所有的包都 require 成功,再继续执行代码。
- define 定义模块:define([`require`,`foo`],function(){return});
CMD规范
是 SeaJS 在推广过程中对模块化定义的规范化产出。淘宝团队开发。
-依赖就近,延迟执行:require(./a) 直接引入。或者Require.async 异步引入。 //依赖就近:执行到这一部分的时候,再去加载对应的文件。
-define 定义模块, export 导出:define(function(require, export, module){});
webpack用来干什么的
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle。
鼠标事件
mouseover和mouseenter的区别
mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件是mouseout
mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是mouseleave
clientHeight,scrollHeight,offsetHeight ,以及scrollTop, offsetTop,clientTop的区别
clientHeight:表示的是可视区域的高度,不包含border和滚动条
offsetHeight:表示可视区域的高度,包含了border和滚动条
scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分。
clientTop:表示边框border的厚度,在未指定的情况下一般为0
scrollTop:滚动后被隐藏的高度,获取对象相对于由offsetParent属性指定的父坐标(css定位的元素或body元素)距离顶端的高度。client(可视区)相关属性
js拖拽功能的实现
首先是三个事件,分别是mousedown,mousemove,mouseup鼠标拖拽事件
clientX,clientY标识的是鼠标的坐标,分别标识横坐标和纵坐标,并且我们用offsetX和offsetY来表示元素的元素的初始坐标
移动的举例应该是:
鼠标移动时候的坐标-鼠标按下去时候的坐标。
也就是说定位信息为:
鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始情况下的offetLeft.
补充:也可以通过html5的拖放(Drag 和 drop)
H5中的拖拽
this指向
箭头函数中this指向举例
var a=11;
function test2(){
this.a=22;
let b=()=>{console.log(this.a)}
b();
}
var x=new test2();
//输出22
箭头函数中 this 的指向 ES6 中的箭头函数并不会使用上面的准则,而是会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。
箭头函数和function有什么区别
箭头函数根本就没有绑定自己的this,在箭头函数中调用 this 时,仅仅是简单的沿着作用域链向上寻找,找到最近的一个 this 拿来使用
改变函数内部this指针的指向函数(bind,apply,call的区别)
通过apply和call改变函数的this指向,他们两个函数的第一个参数都是一样的表示要改变指向的那个对象,第二个参数,apply是数组,而call则是arg1,arg2…这种形式。通过bind改变this作用域会返回一个新的函数,这个函数不会马上执行。
call apply bind的区别
call apply bind的方法作用与基本使用
自己实现一个bind函数
Function.prototype.bind_ = function(ctx) {
const self = this //暂存this
const args_1 = Array.prototype.slice.call(arguments,1)
const newFun = function() {}
function newBind() {
const args_2 = Array.prototype.slice.call(arguments)
const reallyCtx = this instanceof newFun ? this : ctx
return self.apply(reallyCtx,args_1.concat(args_2))
}
newFun.prototype = this.prototype
newBind.prototype = new newFun()
return newBind
JS基础
Js基本数据类型
基本数据类型(值类型):String 字符串、Number 数值、BigInt 大型数值、Boolean 布尔值、Null 空值、Undefined 未定义、Symbol。
引用类型常见的对象
Object、Array、RegExp、Date、Function、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)等
不同数据类型的值的比较,是怎么转换的,有什么规则
待完善
== 和 ===、以及Object.js的区别
符号的强调注意==这个符号,它是判断是否等于,而不是赋值。
(1)==这个符号,还可以验证字符串是否相同。例如:
console.log('我爱你中国' == '我爱你中国'); // 输出结果为true
(2)==这个符号并不严谨,会做隐式转换,将不同的数据类型,转为相同类型进行比较(大部分情况下,都是转换为数字)。例如:
console.log('6' == 6); // 打印结果:true。这里的字符串"6"会先转换为数字6,然后再进行比较
console.log(true == '1'); // 打印结果:true
console.log(0 == -0); // 打印结果:true
console.log(null == 0); // 打印结果:false
(3)undefined 衍生自 null,所以这两个值做相等判断时,会返回 true。
console.log(undefined == null); //打印结果:true。
(4)NaN 不和任何值相等,包括他本身。
console.log(NaN == NaN); //false
console.log(NaN === NaN); //false
全等符号的强调
全等在比较时,不会做类型转换。如果要保证绝对等于(完全等于),我们就要用三个等号===。例如:
console.log('6' === 6); //false
console.log(6 === 6); //true
上述内容分析出:
==两个等号,不严谨,"6"和 6 是 true。
===三个等号,严谨,"6"和 6 是 false。
Object.js
主要区别就是+0!=-0而NaN=NaN
(相对于= =以及= = =的改进)
怎么获得对象上的属性:比如说通过Object.key()
从ES5开始,有三种方法可以列出对象的属性
for(let I in obj)该方法依次访问一个对象及其原型链中所有可枚举的类型
object.keys:返回一个数组,包括所有可枚举的属性名称
object.getOwnPropertyNames:返回一个数组包含不可枚举的属性
JS的命名方式
在 ES6 语法之前,统一使用var关键字来声明一个变量。
在 ES6 语法及之后的版本里,可以使用 const、let关键字来定义一个变量
如果你想定义一个常量,就用 const;如果你想定义一个变量,就用 let。
let const var的区别,什么是块级作用域,如何用ES5的方法实现块级作用域(立即执行函数),ES6 呢
使用 var 关键字声明的变量( 比如 var a = 1),会在所有的代码执行之前被(但是不会赋值),但是如果声明变量时不是用 var 关键字(比如直接写a = 1),则变量不会被声明提前。
JavaScript 没有块级作用域(ES6 之前)
在其他编程语言中(如 Java、C#等),存在块级作用域,由{}包括起来。比如在 Java 语言中,if 语句里创建的变量,只能在 if 语句内部使用:
if(true){
int num = 123;
system.out.print(num); // 123
}
system.out.print(num); // 报错
但是,在 JS 中没有块级作用域(ES6 之前)。举例如下:
if (true) {
var num = 123;
console.log(123); //123
}
console.log(123); //123(可以正常打印)
立即执行函数如下:
(function(a, b) {
console.log("a = " + a);
console.log("b = " + b);
})(123, 456);
立即执行函数:函数定义完,立即被调用,这种函数叫做立即执行函数。
立即执行函数往往只会执行一次。为什么呢?因为没有变量保存它,执行完了之后,就找不到它了。
待完善
暂停死区
在代码块内,使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
能来讲讲JS的语言特性吗
运行在客户端浏览器上;
不用预编译,直接解析执行代码;
是弱类型语言,较为灵活;
与操作系统无关,跨平台的语言;
脚本语言、解释性语言
说说C++,Java,JavaScript这三种语言的区别
从静态类型还是动态类型来看
比如,JS 是动态类型的语言,变量只需要用 var/let/const 来声明。而 Java 中变量的声明,要根据变量的类型来定义。数据分类
从编译型还是解释型来看
- 编译型语言
定义:需要事先通过编译器把所有的代码一次性翻译(编译/转换)好,然后整体执行。比如 exe 文件。
优点:执行效率高,运行更快。
不足:移植性不好,不跨平台;编译之后如果需要修改就需要整个模块重新编译。
编译型语言举例:C、C++
比如说,c 语言的代码文件是.c后缀,翻译之后文件是.obj后缀,系统执行的是 obj 文件;再比如, java 语言的代码文件是.java后缀,翻译之后的文件是.class后缀。
- 解释型语言
定义:在运行过程中(runtime)通过解释器边翻译边执行(也就是逐行翻译:翻译一行,执行一行),不需要事先一次性翻译。
优点:移植性好,跨平台。
缺点:运行速度不如编译型语言。
解释型语言举例:JavaScript、PHP、Python。
Java 语言是属于**半编译半解释**语言。翻译过程: (1)编译:.java代码文件先通过 javac 命令编译成.class文件。 (2)执行:.class文件再通过 jvm 虚拟机,解释执行。有了 jvm 的存在,让 java 跨平台了。
js判断类型
type of()
instance of
Object.prototype.toString.call()
如何判断一个数组
Object.prototype.call.toString()
instanceof
typeof只能判断是object,数组是特殊的obj
数组
数组常用方法
push(),pop(),shift(),unshift(),splice(),sort(),reverse(),map()等
数组去重
splice():数组去重
代码实现:
//创建一个数组
var arr = [1, 2, 3, 2, 2, 1, 3, 4, 2, 5];
//去除数组中重复的数字
//获取数组中的每一个元素
for (var i = 0; i < arr.length; i++) {
//console.log(arr[i]);
/*获取当前元素后的所有元素*/
for (var j = i + 1; j < arr.length; j++) {
//console.log("---->"+arr[j]);
//判断两个元素的值是否相等
if (arr[i] == arr[j]) {
//如果相等则证明出现了重复的元素,则删除j对应的元素
arr.splice(j, 1);
//当删除了当前j所在的元素以后,后边的元素会自动补位
//此时将不会在比较这个元素,我需要再比较一次j所在位置的元素
//使j自减
j--;
}
}
}
console.log(arr);
问题:编写一个方法去掉一个数组中的重复元素。
分析:创建一个新数组,循环遍历,只要新数组中有老数组的值,就不用再添加了。
答案:
// 编写一个方法 去掉一个数组的重复元素
var arr = [1, 2, 3, 4, 5, 2, 3, 4];
console.log(arr);
var aaa = fn(arr);
console.log(aaa);
//思路:创建一个新数组,循环遍历,只要新数组中有老数组的值,就不用再添加了。
function fn(array) {
var newArr = [];
for (var i = 0; i < array.length; i++) {
//开闭原则
var bool = true;
//每次都要判断新数组中是否有旧数组中的值。
for (var j = 0; j < newArr.length; j++) {
if (array[i] === newArr[j]) {
bool = false;
}
}
if (bool) {
newArr[newArr.length] = array[i];
}
}
return newArr;
}
类数组 arguments
当我们不确定有多少个参数传递的时候,可以用 arguments 来获取。在 JavaScript 中,arguments 实际上是当前函数的一个内置对象。所有函数都内置了一个 arguments 对象(只有函数才有 arguments 对象),arguments 对象中存储了传递的所有实参.
arguments的展示形式是一个伪数组。伪数组具有以下特点:
可以进行遍历;具有数组的 length 属性。
按索引方式存储数据。
不具有数组的 push()、pop() 等方法。可用Array.from()转换
箭头函数获取arguments
可用…rest参数获取
字符串
去除字符串首尾空格
使用正则(^\s*)|(\s*$)即可
trim()
trim():去除字符串前后的空白。
JS中string的startwith和indexof两种方法的区别
- startsWith():字符串是否以指定的内容开头
语法:
布尔值 = str.startsWith(想要查找的内容, [position]);
解释:判断一个字符串是否以指定的子字符串开头。如果是,则返回 true;否则返回 false。
参数中的position:
如果不指定,则默认为0。
如果指定,则规定了检索的起始位置。检索的范围包括:这个指定位置开始,直到字符串的末尾。即:[position, str.length)
- indexOf():获取字符串中指定内容的索引
语法 1:
索引值 = str.indexOf(想要查询的字符串);
备注:indexOf() 是从前向后查找字符串的位置。同理,lastIndexOf()是从后向前寻找。
解释:可以检索一个字符串中是否含有指定内容。如果字符串中含有该内容,则会返回其第一次出现的索引;如果没有找到指定的内容,则返回 -1。
这个方法还可以指定第二个参数,用来指定查找的起始位置。语法如下:
索引值 = str.indexOf(想要查询的字符串, [起始位置]);
js字符串转数字的方法
1、使用 Number() 函数
字符串 --> 数字
(1)如果字符串中是纯数字,则直接将其转换为数字。
(2)如果字符串是一个空串或者是一个全是空格的字符串,则转换为 0。
(3)只要字符串中包含了其他非数字的内容(小数点按数字来算),则转换为 NaN。怎么理解这里的 NaN 呢?可以这样理解,使用 Number() 函数之后,如果无法转换为数字,就会转换为 NaN。
2、隐式类型转换:正负号 +a、-a
任何值做+a、-a运算时, 内部调用的是 Number() 函数。不会改变原数值。
3、使用 parseInt()函数:字符串 -> 整数
parseInt():将传入的数据当作字符串来处理,从左至右提取数值, 一旦遇到非数值就立即停止;停止时如何还没有提取到数值, 那么就返回NaN。
按照上面的规律,parseInt()的转换情况如下。
(1)只保留字符串最开头的数字,后面的中文自动消失。
(2)如果字符串不是以数字开头,则转换为 NaN。
(3)如果字符串是一个空串或者是一个全是空格的字符串,转换时会报错。
4、parseFloat()函数:字符串 --> 浮点数(小数)
其他数据类型转换为number
ES6中添加了哪些
ES6 的改进如下:
ES6 之前的变量提升,会导致程序在运行时有一些不可预测性。而 ES6 中通过 let、const 变量优化了这一点。
ES6 增加了很多功能,比如:常量、作用域、对象代理、异步处理、类、继承等。这些在 ES5 中想实现,比较复杂,但是 ES6 对它们进行了封装。
ES6 之前的语法过于松散,实现相同的功能,不同的人可能会写出不同的代码。
ES6 的目标是:让 JS 语言可以编写复杂的大型应用程序,成为企业级开发语言。
简单讲一讲ES6的一些新特性
ES6在变量的声明和定义方面增加了let、const声明变量,有局部变量的概念,赋值中有比较吸引人的结构赋值,同时ES6对字符串、 数组、正则、对象、函数等拓展了一些方法,如字符串方面的模板字符串、函数方面的默认参数、对象方面属性的简洁表达方式,ES6也 引入了新的数据类型symbol,新的数据结构set和map,symbol可以通过typeof检测出来,为解决异步回调问题,引入了promise和 generator,还有最为吸引人了实现Class和模块,通过Class可以更好的面向对象编程,使用模块加载方便模块化编程,当然考虑到 浏览器兼容性,我们在实际开发中需要使用babel进行编译
重要的特性:
块级作用域:ES5只有全局作用域和函数作用域,块级作用域的好处是不再需要立即执行的函数表达式,循环体中的闭包不再有问题
rest参数:用于获取函数的多余参数,这样就不需要使用arguments对象了,
promise:一种异步编程的解决方案,比传统的解决方案回调函数和事件更合理强大
模块化:其模块功能主要有两个命令构成,export和import,export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能
Ajax解决浏览器缓存问题
- 在ajax发送请求前加上 anyAjaxObj.setRequestHeader(“If-Modified-Since”,“0”)。
- 在ajax发送请求前加上 anyAjaxObj.setRequestHeader(“Cache-Control”,“no-cache”)。
- 在URL后面加上一个随机数: “fresh=” + Math.random()。
- 在URL后面加上时间搓:“nowtime=” + new Date().getTime()。
如果是使用jQuery,直接这样就可以了 $.ajaxSetup({cache:false})。这样页面的所有ajax都会执行这条语句就是不需要保存缓存记录。
解决Ajax中IE浏览器缓存问题
eval是做什么的
它的功能是将对应的字符串解析成js并执行,应该避免使用js,因为非常消耗性能(2次,一次解析成js,一次执行)
浅拷贝与深拷贝
浅拷贝:只拷贝最外面一层的数据;更深层次的对象,只拷贝引用。
深拷贝:拷贝多层数据;每一层级别的数据都会拷贝。
拷贝引用的时候,是属于传址,而非传值。深拷贝会把对象里所有的数据重新复制到新的内存空间,是最彻底的拷贝。
- 浅拷贝的实现方式Object.assgin()
const obj1 = {
name: 'qianguyihao',
age: 28,
info: {
desc: 'hello',
},
};
// 浅拷贝:把 obj1 拷贝给 obj2。如果 obj1 只有一层数据,那么,obj1 和 obj2 则互不影响
const obj2 = Object.assign({}, obj1);
console.log('obj2:' + JSON.stringify(obj2));
obj1.info.desc = '永不止步'; // 由于 Object.assign() 只是浅拷贝,所以当修改 obj1 的第二层数据时,obj2 对应的值也会被改变。
console.log('obj2:' + JSON.stringify(obj2));
obj2.info 属性,跟 obj1.info属性,它俩指向的是同一个堆内存地址。所以,当我修改 obj1.info 里的值之后,obj2.info的值也会被修改
浅拷贝和深拷贝
对象深度克隆的简单实现
function deepClone(obj){
var newObj= obj instanceof Array ? []:{};
for(var item in obj){
var temple= typeof obj[item] == 'object' ? deepClone(obj[item]):obj[item];
newObj[item] = temple;
}
return newObj;
}
ES5的常用的对象克隆的一种方式。注意数组是对象,但是跟对象又有一定区别,所以我们一开始判断了一些类型,决定newObj是对象还是数组
深度克隆
实现一个once函数,传入函数参数只执行一次
function ones(func){
var tag=true;
return function(){
if(tag==true){
func.apply(null,arguments);
tag=false;
}
return undefined
}
}
定时器
setTimeout、setInterval和requestAnimationFrame之间的区别
setInterval():循环调用。将一段代码,每隔一段时间执行一次。(循环执行)
setTimeout():延时调用。将一段代码,等待一段时间之后再执行。(只执行一次)
requestAnimationFrame的用法与settimeout很相似,只是不需要设置时间间隔而已。RAF采用的是系统时间间隔,不会因为前面的任务,影响RAF,但是如果前面的任务多的话,
会响应setTimeout和setInterval真正运行时的时间间隔。
setTimeout setInterval RAF三者的区别
RAF详细
ST SI详细
代码执行顺序
setTimeout(function(){console.log(1)},0);
new Promise(function(resolve,reject){
console.log(2);
resolve();
}).then(function(){console.log(3)
}).then(function(){console.log(4)});
process.nextTick(function(){console.log(5)});
console.log(6);
//输出2,6,5,3,4,1
从promise、process.nextTick、setTimeout出发,谈谈Event Loop中的Job queue
实现一个两列等高布局,讲讲思路
为了实现两列等高,可以给每列加上 padding-bottom:9999px;
margin-bottom:-9999px;同时父元素设置overflow:hidden;
知道private和public吗
public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用
private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用
ant-design优点和缺点
优点:组件非常全面,样式效果也都比较不错。
缺点:框架自定义程度低,默认UI风格修改困难。
vue的生命周期
vue实例的生命周期:从Vue实例创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期。
1、创建期间的生命周期函数
- beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
- created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板。我们可以在这里进行Ajax请求。
- beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中
- mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示。(mounted之后,表示真实DOM渲染完了,可以操作DOM了)
2、创建期间的生命周期函数
- beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点
- updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了。
PS:数据发生变化时,会触发这两个方法。不过,我们一般用watch来做。
3、销毁期间的生命周期函数
- beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
- destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
PS:可以在beforeDestroy里清除定时器、或清除事件绑定
简单介绍一下symbol
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。
Symbol属性对应的值是唯一的,解决命名冲突问题
Symbol值不能与其他数据进行计算,包括同字符串拼串
for in、for of 遍历时不会遍历Symbol属性。
js原型链,原型链的顶端是什么?Object的原型是什么?Object的原型的原型是什么?
待完善
js的new操作符做了哪些事情
new 操作符新建了一个空对象,这个对象原型指向构造函数的prototype,执行构造函数后返回这个对象。
new运算符