0.1+0.2 === 0.3嘛?为什么?

不相等。在js中,javascript用Number类型来表示数字(整数或浮点数),遵循IEEE 754标准,通过64位二进制来表示一个数字(1 + 11 + 52):符号位 + 指数位(阶码) + 尾码

在这里插入图片描述

  • 1 符号位,0 表示正数,1 表示负数 s
  • 11 指数位(e)
  • 52 尾数,小数部分(即有效数字)

0.1 的二进制表示即为: 0.00011001100110011…,是一个无限循环小数,由于储存空间有限,js对二进制进行了截取,造成了精度丢失。同理0.2也是如此。所以我们得到了是两个近似值相加的结果:

// 0.1 和 0.2 都转化成二进制后再进行运算
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111

// 转成十进制正好是 0.30000000000000004

延伸:

理论上用有限的空间来存储无限的小数是不可能保证精确的,但我们可以处理一下得到我们期望的结果。当你拿到

1.4000000000000001 这样的数据要展示时,建议使用 toPrecision 凑整并 parseFloat 转成数字后再显示,如下:

parseFloat(1.4000000000000001.toPrecision(12)) === 1.4  // True

对于运算类操作,如 ±*/ ,就不能使用 toPrecision 了。正确的做法是把小数转成整数后再运算。

js的数据类型

基本类型:Number、Boolean、null、undefined、String、symbol(ES6新增)

引用类型:Object。里面包含function、Array、Date

谷歌67版本中还出现了一种 bigInt(ES2020)。是指安全存储、操作大整数。(但是很多人不把这个做为一个类型)。

js整数是怎么表示的?

通过Number类型来表示的,遵循IEEE 754标准,通过64位二进制来表示一个数字(1 + 11 + 52),最大的安全数字是Math.pow(2,53),对于16位的二进制。

Number()的存储空间是多大?如果后台发送一个超过最大自己的数字怎么办

js的最大安全数字为Math.pow(2,53),53位有效数字,值为:9007199254740992。如果超出了,会发生截断,等于js能支持的最大数字。比如900719925474099222,那么得到的值会不精确,也就是900719925474099200。

写代码:实现函数能够深度克隆基本类型

浅克隆:

 function  shallowClone(obj){
    let cloneObj = {};

    for(let i in obj){
        cloneObj[i] = obj[i]
    }
    return cloneObj;
}

深克隆:

  • 考虑基础类型
  • 引用类型
  • RegExp、Date、函数不是JSON安全的
  • 会丢失constructor,所有的构造函数都指向Object
  • 破解循环引用
function deepCopy(obj){
    if(typeof obj === 'object'){
        var result = obj.constructor === Array ?[]:{};
        for (var i in obj){
            result[i] = typeof obj[i] === 'object'?deepCopy(obj[i]):obj[i];
        }
    }else{
        var result = obj;
    }
    return result;
}

事件流是什么?

事件流是网页元素接受事件的顺序,‘DOM2级事件’规定的事件流包括三个阶段:

  • 事件捕获阶段
  • 处于目标阶段
  • 事件冒泡阶段

首先发生的事件捕获,为截获事件提供机会。然后是实际的目标接受事件。最后一个阶段是事件冒泡阶段,可以在这个阶段对事件作出响应。虽然捕获阶段在规范中规定不允许响应事件,但是实际上还是会执行,所以有两次机会获取到目标对象。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>事件冒泡</title>
</head>
<body>
    <div>
        <p id="parEle">我是父元素    <span id="sonEle">我是子元素</span></p>
    </div>
</body>
</html>
<script type="text/javascript">
var sonEle = document.getElementById('sonEle');
var parEle = document.getElementById('parEle');

parEle.addEventListener('click', function () {
    alert('父级 冒泡');
}, false);
parEle.addEventListener('click', function () {
    alert('父级 捕获');
}, true);

sonEle.addEventListener('click', function () {
    alert('子级冒泡');
}, false);
sonEle.addEventListener('click', function () {
    alert('子级捕获');
}, true);

</script>

当有多个嵌套元素,即在捕获阶段又在`冒泡阶段调用事件处理程序时,事件按照dom事件流的顺序执行事件处理程序:

  • 父级捕获
  • 子级冒泡
  • 子级捕获
  • 父级冒泡

且当事件处于目标阶段时,事件调用顺序决定于绑定事件的书写顺序 ,按上面的例子为,先调用冒泡阶段的事件处理程序,再调用捕获阶段的事件处理程序。依次alert出“子集冒泡”,“子集捕获”。

事件是如何实现的?

基于发布的订阅模式,就是在浏览器加载的时候会读取相关事件的代码,但是只有实际等到具体的事件触发的时候才会执行。

比如点击按钮,这是个事件(Event),而负责处理时间的代码段通常被称为事件处理程序(Event Handler),也就是【启动对话框的显示】这个动作。

在Web端,我们常见的就是DOM事件:

  • DOM0级事件,直接在html元素上绑定on-event,比如onclick,取消的话,dom.onclick = null,同一事件只能有一个处理程序,后面的会覆盖前面的。
  • DOM2级事件,通过addEventListener注册事件,通过removeEventListener来删除事件,一个事件可以有多个事件处理程序,按照顺序执行,捕获事件和冒泡事件。
  • DOM3级事件,增加了事件类型,比如UI事件,焦点事件,鼠标事件

new一个函数发生了什么?

构造调用:

  • 创造一个全新的对象
  • 这个对象会被执行[[Prototype]]连接,将这个新对象的[[Prototype]]链接到这个构造函数.prototype所指向的对象
  • 这个新函数会绑定到函数调用的this
  • 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象

new一个构造函数,如果函数返回 return {}、return null、return 1、return true会发生什么情况?

如果函数返回一个对象,那么new这个函数调用返回这个函数的返回对象,否则返回new创建的新对象

symbol有什么用处?

可以用来表示一个独一无二的变量,防止命名冲突。还可以利用symbol不会被常规的方法(除了Objet.getOwnPropertySymbols外)遍历到,所以可以用来模拟私有变量。

主要用来提供遍历接口,布置了symbol.iterator的对象才可以使用for ...of循环,可以统一处理数据结构。调用之后会返回一个遍历器对象,包含有一个next方法,使用next方法后由两个返回值value和done分别表示函数当前执行位置的值和是否遍历完毕。

Symbol.for()可以在全局访问symbol

闭包是什么?

闭包是指有权访问另一个函数作用域中的变量函数。

javascript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个代码作用域规则会确定,执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。

image.png

什么是作用域?

ES5中只存在两种作用域:全局作用域和函数作用域。在javascript中,我们将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量(变量名或者函数名)查找

什么是作用域链?

首先要了解作用域链,当访问一个变量时,编译器在执行这段代码时,会首先从当前的作用域中查找是否有这个标识符,如果没有找到,就回去父作用域查找,如果父作用域还没有找到继续向上查找,直到全局作用域为止,而作用域链,就是有当前作用域与上层作用域的一系列变量对象组成,它保证了当前执行的作用域对符合访问权限的变量和函数的有序访问。

闭包产生的本质

当前环境中存在指向父级作用域的引用

什么是闭包

闭包是一种特殊的对象,它由两部分组成:执行上下文(代号A),以及在该执行上下文中创建的函数(代号B),当B执行时,如果访问了A中变量对象的值,那么闭包就会产生,且在Chrome中使用这个执行上下文A的函数名代指闭包。

一般如何产生闭包
  • 返回函数
  • 函数当做参数传递
闭包的应用场景
  • 柯里化 bind
  • 模块

NaN是什么,用typeof会输出什么?

Not a Number,表示非数字,typeof NaN === ‘number’

js隐式转化,显示转化

一般非基础类型进行转化时会先调用valueOf,如果valueOf无法返回基本类型的值,就会调用toString

字符串和数字
  • ‘+’操作符,如果有一个为字符串,那么都转化到字符串然后执行字符串拼接
  • ‘-’操作符,转化为数字,相减(-a,a*1,a/1)都能进行隐式强制类型转换
布尔值到数字
  • 1 + true = 2
  • 1 + false = 1
转化为布尔值
  • for 中第二个
  • while
  • if
  • 三元表达式
  • || (逻辑或) && (逻辑与) 左边的操作数
符号
  • 不能被转化为数字
  • 能被转换为布尔值(都是true)
  • 可以被转换成字符串“symbol(cool)”
宽松相等(==)和严格相等(===)
  • 宽松相等允许进行强制类型转换,而严格相等不允许
字符串与数字
  • 转化为数字然后进行比较
其他类型与布尔类型
  • 先把布尔类型转化为数字,然后继续进行比较
对象与非对象
  • 执行对象的toPrimitive(对象)然后继续进行比较
假值列表
  • undefined
  • null
  • false
  • +0,-0,NaN
  • “”

了解this嘛,bind,call,apply具体指什么?

它们都是函数的方法

  • call:Array.prototype.call(this,args1,args2)
  • apply:Array.prototype.apply(this,[args1,args2]

四条规则:

  • 默认绑定:没有其他修饰(bind,apply,call),在非严格模式下定义指向全局对象,在严格模式下定义指向undefined
function foo() {
  console.log(this.a); 
}

var a = 2;
foo();
//2
  • 隐式绑定:调用位置是否有上下文对象,或者是否被某个对象拥有或者包含,那么隐式绑定规则会把函数调用中的this绑定到这个上下文对象。而且,对象属性链只有上一层或者说最后一层在调用中起作用。
function foo() {
  console.log(this.a);
}

var obj = {
  a: 2,
  foo: foo,
}

obj.foo(); // 2
  • 显示绑定:通过在函数上运行call和apply,来显示的绑定this
function foo() {
  console.log(this.a);
}

var obj = {
  a: 2
};

foo.call(obj);
  • 显示绑定之硬绑定
function foo(something) {
  console.log(this.a, something);
  
  return this.a + something;
}

function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments);
  };
}

var obj = {
  a: 2
}

var bar = bind(foo, obj);

New绑定,new调用函数会创建一个全新的对象,并将这个对象绑定到函数调用的this

  • New绑定时,如果new一个硬绑定函数,那么会用new新建的对象替换这个硬绑定this
function foo(a) {
  this.a = a;
}

var bar = new foo(2);
console.log(bar.a)
//2

手写bind、apply、call

//call
Function.prototype.call = function (context,...args) {
    context = context || window;

    const fnSymbol = Symbol("fn");
    context[fnSymbol] = this;

    context[fnSymbol](...args);
    delete context[fnSymbol];
}
//apply
Function.prototype.apply = function (context,argsArr) {
    context = context || window;

    const fnSymbol = Symbol("fn");
    context[fnSymbol] = this;

    context[fnSymbol](...argsArr)
    delete context[fnSymbol]
}
// bind
Function.prototype.bind = function(context,...args) {
    context = context || window;
    const fnSymbol = Symbol("fn");
    context[fnSymbol] = this;

    return function (..._args){
        args = args.concat(_args);

        context[fnSymbol](...args);
        delete context[fnSymbol];
    }
}

setTimeout(fn,0),EventLoop

setTimeout按照顺序放到队列里面,然后等待函数调用栈清空之后才开始执行,而这些操作进入队列的顺序,则由设定的延迟时间来决定。

手写Promise原理

class MyPromise{
    constructor(fn){
        this.resolvedCallbacks = [];
        this.rejectedCallbacks = [];

        this.state = 'PENDING';
        this.value = '';

        fn(this.resolve.bind(this),this.reject.bind(this))
    }
    resolve(value){
        if(this.state == 'PENDING'){
            this.state = 'RESOLVED';
            this.value = value;

            this.resolvedCallbacks.map(cb => cb(value));
        }
    }

    reject(value){
        if(this.state == 'PENDING'){
            this.state = 'REJECTED'
            this.value = value

            this.rejectedCallbacks.map(cb => cb(value));
        }
    }

    then(onFulfilled,onRejected){
        if(this.state === 'PENDING'){
            this.resolvedCallbacks.push(onFulfilled);
            this.rejectedCallbacks.push(onRejected);
        }

        if(this.state === 'RESOLVED'){
            onFulfilled(this.value);
        }

        if(this.state == 'REJECTED'){
            onRejected(this.value)
        }
    }
}

js脚本加载问题,async、defer问题

  • 如果依赖其他脚本和DOM结果,使用defer
  • 如果与DOM和其他脚本依赖不强时,使用async

image.png

如何判断一个对象不是空对象

Object.keys(obj).length === 0

在线编程,getUrlParams(url,key);就是简单的获取url的某个参数的问题,但是要考虑多个便捷,多个返回值等

getUrlParams(url) {
    const _url = url || window.location.href;
    console.log(_url)
    const _urlParams = _url.match(/([?&])(.+?=[^&]+)/igm);
    console.log(_urlParams)
  
    return _urlParams ? _urlParams.reduce((a, b) => {
    const value = b.slice(1).split('=');
  
    a[value[0]] = value[1]
  
    return a;
  
    }, {}) : {};
  
    }
console.log(this.getUrlParams('?name=wuweigang&age=25&school=bj university'))

<script src='xxx' />外部js文件先加载还是onload先执行,为什么?

onload是左右加载完成之后才执行的

怎么是事件监听,两种

onclick和addEventListener

事件传播机制(事件流)

冒泡和捕获

原型链和原型链的继承?

  • 其实[[prototype]]__proto__意义相同,均表示对象的内部属性,其值指向对象原型。所有[[prototype]]链最终都会指向内置的Object.prototype,其包含了javascript中许多通用的功能
  • 为什么能创建‘类’,借助一种特殊的属性:所有的函数默认都会拥有一个名为prototype的共有且不可枚举的属性,它会执行另一个对象,这个对象通常被称为函数的原型。
function Person(name){
    this.name = name
}
Person.prototype.constructor = Person
  • 在发生new构造函数调用时,会将创建的新对象[[prototype]]链接到Person.prototype指向的对象,这个机制被称为原型链继承
  • 方法定义在原型上,属性定义在构造函数上
  • 首先要说下js原型和实例的关系:每一个构造函数(constructor)都有一个原型对象(prototype),这个原型对象包含一个指向此构造函数的指针属性,通过new进行构造函数调用生成的实例,此实例包含一个指向原型对象的指针,也就是通过[[prototype]]链接到了这个原型对象
  • 然后说一下js中属性的查找:当我们试图引用实例对象的某个属性时,是按照这样的方式去查找的,首先查找实例对象上是否有这个属性,如果没有找到,就去构造这个实例对象的prototype所指向的对象上查找,如果还找不到,就从这个prototype对象所指的构造函数的prototype原型对象上查找
  • 什么是原型链:这样逐级查找形似一个链条,且通过[[Prototype]]属性链接,所以被称为原型链
  • 什么是原型链继承,类比类的继承:当有两个构造函数A和B,将一个构造函数A的原型对象,通过其[[Prototype]]属性链接到另外一个B构造函数的原型对象时,这个过程被称之为原型继承
什么是原型链(标准答案更正确的解释)

当对象查找一个属性时,如果没有在自身找到,那么就会查找自身的原型,如果原型还没有找到,那么会继续查找原型的原型,直到找到Object.prototype的原型是,此原型为null,查找停止。这种通过原型链接的逐级向上的查找链被称为原型链

什么是原型继承?

一个对象可以使用另一个对象的属性或者方法,就称之为继承。具体是通过将这个对象的原型设置为另一个对象,这样根据原型链的规则,如果查找一个对象属性且在自身不存在时,就会查找另一个对象,相当于一个对象可以使用另一个对象的属性和方法

说下对js的了解

javascript是一种轻量级的解释执行的而非编译的脚本语言。

不需要预先编译而是在程序运行中逐行解释并执行。由ECNAScript、文档对象模型(DOM)和浏览器对象模型(BOM)组成

是基于原型的动态语言,主要独特特性有this、原型和原型链。严格意义上来说分为语言标准部分(ECMAScript)+宿主环境部分

语言标准部分

2015年发布ES6,引入诸多新特性使得能够编写大型项目变成可能,标准自2015之后以年号代号,每年一更

宿主环境部分

  • 在浏览器宿主环境包括DOM+BOM
  • 在node,宿主环境包括一些文件、数据库、网络、与操作系统的交互等

数组能够调用的函数有哪些?

  • push 向数组的末尾添加一个或多个元素,并返回新的长度
  • pop 向删除并返回数组的最后一个元素
  • splice 删除元素,并向数组添加新元素
  • slice 从某个已有的数组返回选定元素
  • shift 删除并返回数组的第一个元素
  • unshift 向数组的开头添加一个或更多元素,并返回新的长度
  • sort 排序
  • reverse 颠倒数组中的元素
  • concat 链接两个或者更多的数组,并返回结果
  • join 将数组所有元素放入一个字符串
  • find
  • findindex
  • map/filter/reduce等函数式编程方法
  • 还有一些原型链上的方法:toString/valueOf

如何判断数组的类型

Array.isArray

函数中的arguments是数组吗?类数组转数组的方法

是类数组,伪数组,是属于鸭子类型的范畴,长的像数组,

  • ...运算符
  • Array.from
  • Array.prototype.slice.apply(arguments)

用过typeScript嘛?它的作用是什么?

为js添加类型支持,以及提供罪行的es语法的支持,是利于团队协作和排错,开发大型项目

PWA使用过吗?serviceWorker的使用原理是啥?

渐进式网络应用(PWA是谷歌在2015年底提出的概念。基本上算是web应用程序,但在外观和感觉上与原生app类似。支持PWA的网站可以提供脱机工作、推送通知和设备硬件访问等功能。

Service Worker是浏览器在后台独立于网页运行的脚本,他打开了通向不需要网页或用户交互的功能的阀门。现在,它们已包括如推送通知和后台同步等功能。将来,Service Worker将会支持如定期同步或地理围栏等其他功能。

如果一个构造函数,bind了一个对象,用这个构造函数创建出的实例会继承这个对象的属性吗?为什么?

不会继承。因为根据this绑定四大规则,new绑定的优先级高于bind显示绑定,通过new进行构造函数调用时,会创建一个新对象,这个新对象会代替bind的对象绑定,作为函数的this,并且在此函数没有返回对象的情况下,返回这个新建的对象

箭头函数和普通函数有啥区别,箭头函数能当构造函数嘛?

  • 普通函数通过function关键字定义,this无法结合词法作用域使用,在运行绑定,只取决于函数的调用方式,在哪里被调用,调用位置。(取决于调用者,和是否独立运行)
  • 箭头函数使用被称为‘胖箭头’的操作=>定义,箭头函数不应用普通函数this绑定的四种规则,而是根据外层(函数或全局)的作用域来决定this,且箭头函数的绑定无法被修改(new也不行)
  • 箭头函数常用与回调函数中,包括时间处理器或定时器
  • 箭头函数和 var self = this,都试图取代传统的this运行机制,将this的绑定拉回到词法作用域
  • 箭头函数没有原型、this、super、arguments、new.target
  • 不能通过new关键字调用

    • 一个函数内部有两种方法:[[call]]和[[Construct]],在通过new进行函数调用时,会执行[[construct]]方法,创建一个实例对象,然后再执行这个函数体,将函数的this绑定在这个实例对象上
    • 当直接调用时,执行[[Call]]方法,直接执行函数体
    • 箭头函数没有[[Construct]]方法,不能被用作构造函数调用,当使用new进行函数调用

知道ES6的class嘛?static关键字有了解嘛?

被static修饰的属性和方法都是静态方法和属性,只能被类名调用,不能被实例化对象调用。同时也不能被子类继承,换句话说它属于当前这个类的。

ES6class 可以看作只是一个语法糖 ,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法

image.png

事件循环机制(Event Loop)

事件循环机制从整体上高速我们javascript代码的执行循序,Event Loop即事件循环,是指浏览器或Node的一种解决javascript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。

先执行宏任务队列,然后执行微任务队列,然后开始下一轮事件循环、继续先执行宏任务,再执行微任务队列。

  • 宏任务:script/setTimeout/setInterval/setImmediate/I/O/UI Rendering
  • 微任务:process.nextTick()/Promise

上述的setTimeoutsetInterval等都是任务源,真正进入任务队列的是他们发布的任务

优先级

  • setTimeout = setInterval 一个队列
  • setTimeout > setImmediate
  • process.nextTick > Promise

扁平化数组

数组扁平化是指将一个多维数组变为一维数组

function flatten(arr){
    let result = []

    for(let i = 0; i< arr.length;i++){
        if(Array.isArray(arr[i])){
            result = result.concat(flatten(arr[i]))
        }else{
            result = result.concat(arr[i])
        }

    }

    return result
}
const a = [1,[2,[3,4]]]
console.log(flatten(a))

实现柯里化

维基百科上说道:柯里化,英语:Currying(果然是满满的英译中的既视感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

通俗一点:是指这样一个函数,它接收函数A,并且能返回一个新的函数,这个新的函数能够处理函数A的剩余参数。

// 普通的add函数
function add(x, y) {
    return x + y
}

// Currying后
function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}

add(1, 2)           // 3
curryingAdd(1)(2)   // 3
function createCurry(func,args){
    var argity = func.length;
    var args = args || []

    return function(){
        var _args = [].slice.apply(arguments)
        args.push(..._args)

        if(args.length < argity){
            return createCurry.call(this,func,args)
        }

        return func.apply(this.,args)
    }
}

数组去重

Array.from(new set([1,1,2,2,]))

let闭包

let会产生临时性死区,在当前的执行上下文中,会进行变量提升,但是未被初始化,所以在执行上下文执行阶段,执行代码如果还没有执行到变量赋值,就引用此变量就会报错,此变量未初始化

最后修改:2021 年 04 月 13 日 02 : 50 PM
如果觉得我的文章对你有用,请随意赞赏
END