js相关知识点

文章目录

1.JS中的8种数据类型及区别

包括值类型(基本对象类型)和引用类型(复杂对象类型)

  • 基本类型(值类型): Number(数字),String(字符串),Boolean(布尔),Symbol(符号),null(空),undefined(未定义)在内存中占据固定大小,保存在栈内存中
  • 引用类型(复杂数据类型): Object(对象)、Function(函数)。其他还有Array(数组)、Date(日期)、RegExp(正则表达式)、特殊的基本包装类型(String、Number、Boolean) 以及单体内置对象(Global、Math)等 引用类型的值是对象 保存在堆内存中,栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址。

JS中的数据类型检测方案

  1. typeof
    优点:能够快速区分基本数据类型
    缺点:不能将Object、Array和Null区分,都返回object

  2. instanceof
    优点:能够区分Array、Object和Function,适合用于判断自定义的类实例对象
    缺点:Number,Boolean,String基本数据类型不能判断

  3. Object.prototype.toString.call()
    优点:精准判断数据类型
    缺点:写法繁琐不容易记,推荐进行封装后使用

  4. var && let && const
    ES6之前创建变量用的是var,之后创建变量用的是let/const

三者区别:

  • var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
    let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
    const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,且不能修改。
  • var可以先使用,后声明,因为存在变量提升;let必须先声明后使用。
  • var是允许在相同作用域内重复声明同一个变量的,而let与const不允许这一现象。
  • 在全局上下文中,基于let声明的全局变量和**全局对象GO(window)**没有任何关系 ;
    var声明的变量会和GO有映射关系;
  • 会产生暂时性死区:

暂时性死区是浏览器的bug:检测一个未被声明的变量类型时,不会报错,会返回undefined
如:console.log(typeof a)//undefined
而:console.log(typeof a)//未声明之前不能使用
let a

  • let /const/function会把当前所在的大括号(除函数之外)作为一个全新的块级上下文,应用这个机制,在开发项目的时候,遇到循环事件绑定等类似的需求,无需再自己构建闭包来存储,只要基于let的块作用特征即可解决

2.闭包的两大作用:保存/保护

在这里插入图片描述

概念

  • 闭包:即重用一个变量又保护变量不被污染的一种机制。

  • 为什么使用闭包 : 全局变量和局部变量都具有不可兼得的优缺点。
    全局变量: 优: 可重用, 缺: 易被污染。
    局部变量: 优: 仅函数内可用,不会被污染。 缺: 不可重用!

闭包是指有权访问另一个函数作用域中的变量的函数–《JavaScript高级程序设计》

稍全面的回答: 在js中变量的作用域属于函数作用域, 在函数执行完后,作用域就会被清理,内存也会随之被回收,但是由于闭包函数是建立在函数内部的子函数, 由于其可访问上级作用域,即使上级函数执行完, 作用域也不会随之销毁, 这时的子函数(也就是闭包),便拥有了访问上级作用域中变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。

闭包的作用:通过一系方法,将函数内部的变量(局部变量)转化为全局变量。函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

特性

  1. 内部函数可以访问定义他们外部函数的参数和变量
  • 闭包是密闭的容器,,类似于set、map容器,存储数据的
  • 闭包是一个对象,存放数据的格式为 key-value 形式
  1. 函数嵌套函数
  2. 本质是将函数内部和外部连接起来。优点是可以读取函数内部的变量,参数和变量不会被回收

那么使用闭包有什么好处呢?

使用闭包的好处是:
1.希望一个变量长期驻扎在内存中
2.避免全局变量的污染
3.私有成员的存在

与回调函数区别

回调函数

  1. 一般在异步操作中,当异步操作结束,需要回到主线程做点事情,需要用到回调函数
  2. 经常应用在书写类或者扩建等公用性比较高的模块
  3. 回调函数就是将函数作为参数使用,作为函数参数的函数就是回调函数

回调函数原理:我现在出发,到了通知你”。这是一个异步的流程,“我出发”这个过程中(函数执行),“你”可以去做任何事,“到了”(函数执行完毕)“通知你”(回调)进行之后的流程。

回调函数的适用场合:

  • 资源加载:动态加载js文件后执行回调,加载iframe后执行回调,ajax操作回调,图片加载完成执行回调,AJAX等等。
  • DOM事件及Node.js事件基于回调机制(Node.js回调可能会出现多层回调嵌套的问题)。

闭包形成的条件:

  1. 函数的嵌套
  2. 内部函数引用外部函数的局部变量,延长外部函数的变量生命周期

闭包的用途:

  1. 可以读取函数内部的变量,,就是让这些变量的值始终保持在内存中(阻止被回收)。
  2. 模仿块级作用域
  3. 封装私有化变量
  4. 创建模块

闭包应用场景

闭包的两个场景,闭包的两大作用:保存/保护。
在开发中, 其实我们随处可见闭包的身影, 大部分前端JavaScript 代码都是“事件驱动”的,即

  • 一个事件绑定的回调方法;
  • 发送ajax请求成功|失败的回调;
  • setTimeout的延时回调;
  • 一个函数内部返回另一个匿名函数,

闭包的优点:延长局部变量的生命周期
闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏

3.javascript的垃圾回收原理:

(1)在javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收;
(2)如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。

  1. 项目中,如果存在大量不被释放的内存(堆/栈/上下文),页面性能会变得很慢。当某些代码操作不能被合理释放,就会造成内存泄漏。我们尽可能减少使用闭包,因为它会消耗内存。

  2. 浏览器垃圾回收机制/内存回收机制:

浏览器的Javascript具有自动垃圾回收机制(GC:GarbageCollecation),
垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。

标记清除:当变量进入执行环境时,被标记为“进入环境”,当变量离开执行环境时,会被标记为“离开环境”。垃圾回收器会销毁那些带标记的值并回收它们所占用的内存空间。(最常用)
谷歌浏览器:“查找引用”,浏览器不定时去查找当前内存的引用,如果没有被占用了,浏览器会回收它;如果被占用,就不能回收。
IE浏览器:“引用计数法”,当前内存被占用一次,计数累加1次,移除占用就减1,减到0时,浏览器就回收它。

  1. 优化手段:内存优化 ; 手动释放:取消内存的占用即可。
    (1)堆内存:fn = null 【null:空指针对象】
    (2)栈内存:把上下文中,被外部占用的堆的占用取消即可。

  2. 内存泄漏
    在 JS 中,常见的内存泄露主要有 4 种,全局变量、闭包、DOM 元素的引用、定时器

4.作用域和作用域链

创建函数的时候,已经声明了当前函数的作用域==>当前创建函数所处的上下文。如果是在全局下创建的函数就是[[scope]]:EC(G),函数执行的时候,形成一个全新的私有上下文EC(FN),供字符串代码执行(进栈执行)。

定义:简单来说作用域就是变量与函数的可访问范围,由当前环境与上层环境的一系列变量对象组成
1.全局作用域:代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域。
2.函数作用域:在固定的代码片段才能被访问

作用:作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

作用域链参考链接
一般情况下,变量到 创建该变量 的函数的作用域中取值。但是如果在当前作用域中没有查到,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

5.JS 中 this 的五种情况

面向对象语言中 this 表示当前对象的一个引用。但在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变。

  • 在方法中,this 表示该方法所属的对象。
  • 如果单独使用,this 表示全局对象。
  • 在函数中,this 表示全局对象。
  • 在函数中,在严格模式下,this 是未定义的(undefined)。
  • 在事件中,this 表示接收事件的元素。
  • 类似 call() 和 apply() 方法可以将 this 引用到任何对象。
  1. 作为普通函数执行时,this指向window。
  2. 当函数作为对象的方法被调用时,this就会指向该对象
  3. 构造器调用,this指向返回的这个对象。(new一个对象)
  4. 箭头函数的this绑定看的是this所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。
  5. 基于Function.prototype上的 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向apply接收参数的是数组,call接受参数列表,bind方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this指向除了使用new 时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。

一、call,apply,bind的相同点:

  1. 都是动态的修改当前函数内部环境对象this的指向,都不会修改原先函数的this指向。
  2. 第一个参数都是this要指向的对象;
  3. 都可以利用后续参数传参;

二、call,apply,bind的区别:

  1. 执行方式不同:
    call和apply是改变后页面加载之后就立即执行,是同步代码。
    bind是异步代码,改变后不会立即执行;而是返回一个新的函数
  2. 传参方式不同:
    call和bind传参是一个一个逐一传入,不能使用剩余参数的方式传参。
    apply可以使用数组的方式传入的,只要是数组方式就可以使用剩余参数的方式传入。
  3. 修改this的性质不同:
    call、apply只是临时的修改一次,也就是call和apply方法的那一次;当再次调用原函数的时候,它的指向还是原来的指向。
    bind是永久修改函数this指向,但是它修改的不是原来的函数;而是返回一个修改过后新的函数,此函数的this永远被改变了,绑定了就修改不了。

6.原型 && 原型链

每个 class都有显示原型 prototype
每个实例都有隐式原型 _ proto_
实例的_ proto_指向对应 class 的 prototype

原型: Father.prototype 就是原型,它是一个对象,我们也称它为原型对象。
在 JS 中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象。
原型的作用,就是共享方法。我们通过 Father.prototype.method 可以共享方法,不会反应开辟空间存储方法。

原型链:原型与原型层层相链接的过程即为原型链。
函数的原型链对象constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针是指向上一层的原型对象,而上一层的原型对象的结构依然类似。因此可以利用__proto__一直指向Object的原型对象上,而Object原型对象用Object.prototype.__ proto__ = null表示原型链顶端。如此形成了js的原型链继承。同时所有的js对象都有Object的基本防范。

特点: JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
在这里插入图片描述

7.prototype和__proto__

在这里插入图片描述

三个概念,构造函数,原型对象,实例对象

  • 每创建一个函数,该函数都会自动带有一个prototype属性。该属性是一个指针,指向一个对象,该对象称之为原型对象.
  • 原型对象上默认有一个属性constructor,该属性也是一个指针,指向其相关联的构造函数
  • 通过调用构造函数产生的实例对象,都拥有一个内部属性,指向了原型对象。其实例对象能够访问原型对象上的所有属性和方法。
  • 三者的关系是,每个构造函数都有一个原型对象,原型对象上包含着一个指向构造函数的指针,而实例都包含着一个指向原型对象的内部指针。
  • 通俗的说,实例可以通过内部指针访问到原型对象,原型对象可以通过constructor找到构造函数
  • 实例对象的隐式原型等同于构造函数的显式原型

首先,明确一点,prototype是构造函数属性,__proto__是构造函数实例即对象的属性,即:

__proto__是每个对象都具有的属性
prototype是Function独有的属性,只有函数对象才有prototype
构造函数的prototype和相应实例的__proto__指向同一个对象。

foo.__proto__.__proto__ === Object.prototype === null //true
Foo.prototype.__proto === Object.prototype //true
Object === Object.prototype.constructor

8.继承 - ES5方法

ES6之前并没有给我们提供extends继承,我们可以通过构造函数+原型对象模拟实现继承。
继承属性,利用call改变this指向。但该方法只可以继承属性,实例不可以使用父类的方法。

 function Father(name) {
        this.name = name;
    }
    Father.prototype.dance = function () {
      console.log('I am dancing');
    };
    function Son(name, age) {
        Father.call(this, name); //改变了this指向
        this.age = age;
    }
    let son = new Son('小红', 100);
    son.dance();   //报错

复制代码如何继承父类的方法呢?
解决方法1:利用Son.prototype = Father.prototype改变原型指向,但此时我们给子类增加原型方法,同样会影响到父类。

 function Father(name) {
        this.name = name;
    }
    Father.prototype.dance = function () {
        console.log('I am dancing');
    };
    function Son(name, age) {
        Father.call(this, name);
        this.age = age;
    }
    Son.prototype = Father.prototype;
    //为子类添加方法
    Son.prototype.sing = function () {
        console.log('I am singing');
    };
    let son = new Son('小红', 100);
    //此时父类也被影响了
    console.log(Father.prototype) //{dance: ƒ, sing: ƒ, constructor: ƒ}

复制代码解决方法2:子类的原型指向父类的实例,这样就可以顺着原型链共享父类的方法了。并且为子类添加原型方法的时候,不会影响父类。

function Father(name) {
    this.name = name;
}
Father.prototype.dance = function () {
    console.log('I am dancing');
};
function Son(name, age) {
    Father.call(this, name);
    this.age = age;
}
Son.prototype = new Father();
Son.prototype.sing = function () {
    console.log('I am singing');
};
let son = new Son('小红', 100);
console.log(Father.prototype) //{dance: ƒ, constructor: ƒ}

在这里插入图片描述

9.类

类的本质还是一个函数,类就是构造函数的另一种写法。

function Star(){}
console.log(typeof Star); //function

class Star {}
console.log(typeof Star); //function

ES6中类没有变量提升
通过构造函数创建实例,是可以变量提升的。es6中的类,必须先有类,才可以实例化。

补充:变量提升
(1)变量提升,很简单,就是把变量提升提到函数的top的地方。我么需要说明的是,变量提升只是提升变量的声明,并不会把赋值也提升上来。
(2)函数提升是把整个函数都提到前面去。
(3)函数提升优先于变量提升。

类的所有方法都定义在类的prototype属性上面

class Father{
    constructor(name){
        this.name = name;
    }
    sing(){
        return this.name;
    }
}
let red = new Father('小红');
let green = new Father('小绿');
console.log(red.sing === green.sing); //true

向类中添加方法
通过Object.assign,在原型上追加方法。

class Father{
    constructor(name){
        this.name = name;
    }
    sing(){
        return this.name;
    }
}
//在原型上追加方法
Object.assign(Father.prototype,{
    dance(){
        return '我爱跳舞';
    }
});
let red = new Father('小红');
let green = new Father('小绿');
console.log(red.dance());//我爱跳舞
console.log(red.dance === green.dance); //true

constructor方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

继承 - ES6方法

class Father {
    constructor(name){
        this.name = name;
    }
    dance(){
        return '我在跳舞';
    }
}
class Son extends Father{
    constructor(name,score){
        super(name);
        this.score = score;
    }
    sing(){
        return this.name +','+this.dance();
    }
}
let obj = new Son('小红',100);

类和构造函数的区别

(1) 类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
(2) 类的所有实例共享一个原型对象
(3) 类的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。

严格模式

JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性JavaScript变体的一种方式,即在严格的条件下运行 JS 代码。

严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript 语义做了一些更改:

  1. 消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
  2. 消除代码运行的一些不安全之处,保证代码运行的安全。
  3. 提高编译器效率,增加运行速度。
  4. 禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript做好铺垫。比如一些保留字如:class,enum,export, extends, import, super 不能做变量名

总结

构造函数特点:

  1. 构造函数有原型对象prototype
  2. 构造函数原型对象prototype里面有constructor,指向构造函数本身。
  3. 构造函数可以通过原型对象添加方法。
  4. 构造函数创建的实例对象有__proto__原型,指向构造函数的原型对象。

类:

  1. class本质还是function
  2. 类的所有方法都定义在类的prototype属性上
  3. 类创建的实例,里面也有__proto__指向类的prototype原型对象
  4. 新的class写法,只是让对象原型的写法更加清晰,更像面向对象编程的语法而已。
  5. ES6的类其实就是语法糖。

语法糖

语法糖(Syntactic sugar),也译为糖衣语法。指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。

通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

语法糖”可以给我们带来方便,是一种便捷的写法,编译器会帮我们做转换;而且可以提高开发编码的效率,在性能上也不会带来损失。

10.垃圾回收机制(GC,Garbage Collection)

常见的垃圾收集器有串行垃圾回收器(Serial)、并行垃圾回收器(Parallel)、并发清除回收器(CMS)、G1回收器。

  1. 什么是垃圾
    没有被使用(引用)的对象就是垃圾。

  2. 什么是垃圾回收
    没有被引用的对象被销毁,内存被释放,就是垃圾回收。
    C、C++ 等编程语言需要手动垃圾回收。
    Java、JavaScript、PHP、Python 等变成语言自动垃圾回收。

  3. 变量的生命周期(何时会被回收)
    全局变量: 整个脚本执行完毕,全局变量就被销毁。
    局部变量: 函数调用完毕,局部变量就被销毁。

  4. 垃圾没有及时回收的后果
    没有被及时回收的垃圾会常驻内存,造成内存泄漏。

  5. 垃圾回收的常见算法:引用计数、标记清除

引用计数

  1. 原理
  • 每个对象都有一个引用标记,记录引用次数
  • 如果对对象进行引用,引用标记+1
  • 如果取消了对对象的引用,引用标记-1
  • 当对象的引用标记次数为 0,就变为垃圾对象,会立即被清除。
  1. 优缺点:
  • 优点: 垃圾对象比清除地非常及时
  • 缺点: 如果两个对象互相引用,会造成两个对象常驻内存,造成内存泄漏

标记清除

  1. 原理
    浏览器会不停地进行垃圾回收的循环,每次循环经历两个阶段:
  • 标记阶段:
    从根对象开始,一层一层向下找,对所有的能找到的对象进行标记,有标记的对象成为可到达对象,没有标记的对象就是不可到达对象,也就是垃圾对象。
  • 清除阶段
    线性变量内存中所有的对象,如果对象没有标记就作为垃圾被清除。
    清除完垃圾之后,去掉所有对象的标记,继续进行下一轮的标记清除。
  1. 优缺点
  • 优点:不会造成内存泄漏
  • 缺点:需要进行深度递归遍历,垃圾回收不如引用计数方式更及时。

11.new运算符的实现机制

  1. 首先创建了一个新的空对象 son{}
  2. 设置原型,将对象的原型设置为函数的prototype对象。son.__proto__ = Father.prototype
  3. 让函数的this指向这个对象,Father.call(this)
  4. 执行构造函数的代码(为这个新对象添加属性)son.name
  5. 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。return this

每个new出来的实例的方法是共享的吗?

方法1:在构造函数上直接定义方法(不共享)

    function Star() {
        this.sing = function () {
            console.log('我爱唱歌');
        }
    }
    let stu1 = new Star();
    let stu2 = new Star();
    stu1.sing();//我爱唱歌
    stu2.sing();//我爱唱歌
    console.log(stu1.sing === stu2.sing);//false

方法2:通过原型添加方法(共享)
构造函数通过原型分配的函数,是所有对象共享的。

    function Star(name) {
        this.name = name;
    }
    Star.prototype.sing = function () {
        console.log('我爱唱歌', this.name);
    };
    let stu1 = new Star('小红');
    let stu2 = new Star('小蓝');
    stu1.sing();//我爱唱歌 小红
    stu2.sing();//我爱唱歌 小蓝
    console.log(stu1.sing === stu2.sing);//true

12.EventLoop 事件循环

JavaScript是一种单线程的编程语言,同一时间只能做一件事,所有任务都需要排队依次完成。

Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理

浏览器事件循环

同步任务与异步任务

同步任务

含义:在主线程上排队执行的任务,只有一个任务执行完毕,才能执行后一个任务

异步任务

  • 含义:不进入主线程、而进入”任务队列”的任务,当主线程中的任务运行完了,才会从”任务队列”取出异步任务放入主线程执行。

  • 分类:异步任务又分为宏任务和微任务。所有同步任务都在主线程上执行,形成一个函数调用栈(执行栈),而异步则先放到任务队列(task queue)里,任务队列又分为宏任务(macro-task)与微任务(micro-task)。

宏任务

宏任务包括:script(整体代码)setTimoutsetIntervalsetImmediate(node.js环境)I/OUI交互事件

微任务

微任务包括:new promise().then(回调)MutationObserver(html5新特性)Object.observe(已废弃)process.nextTick(node环境)

​ 若同时存在promise和nextTick,则先执行nextTick(需要立即执行)

异步设定的时间准吗?不准的话怎么解决?

异步事件 setInterval 到时后会把回调函数放入消息队列中Event Queue主线程的宏任务执行完毕后依次执行消息队列的微任务,等微任务执行完了在循环回来执行宏任务。并且由于消息队列中存在大量任务,其他任务执行时间就会造成定时器回调函数的延迟,如果不处理则会一直叠加延迟。

其实要解决这个也很简单
通过计算时差可以有效的解决

const _setInterval = (fn, delay, ...rest) => {
  let lastTime = Date.now();
  return setInterval(() => {
    let now = Date.now();
    if (now - lastTime >= delay) {
      lastTime = lastTime + delay;
      fn(...rest);
    }
  }, 1);
};

const timer = _setInterval(() => {
  console.log('执行了')
}, 500)

执行过程

  1. 所有同步任务都在主线程上执行,形成一个执行栈(调用栈);
  2. 主线程之外,还存在一个**‘任务队列’**(task queue),当异步的代码运行完毕以后,会将代码中的回调送入到任务队列中(队列遵循先进先出得原则)
  3. 一旦主线程的栈中的所有同步任务执行完毕后,调用栈为空时系统就会将队列中的回调函数依次压入调用栈中执行,当调用栈为空时,仍然会不断循环检测任务队列中是否有代码需要执行;
    在这里插入图片描述
    在这里插入图片描述

执行顺序

  1. 先执行同步代码,
  2. 遇到异步宏任务则将异步宏任务放入宏任务队列中,
  3. 遇到异步微任务则将异步微任务放入微任务队列中,
  4. 当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,
  5. 微任务执行完毕后再将异步宏任务从队列中调入主线程执行,
  6. 一直循环直至所有任务执行完毕。

注意:当宏任务和微任务都处于 任务队列(Task Queue) 中时,微任务的优先级大于宏任务,即先将微任务执行完,再执行宏任务;

在这里插入图片描述

Node js 环境中的事件环(Event Loop)

Node是基于V8引擎的运行在服务端的JavaScript运行环境,在处理高并发、I/O密集(文件操作、网络操作、数据库操作等)场景有明显的优势。虽然用到也是V8引擎,但由于服务目的和环境不同,导致了它的API与原生JS有些区别,其Event Loop还要处理一些I/O,比如新的网络连接等,所以Node的Event Loop(事件环机制)与浏览器的是不太一样。

在这里插入图片描述
执行顺序如下:

  • timers: 计时器,执行setTimeout和setInterval的回调
  • pending callbacks:执行延迟到下一个循环迭代的 I/O 回调
  • idle, prepare: 队列的移动,仅系统内部使用
  • poll轮询: 检索新的 I/O事件;执行与 I/O 相关的回调。事实上除了其他几个阶段处理的事情,其他几乎所有的异步都在这个阶段处理。
  • check:执行setImmediate回调,setImmediate在这里执行
  • close callbacks:执行close事件的callback,一些关闭的回调函数,如:socket.on(‘close’, …)

13.setTimeout、Promise、Async/Await 的区别

setTimeout

settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行。


console.log('setTimeout start');
 
setTimeout(function(){
    console.log('setTimeout execute');
})
 
console.log('setTimeout end ');

Promise

Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行

promise是用来解决两个问题的:

  • 回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象。
  • promise可以支持多个并发的请求,获取并发请求中的数据,这个promise可以解决异步的问题,本身不能说promise是异步的。
 console.log('script start');
    var promise1 = new Promise(function (resolve) {
        console.log('promise1');
        resolve();
        console.log('promise1 end');
    }).then(function () {
        console.log('promise2');
    })
 
    setTimeout(function () {
        console.log('setimeout');
    })
 
    console.log('script end');

async/await

async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。

Async/Await 如何通过同步的方式实现异步

Async/Await就是一个自执行的generate函数。利用generate函数的特性把异步的代码写成“同步”的形式,第一个请求的返回值作为后面一个请求的参数,其中每一个参数都是一个promise对象。

await的含义为等待,也就是 async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后,才能继续执行下面的代码。await通过返回一个Promise对象来实现同步的效果。

async function async1(){
        console.log('async1 start');//2
        await async2(); 
        //等待 async2()返回之后 再执行下面的语句 ,
        // 相当于将 console.log('async1 end')异步化了 相当于 console.log('async1 end')在then之后执行了
 
        console.log('async1 end')//5
    }
    async function async2(){
        console.log('async2')//3
    }
 
    //
    console.log('script start');//1
    async1();
    console.log('script end')//4

补充
generator和函数不同的是,generatorfunction*定义(注意多出的*号),并且,除了return语句,还可以用yield返回多次。
看上去是同步的代码,实际执行是异步的。

try {
    r1 = yield ajax('http://url-1', data1);
    r2 = yield ajax('http://url-2', data2);
    r3 = yield ajax('http://url-3', data3);
    success(r3);
}
catch (err) {
    handle(err);
}

Promise.all

  1. 特点
    promise.all()的入参是一个数组,可以传基本类型,也可以传promise对象;
    返回结果是一个Promise对象;
    入参数组中每一个都返回成功,此函数才返回成功;
    只要有一个执行失败,则返回失败;

  2. 功能:
    Promise.all(iterable)返回一个新的Promise实例,此实例在iterable参数内素有的
    Promise都fulfilled或者参数中不包含Promise时,状态变成fulfilled。
    如果参数中Promise有一个失败rejected ,此实例回调失败,失败原因的是第一个失败Promise的返回结果

let p = Promise.all([p1,p2,p3])
  1. 实现原理
    Promise.all是挂载到Promise类实例上的;
    返回的是一个Promise;
    需要遍历入参数组的每一项,判断传入是否为Promise对象,如果是则执行.then回调函数,然后将回调函数的结果处理后返回,如果失败则reject;
    如果数组中存在非Promise对象,则直接返回;
    最后通过计数器,判断当前的结果是否全部返回;

14.介绍节流防抖原理、区别以及应用

节流:事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。

防抖:多次触发事件,事件处理函数只能执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发准备执行事件函数前,会等待一定的时间(这时间是码农自己去定义的,比如 1 秒),如果没有再次被触发,那么就执行,如果被触发了,那就本次作废,重新从新触发的时间开始计算,并再次等待 1 秒,直到能最终执行!

使用场景:

节流:滚动加载更多、搜索框搜的索联想功能、高频点击、表单重复提交……
防抖:搜索框搜索输入,并在输入完以后自动搜索、手机号,邮箱验证输入检测、窗口大小 resize 变化后,再重新渲染

15.事件委托

事件委托,又称事件代理,把原本需要绑定在子元素的响应事件委托给父元素(即绑定在父元素上),让父元素担当事件监听的职务。原理是dom元素的事件冒泡。

dom事件流,一个事件触发后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。
(1)捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”(capture phase),捕获阶段不会响应任何事件;
(2)目标阶段:在
目标节点上触发
,称为“目标阶段”
(3)冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层。

事件委托的优点:
【1】可以大量节省内存占用,减少事件注册,比如在ul上代理所有li的click事件就非常棒
【2】可以实现当新增子对象时无需再次对其绑定(动态绑定事件)

2.怎么阻止默认动作?

有一些html元素默认的行为,比如说a标签,点击后有跳转动作;form表单中的submit类型的input有一个默认提交跳转事件;reset类型的input有重置表单行为。

如果你想阻止这些浏览器默认行为,JavaScript为你提供了方法。

如下代码:

var $a = document.getElementsByTagName("a")[0];
$a.onclick = function(e){
alert("跳转动作被我阻止了")
e.preventDefault();
//return false;//也可以
}

默认事件没有了。

既然return false 和 e.preventDefault()都是一样的效果,那它们有区别吗?当然有。
仅仅是在HTML事件属性 和 DOM0级事件处理方法中,才能通过返回 return false 的形式组织事件宿主的默认行为。

3.怎么阻止冒泡事件

function stopBubble(e){
if(e&&e.stopPropagation){//非IE
e.stopPropagation();
}
else{//IE
window.event.cancelBubble=true;
}
}

16.排他操作

如果有同一组元素,我们想要某一个元素实现某种样式, 需要用到循环的排他思想算法:

所有元素全部清除样式(干掉其他人)

给当前元素设置样式 (留下我自己)

注意顺序不能颠倒,首先干掉其他人,再设置自己

在这里插入图片描述
在这里插入图片描述

 <script>
      // 获取元素
      var tab_list = document.querySelector('.tab_list');
      var lis = tab_list.querySelectorAll('li');
      var items = document.querySelectorAll('.item');
      // for循环绑定点击事件
      for (var i = 0; i < lis.length; i++) {
          // 开始给5个小li 设置索引号 
          lis[i].setAttribute('index', i);
          lis[i].onclick = function() {
              // 1. 上的模块选项卡,点击某一个,当前这一个底色会是红色,其余不变(排他思想) 修改类名的方式

              // 干掉所有人 其余的li清除 class 这个类
              for (var i = 0; i < lis.length; i++) {
                  lis[i].className = '';
              }
              // 留下我自己 
              this.className = 'current';
              // 2. 下面的显示内容模块
              var index = this.getAttribute('index');
              console.log(index);
              // 干掉所有人 让其余的item 这些div 隐藏
              for (var i = 0; i < items.length; i++) {
                  items[i].style.display = 'none';
              }
              // 留下我自己 让对应的item 显示出来
              items[index].style.display = 'block';
          }
      }
 </script>

16.Ajax 如何使用 一个完整的 AJAX ?

请求包括五个步骤:

  1. 创建 XMLHTTPRequest 对象
  2. 使用 open 方法创建 http 请求,并设置请求地址
    xhr.open(get/post,url,async,true(异步),false(同步))经常 使用前三个参数
  3. 设置发送的数据,用 send 发送请求
  4. 注册事件(给 ajax 设置事件)
  5. 获取响应并更新页面
function  Api ({url, method = 'GET', header = {}, callback = function(){}}) {
    
    const xhr = new XMLHttpRequest();
    xhr.open(method, url);
    xhr.responseType = 'json';

    for (let x in header) {
        xhr.setRequestHeader(x, header[x]);
    }

    xhr.send();

    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
            callback(xhr.response);
        }
    }
}

17如何判断一个数据是 NaN

NaN 非数字
1、NaN 不是一个数字且数据类型为 number,而且不等于自身
可直接采用内置方法 isNaN

function isNaN(n) {
if (n !== n) {
return true;
} else {
return false;
}}

2、利用 NaN 是唯一一个不等于任何自身的特点

var a=NaN;
a==a; //false

3、object.is 方法(判断两个值是否相等) n==nan

console.log(Object.is("a", NaN));
console.log(Object.is(1, NaN));
console.log(Object.is(NaN, NaN));

Js 中 null 与 undefined 区别

相同点:用 if 判断时,两者都会被转换成 false
不同点: number 转换的值不同 number(null)为 0 number(undefined) 为 NaN
Null 表示一个值被定义了,但是这个值是空值
Undefined 变量声明但未赋值

typeof undefined       // undefined
typeof null         // object
null === undefined      // false
null == undefined      // true

9. typeof(null) == object

在 JavaScript 中 null 表示 “什么都没有”。

null是一个只有一个值的特殊类型。表示一个空对象引用。

Null 只有一个值,是 null。不存在的对象。

Undefined 只有一个值,是undefined。没有初始化。undefined 是从 null 中派生出来的。

简单理解就是:undefined 是没有定义的,null 是定义了但是为空。

10. undefined 和 null 的区别

18.简述 weakMap 与 Map 的区别

WeakMap与Map类似,但有几点区别:

  1. WeakMap只接受对象作为key,如果设置其他类型的数据作为key,会报错。
  2. WeakMap的key所引用的对象都是弱引用,只要对象的其他引用被删除,垃圾回收机制就会释放该对象占用的内存,从而避免内存泄漏。
  3. 由于WeakMap的成员随时可能被垃圾回收机制回收,成员的数量不稳定,所以没有size属性。
  4. 没有clear()方法
  5. 不能遍历

19.移动端适配方案

简单一句话概括:移动端适配就是在进行 屏幕宽度 的 等比例缩放 :

平时我们开发中,拿到的移动端设计稿一般是 750 * 1334 尺寸大小( iPhone6 的设备像素为标准的设计图)。那如果在 750px 设计稿上量出的元素宽度为 100px ,那么在 375px 宽度的屏幕下,这个元素宽度就应该等比例缩放成 50px 。

所以适配的难点是:如果实现页面的等比例缩放?

Rem 方案

该方案的核心就是:所有需要动态布局的元素,不再使用 px 固定尺寸,而是采用 rem 相对尺寸

只要调整html标签的 font-size,就能让所有使用 rem 单位的元素跟随着发生变化,而使用 px 单位的元素不受影响。问题的关键在于如何根据屏幕尺寸跳转 html 标签的 font-size。

VW 适配方案

vw 是相对单位,1vw 表示屏幕宽度的 1%。基于此,我们可以把所有需要适配屏幕大小等比缩放的元素都使用 vw 作为单位。不需要缩放的元素使用 px 做单位。

举个例子。设计师交付的设计稿宽度是 750px,设计稿上一个标题的 fontSize 标注尺寸是32px。(32/750)*100% = 4.27% ,换句话说这个标题的字号占屏幕宽度的占比是4.27%,不管任何屏幕都是如此。4.27% 即 4.27vw。

对于任何需要等比缩放的元素,在写CSS设置样式时直接换算成 vw 即可,尺寸 = 100vw*设计稿标注大小/设计稿宽度。

REM + VW 方案

REM方案 的优势是可以手动控制 rem 的大小,防止屏幕太大时,页面元素也缩放很大,但是缺点就是需要使用 JS 。 VW方案 刚好相反,无需使用 JS 但是无法手动控制 vw 的大小。

其实我们可以把两者结合:

对于布局元素,我们仍然使用 rem 单位。但是对于根元素的字体大小,我们不需要使用JS来动态计算了

这段js可以直接使用css来实现

对于大屏设备,我们使用媒体查询

viewport 缩放方案

还有一种更简单粗暴的方法,就是我们设置 initial-scale

我们的布局完全基于设计稿 750px ,布局元素单位也使用 px 固定单位 (布局视口写死750px)

对于 375px 宽度,我们就将整个页面缩放 0.5 :

<meta name="viewport" content="width=750, initial-scale=0.5, minimum-scale=0.5, maximum-scale=0.5, user-scalable=0">

此方案的缺点: 整个页面都被缩放了,对于不想缩放的元素无法控制。

总结:
rem方案
vw方案
rem+vw方案
viewport方案

20.简述 webpack 的打包流程

  1. 初始化参数: 从配置文件webpack.config.js和 Shell 语句中读取与合并参数,得出最终的参数。

  2. 开始编译: 根据我们的webpack配置注册好对应的插件调用 compile.run 进入编译阶段

  3. 编译模块: 进入 make 阶段,会从 entry 开始进行两步操作:

  • 第一步是调用 loaders 对模块的原始代码进行编译,转换成标准的JS代码,
    build module模块:
    对不同文件类型的依赖模块文件使用对应的Loader进行编译,最终转为Javascript文件(编译)

  • 第二步是调用 acorn 对JS代码进行语法分析,然后收集其中的依赖关系。每个模块都会记录自己的依赖关系,从而形成一颗关系树。

  1. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表。
  2. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

Loader和Plugin的不同?

plugin: plugin也是为了扩展webpack的功能,但是 plugin 是作用于webpack本身上的。而且plugin不仅只局限在打包,资源的加载上,它的功能要更加丰富。

  1. 两者都是为了扩展webpack的功能。loader它只专注于转化文件(transform)这一个领域,完成压缩,打包,语言翻译; 而plugin不仅只局限在打包,资源的加载上,还可以打包优化和压缩,重新定义环境变量等
  2. loader运行在打包文件之前(loader为在模块加载时的预处理文件);plugins在整个编译周期都起作用
  3. 一个loader的职责是单一的,只需要完成一种转换。一个loader其实就是一个Node.js模块。当需要调用多个loader去转换一个文件时,每个loader会链式的顺序执行
  4. 在webpack运行的生命周期中会广播出许多事件,plugin会监听这些事件,在合适的时机通过webpack提供的API改变输出结果

常见loader:

file-loader: 可以解析项目中URL的引入,将文件拷贝到相应的路径,并修改打包后文件的引入路径,让其指向正确的文件
tslint-loader:通过 TSLint 检查 TypeScript 代码
ts-loader:将 TypeScript 转换成 JavaScript
eslint-loader:通过 ESLint 检查 JavaScript 代码
style-loader:动态创建 style 标签,将 CSS 代码插入到 head 中
css-loader:负责处理 @import、url 等语句。例如 import css from
‘file.css’、url(image.png)`
less-loader:将 .less 文件内容转换成 CSS
sass-loader:将 .sass 文件内容转换成 CSS

常用的plugin

define-plugin:定义环境变量
commons-chunk-plugin:提取公共代码
uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码

21.webpack是什么

webpack是一个静态模块处理器,当其处理应用程序时,会递归的构建一个关系依赖图,其中包含应用程序需要的每个模块,然后把所有这些模块打包成一个或多个包

21.简述 Javascript 的柯里化与逆柯里化

柯里化定义

接受多个参数的函数,
变换成,
接受一个单一参数(最初函数的第一个参数)的函数,
并且返回接受余下参数,
且返回结果的新函数的技术

高阶函数

高阶函数是实现柯里化的基础,高阶函数是至少满足以下两个特性之一
1、函数可以作为参数被传递
2、函数可以作为返回值输出

函数柯里化就是对高阶函数的降阶处理,缩小适用范围,创建一个针对性更强的函数。

函数柯里化有哪些用处呢?

一、可以惰性求值
二、可以提前传递部分参数

反柯里化

反柯里化:扩大方法的适用范围
1、可以让任何对象拥有其他对象的方法(改变原来方法上下文)
2、增加被反柯里化方法接收的参数

22.JS 5种遍历对象的方式

1.for in

for in 循环是最基础的遍历对象的方式,它还会得到对象原型链上的属性
在这种情况下可以使用对象的 hasOwnProperty() 方法过滤掉原型链上的属性

2.Object.keys

该方法返回对象自身属性名组成的数组,它会自动过滤掉原型链上的属性,然后可以通过数组的 forEach() 方法来遍历

另外还有 Object.values() 方法和 Object.entries() 方法,这两方法的作用范围和 Object.keys() 方法类似,因此不再说明

for in 循环和 Object.keys() 方法都不会返回对象的不可枚举属性

3.Object.getOwnPropertyNames

该方法返回对象自身属性名组成的数组,包括不可枚举的属性,也可以通过数组的 forEach 方法来遍历

4.Object.getOwnPropertySymbols

Object.getOwnPropertySymbols() 方法返回对象自身的 Symbol 属性组成的数组,不包括字符串属性

5.Reflect.ownKeys

该方法返回对象自身所有属性名组成的数组,包括不可枚举的属性和 Symbol 属性

23.前端优化

  1. 路由懒加载:一开始进入页面时不需要一次性把资源都加载完,需要时在加载对应的资源。
  2. 图片懒加载(vue-lazyload)
    在图片没有进入可视区域时先不给img的src赋值,优先加载可视区域的内容,其他部分等进入了可视区域再加载,从而提高性能
  3. 减少http请求
    网页中的的图片、form、flash等等元素都会发出HTTP请求,尽可能的减少页面中非必要的元素,可以减少HTTP请求的次数。
  4. 精灵图
    精灵图可以大大地减少CSS背景图片的HTTP请求次数;
    图片是增加HTTP请求的最大可能者;把全站的图标都放在一个图像文件中,然后用CSS的background-imagebackground-position属性定位来显示其中的一小部分。
  5. 压缩图片
    图片是最占流量的资源,因此尽量避免使用它,使用时选择最合适的格式(实现需求的前提下,以大小判断),合适的大小,然后使用智图压缩,同时在代码中用Srcset来按需显示。
    PS:过度压缩图片大小影响图片显示效果
  6. 少用location.reload()
    使用location.reload() 会刷新页面,刷新页面时页面所有资源(css,js,img等)会重新请求服务器;建议使用location.href=“当前页url” 代替location.reload() ,使用location.href 浏览器会读取本地缓存资源。
  7. 采用按需加载
    将不影响首屏的资源和当前屏幕资源不用的资源放到用户需要时才加载,可以大大提升重要资源的显示速度和降低总体流量。
    PS:按需加载会导致大量重绘,影响渲染性能
    a) LazyLoad
    b) 滚屏加载
    c) 通过Media Query加载
  8. 压缩图片优化
  9. 合理使用缓存
    使用缓存可以减少向服务器的请求数,节省加载时间,所以所有静态资源都要在服务器端设置缓存,并且尽量使用长Cache(长Cache资源的更新可使用时间戳)
  10. 预加载
    通过浏览器特性来提高资源加载速度

24.客户端渲染(CSR)与服务端渲染(SSR)

1.客户端渲染(CSR):

优点:

  • 前后端分离,开发效率高。
  • 用户体验更好,我们将网站做成SPA(单页面应用)或者部分内容做成SPA,当用户点击时,不会形成频繁的跳转。

缺点:

  • 前端响应速度慢,特别是首屏,可能会导致用户流失甚至举报扣工资。
  • 不利于SEO优化,因为客户端渲染页面的代码中只有一个空代码,爬虫无法拾取关键词。

2. 服务端渲染(SSR):

优点:

  • 尽量不占用前端的资源,前端这块耗时少,速度快。
  • 有利于SEO优化,因为在后端有完整的html页面,所以爬虫更容易爬取信息。

缺点:

1、不利于前后端分离,主要工作在后端哪里,前端显得没啥总用了,开发效率慢。

2、前端首屏响应速度变快,但是加大了服务器的压力。

二者之间的选择:

如果是企业级网站,主要功能是页面展示,它没有复杂的交互,并且需要良好的SEO,那我们应该使用服务端渲染

如果是后台管理页面,交互性很强,它不需要考虑到SEO,那我们应该使用客户端渲染

具体使用哪种渲染方式也不是绝对的,现在很多网站使用服务端渲染和客户端渲染结合的方式:首屏使用服务端渲染,其他页面使用客户端渲染。这样可以保证首屏的加载速度,也完成了前后端分离。

25.js如何实现深拷贝

  1. 使用递归的方式实现深拷贝
  2. 通过JSON对象实现深拷贝 let _obj = JSON.stringify(obj),
    注意: 无法实现对象中方法的深拷贝
  3. 通过Object.assign()拷贝
    注意: 当对象只有一级属性为深拷贝;当对象中有多级属性时,二级属性后就是浅拷贝

二,数组深拷贝的几种方法

  1. concat(arr1, arr2,…)
    注意:当数组中的元素均为一维是深拷贝,数组中元素一维以上是值的引用
  2. slice(idx1, idx2)
    1)没有参数是拷贝数组
    2)只有一个参数是从该位置起到结束拷贝数组元素
    3)两个参数,拷贝从起始位置到结束位置的元素(不包含结束位置的元素:含头不含尾)
    注意:当数组中的元素均为一维是深拷贝, 数组中元素一维以上是值的引用

26.什么是BOM和DOM?

Javascript 由三部分构成,ECMAScript,DOM和BOM。根据宿主(浏览器)的不同,具体的表现形式也不尽相同,ie和其他的浏览器风格迥异,IE 扩展了 BOM,加入了 ActiveXObject 类,可以通过 JavaScript 实例化 ActiveX 对象。

  1. ECMAScript(核心)   描述了JS的语法和基本对象
  2. DOM 是文档对象模型,处理网页内容的方法和接口。是W3C 的标准; [所有浏览器公共遵守的标准]
  3. BOM 是浏览器对象模型,提供与浏览器交互的方法和接口。各个浏览器厂商根据 DOM在各自浏览器上的实现;[表现为不同浏览器定义有差别,实现方式不同]

DOM的介绍

DOM 全称是 Document Object Model,也就是文档对象模型。描述了处理网页内容的方法和接口,是HTML和XML的API,DOM把整个页面规划成由节点层级构成的文档。

这个DOM定义了一个HTMLDocument和HTMLElement做为这种实现的基础,就是说为了能以编程的方法操作这个 HTML 的内容(比如添加某些元素、修改元素的内容、删除某些元素),我们把这个 HTML 看做一个对象树(DOM树),它本身和里面的所有东西比如 <div></div> 这些标签都看做一个对象,每个对象都叫做一个节点(node),节点可以理解为 DOM 中所有 Object 的父类。
在这里插入图片描述

BOM的介绍

BOM 是 Browser Object Model,浏览器对象模型。刚才说过 DOM 是为了操作文档出现的接口,那 BOM 顾名思义其实就是为了控制浏览器的行为而出现的接口。

浏览器可以做什么呢?比如跳转到另一个页面、前进、后退等等,程序还可能需要获取屏幕的大小之类的参数。所以 BOM 就是为了解决这些事情出现的接口。比如我们要让浏览器跳转到另一个页面,只需要location.href = "http://www.xxxx.com";这个 location 就是 BOM 里的一个对象。

DOM和BOM的联系

BOM的核心是Window,而Window对象又具有双重角色,它既是通过js访问浏览器窗口的一个接口,又是一个Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都以window作为其global对象。

Window对象包含属性:document、location、navigator、screen、history、framesDocument根节点包含子节点:forms、embeds、anchors、images、links
window.document可以看出,DOM的最根本的对象是BOM的window对象的子对象。

由于BOM的window包含了document,因此可以直接使用window对象的document属性,通过document属性就可以访问、检索、修改XHTML文档内容与结构。因为document对象又是DOM(Document Object Model)模型的根节点。

可以说,BOM包含了DOM(对象),浏览器提供出来给予访问的是BOM对象,从BOM对象再访问到DOM对象,从而js可以操作浏览器以及浏览器读取到的文档。

foreach和map的区别

foreach和map的不同点:
(1)map()会分配内存空间存储新数组并返回,forEach()不会返回数据。
map遍历的后的数组通常都是生成一个新的数组,新的数组的值发生变化,当前遍历的数组值不会变化。
(2)forEach()允许callback更改原始数组的元素。map()返回新的数组。
forEach遍历通常都是直接引入当前遍历数组的内存地址,生成的数组的值发生变化,当前遍历的数组对应的值也会发生变化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值