JS简介
JavaScript是一种具有函数优先的轻量级,解释型,或解释编译型的编程语言 编译型语言:写的程序在执行之前,需要一个专门的编译过程,把所有程序编译成为机器语言的文件 解释型语言:在程序运行时才编译,执行一段代码就要编译一段代码 区别:编译时机不同
js语言特点
解释型 弱类型 动态性
javascript数据类型
基本数据类型 number Boolean undefined null string symbol 引用数据类型 (Object、Date、function、array、正则) 栈内存:存储值大小固定,系统自动分配内存空间,空间小,效率高 堆内存:存储值大小不定,可动态调整,程序员分配内存空间,空间大,效率低 基本数据类型存储在栈,不可变,引用数据类型同时存储在堆栈,栈存地址,可变
基本数据类型与引用数据类型的区别:
- 访问机制不同(直接访问 通过栈内地址访问)
- 复制变量不同(相互独立互不影响 复制地址)
- 比较变量不同(比较值 比较地址)
- 参数传递不同(传值 传地址)
数据类型检测方式
-
typeof arr基本数据类型准确(除typeof null=object) 引用数据类型除function,其余均返回object -
arr.constructor 不准确 -
arr instanceof Array 检测Array.prototype是否出现在arr的原型链上 function instanceOf(l,r){
l=l.__proto__;
r=r.prototype;
while(l){
if(l===r) return true;
l=l.__proto__;
}
return false;
}
console.log(instanceOf([],Array));
-
Object.prototype.toString.call()准确判断 console.log(Object.prototype.toString.call([]));
数据类型转换
转为number:undefined =>NaN 转为object: undefined、null=》{} string、Boolean、number=》包装对象 object转number 先调用valueof()若为原始值则返回,再调用tostring()若为原始值返回,最后调用Number() object转string 先调用tostring()再调用valueof(),最后 String()若为基本数据类型返回 object转Boolean =true
[]=[]与[]==![]返回值
[]==[]返回值为false,比较的是两个引用地址
[]==![]结果为true 先将等号右边变为false ,即判断[]=false,转化为number类型0=0
包装对象:
null和undefined没有包装对象 存取字符串、数字或布尔值的属性时所创建的临时对象,用来处理属性的引用,如果引用结束,包装对象则销毁 包装类型和引用类型的区别是对象的生存期
包装对象:当访问基本数据类型上的属性和方法是,访问的都是临时创建的包装对象上的属性和方法,如果对象上没有改属性和方法,则沿着原型链向上查找对应的属性和方法
全局变量与全局函数
全局变量是window对象的属性,全局函数是window对象的方法
双=与三=区别
===严格相等运算符(类型且值都相等) ==宽松相等运算符(在比较之前先转为相同类型)
arguments与形参的双向绑定特性
在调用时,arguments对象与实际传递了值得形参发生双向绑定
call bind apply作用区别(代码实现)
1.call apply 的作用是相同的 都是改变this指向并且执行函数,但是他们的参数是有区别的,第一个参数this要指向的对象,apply的第二个参数是一个参数数组,而call后面参数为参数是序列 2.bind 的作用与call和apply是有区别的 ,它改变了this指向但是并没有执行函数,而且返回一个函数,后面的参数成为预设参数 3.第一个参数为null undefined 空时,this会指向window
Function.prototype.call1=function(newObj, ...args){
newObj.fun=this;
let res=newObj.fun(...args);
delete newObj.fun;
return res;
}
let obj={
name:'hjn',
printName:function(age){
console.log(this.name+' '+age);
}
}
let obj2={
name:'hjn2',
}
obj.printName.call1(obj2,18);
Function.prototype.apply1=function(newObj, args){
newObj.fun=this;
let res=newObj.fun(...args);
delete newObj.fun;
return res;
}
let obj={
name:'hjn',
printName:function(age){
console.log(this.name+' '+age);
}
}
let obj2={
name:'hjn2',
}
obj.printName.apply1(obj2,[18]);
Function.prototype.bind1=function(newObj, ...outerArgs){
newObj.fun=this;
function inner(...innerArgs){
let args=[...innerArgs,...outerArgs];
let res=newObj.fun(...args);
delete newObj.fun;
return res;
}
return inner;
}
let obj={
name:'hjn',
printName:function(age){
console.log(this.name+' '+age);
}
}
let obj2={
name:'hjn2',
}
obj.printName.bind1(obj,18)();
函数生命周期
定义:[[scope]]确定(函数声明 函数表达式 Function构造函数 ) 调用:独立调用 对象方法调用 作为构造函数调用 call间接调用 执行:
结束(垃圾回收机制 内存被引用就不释放)
内存泄漏:不会再用到的内存没有及时释放
垃圾回收机制:
- 标记清除:从根节点出发遍历对象,对访问过的对象打上标记,表示该对象可达,对那些没有标记的对象进行回收,使得空间能够重新利用
- 引用计数:系统会有一张引用表,保存了各种值的引用次数,如果该次数为0,就表示这个值不会在用到了,因此可以释放这块内存
预解析过程中声明提升规则
1.函数声明整体提前 2.变量声明提前,赋值留在原地 3.函数首先被提升,然后是变量 4.函数声明有冲突,会覆盖,变量声明有冲突,会忽略
作用域
函数作用域 全局作用域 块作用域 作用域两种工作模式:词法作用域(书写位置) 动态作用域(调用位置) – 通过 new Function 创建的函数对象不遵从静态词法作用域 – 通过 new Function 创建的函数对象总是在全局作用域中执行 [[scope]]属性:虚拟属性 无法访问和修改 函数创建时生成的属性,保存着这个函数父级执行上下文的所有变量对象(词法作用域) 作用域链:当前环境的变量对象+父级环境的变量对象
闭包
当前函数执行,形成一个私有的上下文,函数执行完,当前私有上下文中的某些内容被上下文以外的内容占有,那么当前上下文就不能被释放(形成一个不被销毁的私有作用域) 、返回一个不被销毁的私有作用域 作用:保存变量(不被销毁) 保护变量(私有作用域) 形式:以普通函数返回 以对象方法返回 缺点:函数中的变量保存在内存中,内存消耗大 ,函数外部会改变函数内部变量的值
应用场合:立即执行函数 防抖节流
this绑定规则
1.默认绑定规则:this===window/函数的独立调用/立即执行函数 2.隐式绑定规则:谁调用指向谁 数组中存放函数 this指向数组 3.new绑定 构造函数 4.显示绑定:call apply 优先级 new》显式》隐式》默认
严格模式规则
1.变量必须先声明再使用 2.全局作用域this指向undefined 3.构造函数不加new 报错 4.函数参数不允许重名
纯函数
一个输入只有一个输出 并且不产生副作用
构造函数与普通函数区别
构造函数三要素:函数名大写 内部使用this 用new关键字调用
普通函数返回值:如果没有return 或者return;返回值为undefined 构造函数返回值:只有引用可以被返回,其他均返回实例对象 构造函数不使用new也可以,就是this会指向window
new运算符作用(代码实现)
1.创建一个空的对象{} 2.该对象会被执行prototype连接 3.将该对象作为this的上下文,并执行构造函数的代码 4.如果构造函数没有返回引用类型,就返回实例对象
function Person(name, age){
this.name =name;
this.age = age;
}
function new_2(fun,...args){
let obj={};
obj.__proto__=fun.prototype;
let res=fun.call(obj,...args);
if(res&&typeof res==="object") return res;
return obj;
}
let person = new_2(Person,'hjn',18);
console.log(person);
prototype与–proto–
函数prototype属性:只要创建一个函数,js引擎就会为他创建一个对象,并初始化一个属性constructor,用来引用该函数
fun.prototype.constructor===fun
__proto__属性:每个实例对象都有一个属性来指向构造函数的prototype
fun.__proto__=Fun.prototype
属性的类型:
静态属性:静态属性只能通过构造函数来访问。 原型属性:原型对象上的属性 实例属性:实例对象可以访问本身对象上的实例属性,也可以访问原型对象上的原型属性,但是不能访问构造函数上的静态属性。
数据属性的特性: value writable configurable可配置性 enumrable可枚举特性 访问器属性特性:get set configurable可配置性 enumrable可枚举特性
属性描述符设置 Object.defineProperty(obj,prop,descriptor)/Object.defineProperties(obj,props,descriptors) 默认undefined false,字面量定义默认为true 属性描述符查询 Object.getOwnPropertyDescriptor(obj,prop)/Object.getOwnPropertyDescriptors(obj)
检测属性方式
obj.hasOwnProperty(prop)(判断自身属性) Object.getOwnPropertyNames(obj)(返回指定对象的所有自身属性) prop in obj (检查原型链上是否有该属性 返回布尔值)
Function.proto==Function.prototype Object.proto==Function.prototype Function.prototype.proto==Object.prototype Object.prototype.proto==null
获取原型对象Object.getPrototypeOf(obj) 判断原型对象obj.isPrototypeOf(object) obj是否在object对象的原型链上 设置原型对象obj1=Object.create(obj) obj1.proto==obj
数组的原型链
[].proto=>Array.prototype=>Object.prototype=>null
拷贝与继承
函数内方法:定义在函数内部,会让他的每一个实例对象都克隆这个方法,内存占用较大,可以访问函数内部的私有变量 prototype上的方法:定义在构造函数的prototype属性上,会让所有的实例对象都共享这个方法,内存占用比较少,但是不可以访问函数内部的私有变量
浅拷贝:拷贝基本类型的值,拷贝引用类型的引用两者会指向同一内存空间 深拷贝:浅拷贝+递归 拷贝基本类型的值,拷贝引用数据类型时使用递归,完成之后,两者无关(代码实现)
继承方式:
1.原型继承 :将子级的原型对象赋值为父级的一个实例 Child.prototype=new Father() 优点:子级的实例也是父级的实例,子级的实例可以访问到父级新增的属性和方法 缺点:子级和父级在一条原型链上,如果原型属性被修改,所有实例访问的值都被修改,不能传参
2.构造继承:call、apply继承 子级的构造函数内部调用父级构造函数 Father.call(this,name) 优点:子级可以访问到父级的属性和方法,实现继承 缺点:子级无法访问到父级原型链上的属性和方法
3.组合继承:原型继承+构造继承 优点:既可以访问到构造函数上的属性方法,也可访问到原型链上的属性方法 缺点:父级构造函数要被执行多次
4.原型式继承:创建临时构造函数 Object.create()方法重写 function objectCreate(obj){ function F(){} F.prototype=obj return new F() } a.返回一个对象 b.返回对象的原型(proto)就是obj 优点:不需要定义一个构造函数
5.寄生式继承:在原型式继承的基础上,在封装继承过程的函数内部增强函数 function CreateAnother(obj,constructor){ var newObj=Object.create(obj) newObj.constructor=constructor newObj.say=function(){…} return newObj } 6.寄生组合式继承:构造继承+寄生式继承 优点:只调用一次父级构造函数
Array 的静态方法(es6)
Array.of() 创建数组 Array.from() 类数组对象转化为数组 Array.isArray()
类数组对象转换为真正的数组有哪些方法
- es6 Array.from(arguements)
- es5 Array.prototype.slice.call(arguements,0)
- [].prototype.slice.call(arguements)
- […arguements]
创建数组的方法
- 字面量赋值
- new Array(7)//[,] new Array(1,2,3) //[1,2,3]
- es6 Array.of(7)//[7]
XML与HTML
可扩展标记语言类似HTML 设计宗旨是传输和存储数据,而不是显示数据 自行定义标签
HTML超文本标记语言 被设计用来显示数据
JSON是什么
json (JavaScript对象表示法) 是轻量级的文本数据交换格式 独立于语言和平台
json 与js 对象的区别
1.json不能表示undefined 2.json中字符串全部用双引号括起来 3.json 是一个字符串
静态方法 JSON.stringify()将对象格式转为json字符串,对象序列化 JSON.parse()将json对象解析为原生js对象,对象反序列化
var let const 区别(es6)
var es5通过var声明变量,没有块级作用域,可能会造成变量污染,存在变量生命提升 let es6新增,只在块级作用域内有效,不允许在相同的作用域内重复声明变量,不存在变量提升 ,暂时性死区(当前作用域开头位置到声明的位置,不能使用,不能声明变量) const 声明一个只读的变量,一旦声明,常量的值就不能改变,声明时必须进行初始化 let /const 创建全局变量时,不会在全局对象上创建属性
默认值(es6)
function(x,y=‘world’){}严格相等判断 if(typeof y===‘undefined’){ y=‘world’}当没有赋值的时候,赋值为默认值
解构赋值(es6)
按照一定模式,将值或属性从数组或对象中提取出来 目的:简化提取信息
扩展运算符(es6)
扩展运算符:将数组展开成元素序列 rest运算符:将元素序列合并成数组
模板字符串:``(es6)
字符串新增哪些方法(es6)
includes(‘word’,pos)true startsWith() endsWidth() repeat(n)
箭头函数普通函数区别(es6)
语法简洁 不绑定this 从作用域链上一层继承this 不可以当做构造函数 没有arguements对象 用rest代替
数组新增那些方法(es6)
- arr.keys()获取下标
- arr.values()
- arr.entries()
- arr.find()
- arr.findIndex()
- arr.fill 替换掉原来元素
- arr.includes()是否包含某元素
- arr.of()创建数组
- arr.from类数组转为数组
- arr.some()
- arr.every()
- arr.flat()数组扁平化
- for in获取数组下标,遍历对象属性名
- for of获取数组值,类数组,set,map不能遍历对象
对象的扩展(es6)
静态方法: Object.assign(target,s1,s2)对象合并 (浅拷贝) Object.keys()获取对象属性名 Object.values()值 Object.entries()键值
symbol(es6)
var s1=Symbol() 基本数据类型,普通函数调用 用途:保证属性名不会重复 Symbol.for(str)搜索以该参数为标识的symbol值 如果找到 返回true 没有找到则会创建 Symbol.keyFor(s1)返回一个已经登记的symbol类型值的标识
数据结构Set(es6)
const items=new Set() size add() delete() has() clear() keys() values()
数据结构Map(es6)
var map=new Map() map.get()
map.set()
方法同上
promise对象用于异步操作,解决回调地狱的问题(es6)
1匿名函数 bug难找到 2不易阅读维护 3操作顺序变更难修改
promise优点:
将异步操作用同步方式书写,方便阅读维护 将数据请求与数据处理分开 三种状态:等待成功失败 new Promise(fun(res,rej){…}) promise构造函数执行时立即调用执行函数
了解什么是Promise/A+规范?
https://promisesaplus.com/
宏任务&&微任务
宏任务:异步任务 微任务:promise 中的then catch finaliy 执行顺序: 执行一个宏任务 此时,如果遇到微任务,进入任务队列排队 宏任务执行完毕,执行所有微任务 原型方法 promise.prototype.then() promise.prototype.catch() promise.prototype.finally() 静态方法 Promise.resolve() Promise.reject() Promise.all()参数为promise对象数组,只有全部成功之后才返回成功 Promise.race()参数同上,由率先执行完毕的promise的状态决定
异步渲染执行流程
先执行宏任务,执行完毕以后查看是否有微任务 没有的话直接渲染 有的话,执行完所有微任务进行渲染 执行宏任务=》执行微任务=》渲染
class语法(为了更接近传统语言的写法)(es6)
类与构造函数的区别
必须使用new调用 类内部严格模式 不存在变量提升 (必须先定义后使用)
实例属性 顶部(count=0)或者构造函数内部 原型属性 静态属性 方法前static
类内的方法定义在原型对象上
事件三要素
元素 触发事件 事件函数
绑定事件函数的方法
1.内联模型
2.脚本模型 div.οnclick=function(){}
3.ele.addEventListener(‘click’,function(){},true)true表示事件在捕获阶段执行,默认false就是冒泡阶段
EventTarget对象的原型方法
1.addEventListener 添加监听器 2.removeEventListener删除监听器,具名函数可删除,匿名函数不可删除 3.dispatchEvent 派发事件
浏览器获取dom元素的方法
? document.getElementById()通过id选择器 ? document.getElementsByClassName()通过类选择器 ? document.getElementsByTagName()通过标签选择器 ? document.getElementsByName()通过类名 ? document.querySelector()通过css选择器获取一个 ? document.querySelectorAll()通过css选择器获取一组 ? document.body 获取body ? document.documentElement 获取HTML标签
JavaScript事件机制
事件流:当几个都具有同一事件的元素层叠在一起的时候,那么你点击其中一个元素,并不是只有当前被点击的元素会触发事件,而是层叠的元素都会触发事件
事件流两种模式
事件冒泡:事件按照从目标元素到根元素的顺序执行,即从内到外 阻止事件冒泡:event.stopPropagation() 判断当前时间是否冒泡:event.bubbles onmouseover onmouseout 支持冒泡 onmouseenter onmouseleave 不支持冒泡 事件捕获:事件沿dom树向下传播,即从外到内 冒泡应用——事件委托(事件代理):如果想要在大量子元素中单击任何一个元素都可以执行一段代码,那么就可以将事件监听器放在父节点上,并将事件的影响设置为每个子节点,而不是在每个子节点上都设置监听器 优点:较少代码书写 提高代码性能
事件处理周期
触发一个事件后,在HTML元素间传播过程 阶段一:事件捕获 阶段二:目标触发,执行监听函数 阶段三:事件冒泡
进程与线程
进程是系统分配的一块独立内存,进程之间互相独立,一个进程有多个线程组成,多个线程在进程中协助完成任务,同一进程下各个线程之间共享程序的内存空间
浏览器是多进程的
浏览器进程 :即浏览器的主进程,负责协调主控(界面显示,用户交互,各个页面管理,创建销毁进程,网络资源的管理 下载) 渲染进程 :浏览器内核,render进程是多线程的 主要用于页面渲染事件处理脚本执行 插件进程 :每种类型的插件对应一个进程,使用时才创建 GPU进程:用于3D绘制
多进程的优势: 1.某个Tab页崩溃不会影响浏览器的使用 2.插件崩溃也不会影响使用 3.提高浏览器稳定性
浏览器进程是多线程的
GUI渲染线程:负责渲染浏览器界面,解析HTML,css,构建dom树和css树,布局绘制, 执行重绘回流。 JS引擎线程:解析JS,与GUI渲染线程互斥,当JS线程执行时,渲染进程会被挂起,保存在任务队列中,等JS线程空闲立即执行,JS执行事件过长会导致页面渲染不连贯,导致页面渲染阻塞 事件触发线程:当JS引擎执行到事件代码时,会将任务添加到事件线程,当任务触发时,会将回调函数添加到任务队列的尾部,等JS线程空闲的时候,会从任务队列中取任务执行。 定时器线程:当时间到达,添加到事件队列中,等JS引擎空闲从任务队列中取任务执行。 网络请求线程:在XMLHttpRequest连接后浏览器新开一个线程,检测到状态变更时,如果有回调函数,该线程就会产生状态变更事件,将回调函数放进任务队列
同步与异步
同步任务是指,在主线程上排队执行的任务,只有前一个任务执行完毕才能执行后一个任务 异步任务是指:不进入主线程,而是进入任务队列,只有任务队列通知主线程某个异步任务可以执行了,该任务才会进入主线程执行
event loop 事件轮询、事件循环:是指主线程重复从消息队列中不断取任务执行任务的过程 dom渲染 :每次从任务队列取任务之前进行一次渲染 执行同步任务-》渲染-》执行一个宏任务=》执行微任务=》渲染
左移右移
5>2 5右移2位 5<2 5左移2位 正数补0 负数右移补1
》》》无符号右移 《《《无符号左移 正数规则相同 负数右移补0
前端js文件需要被要压缩优化,后端node.js 文件不需要被压缩优化
原因 :客户端向服务器发起请求,服务器响应请求,同时要将前端所需要的js文件从服务器传输到客户端,将文件进行压缩可以节省更多的带宽 ,提高传输效率,从而提高用户体验。而后端的文件并不需要通过网络传输,无非就是所占内存空间大了一点,所以不需要进行压缩
Async defer的区别
浏览器在解析HTML是,如果遇到什么属性都没有的script标签,那么就会立即停止对页面的解析,会先去请求引用的js文件,然后解析JS脚本,解析完毕后,继续解析html。所以就阻塞了对HTML文件的解析,解决方案:
- async表示异步,当浏览器遇到带有async属性的script的时候,请求脚本的网络请求是异步的,不会阻塞页面的解析。但是当脚本已经完全获取到了以后,如果浏览器的解析还没有完成,就会停下来,先让JS引擎执行代码,执行代码完毕后再进行解析。所以async是不可控的,每一个脚本的执行顺序是不确定的,完全依赖与网络,谁先到就执行谁
- defer表示延迟,获取该脚本的网络请求也是异步的,不会阻塞浏览器解析页面。当页面全部解析完成以后,才会去执行脚本的内容
区别:
- async可能会阻塞页面的渲染,但是defer不会阻塞页面的渲染
- async中执行的脚本的顺序可能是乱的,完全取决于当前的网络,而在defer里面,脚本的执行顺序是确定的
Map与对象的区别
对象的键必须是字符串 map的建可以是任意数据类型
对象keys()返回数组 map.keys 返回一个迭代器
JS是单线程还是多线程,如何实现的异步(事件循环)
单线程
JS在执行时首先执行同步任务,如果遇到异步任务先放到任务队列,等同步任务执行完毕,异步任务准备好以后,主线程会从任务队列中取任务。
async、await作用?
Async标识在该函数内部可能会发生异步任务,返回值为一个promise
Await表示当前异步任务执行完毕之后才可执行下面语句
浏览器的重绘和回流是什么,区别?在什么情况下会触发?
回流、重排:当某些元素的尺寸、布局、是否隐藏等发生更改时,页面需要重新构建称为回流(至少一次)
重绘:render树种元素的一些属性发生改变,比如颜色并不会影响页面的布局成为重绘
区别:回流一定引起重绘,重绘不一定引起回流
当页面布局改变和几何属性更改时一定会引起回流
如何优化
- 添加多个元素时,元素一次性传建好,最后append
- 不直接改变元素的属性,通过classname来改变属性
- 实现动画尽量使用绝对定位不会影响其他元素
- Css代码 尽量不要使用表达式 运算
- 避免使用table布局
- 使用transition transform 避免重绘
防抖和节流的区别?(代码实现)
防抖:触发高频事件后,函数在n秒内只会执行一次,如果在n秒内重复触发,时间将会重新计算
缺点:在n秒内重复触发事件会一直被推迟
节流:触发高频事件后,函数在n秒内只会执行一次
区别:函数节流不管事件触发有多频繁都会在一定时间内执行一次时间处理,而防抖只会在最后执行一次,页面无限加载的场景,我们需要在用户滚动页面时,每隔一段时间触发一次Ajax请求,适合用节流。
function debounce(func, delay){
let timer=null;
return function(){
clearTimeout(timer);
timer=setTimeout(()=>{
func.call(this,...arguments);
}, delay);
}
}
function thrrote(func, delay){
let canRun=true;
return function(){
if(!canRun) return;
canRun=false;
setTimeout(()=>{
func();
canRun=true;
},delay);
}
}
Promise.all()&&promise.race()
function promiseAll(promiseList) {
let arr=[];
return new Promise(function(resolve, reject) {
promiseList.forEach(promise => {
Promise.resolve(promise).then(res=>{
arr.push(res);
if(arr.length===promiseList.length){
resolve(arr);
}
},err => reject(err));
})
})
}
function promiseRace(promiseList) {
return new Promise(function(resolve, reject) {
promiseList.forEach(promise => {
Promise.resolve(promise).then(res=>{
resolve(res);
},err => reject(err));
})
})
}
数组map的实现?
Array.prototype.map2=function(fun){
let res=[];
for(let i=0;i<this.length;i++){
res.push(fun(this[i]));
}
return res;
}
Array.prototype.map2=function(fun){
let res=[]
this.forEach(item=>{
res.push(fun(item));
});
return res;
};
Array.prototype.map2=function(fun){
let res=[]
this.reduce((prev, cur,index,arr)=>{
res.push(fun(cur));
},0);
return res;
};
reduce实现
Array.prototype.reduce_2=function(fn,initValue){
if(!Array.isArray(this)) throw new Error('这是数组的方法');
if(typeof fn !== 'function') throw new Error('fn must be a function');
let prev = initValue?initValue:this[0];
for(let i=initValue?0:1;i<this.length;i++){
prev=fn(prev,this[i],i,this);
}
return prev;
}
手动实现数组的 filter/some/every方法(ES5)
Array.prototype.filter_2=function(fn){
let arr=[];
this.forEach(item=>{
if(fn(item)){
arr.push(item);
}
})
return arr;
}
Array.prototype.some_2=function(fn){
for(let i=0;i<this.length;i++){
if(fn(this[i])) return true;
}
return false;
}
Array.prototype.every_2=function(fn){
for(let i=0;i<this.length;i++){
if(!fn(this[i])) return false;
}
return true;
}
单向链表中判断是否有环?
var hasCycle = function(head) {
let set=new Set();
while(head){
if(set.has(head)) return true
set.add(head);
head=head.next;
}
return false
};
var hasCycle = function(head) {
let slow=head;
let fast=head;
while(fast&&fast.next){
slow=slow.next;
fast=fast.next.next;
if(slow===fast) return true;
}
return false;
};
0.1+0.2!=0.3的原因及解决方案
ECMAScript是基于IEEE754标准的,这种计算方法会将数值保存为二进制然后进行计算,有些浮点数转换成二进制是无穷的,于是会产生舍入误差,因此计算的精确程度不如整数。 解决办法: 1、因为整数的计算结果是精确的,所以我们先将浮点数转换整数,最后再把整数转换成小数。 2、除了在加减乘除上做文章,我们还可以通过计算出的结果保留几位小数来得到正确答案
function round(x,n){
return Math.round(x*Math.pow(10,n))/Math.pow(10,n)
}
console.log(round(0.1+0.2,1));
function sum(a,b){
let l1=a.toString().split('.')[1].length;
let l2=b.toString().split('.')[1].length;
let len=l1>=l2?l1:l2;
let res= a*Math.pow(10,len)+b*Math.pow(10,len);
return res/Math.pow(10,len);
}
什么是面向对象,面向对象特性
1.面向过程思想:面向步骤,分为一个个步骤,一步步实现 优点:性能较高 缺点:不易复用,维护,扩展 2.面向对象思想:先把对象找到,然后找到它的功能,实现对象之间分工合作 特性:封装性 继承性 多态性 优点:易复用,维护,扩展,可以设计出低耦合的系统,更加灵活,易于维护 缺点:性能较低
什么是递归
一个函数调用同一个函数 自己调用自己,因为自己调用自己会出现死循环,所以还需要设置一个终止条件 递归永远表现的是树形结构=》递归树 最先调用的函数最后执行完毕,最后调用的函数最先执行完毕
|