头部背景图片
小畅的学习笔记 |
小畅的学习笔记 |

面试宝典-Javascript

秋招季,总结一下HTML部分面经~

一. JS作用域和作用域链

1. 作用域:

作用域就是变量和函数的可访问范围,或者说变量或函数起作用的区域。

  1. javascript函数的作用域:
    函数内的区域,就是这个函数的作用域,变量和函数在这个区域都可以访问操作。最外层函数外的区域叫全局用域,函数内的区域叫局部作用域
  2. javascript变量的作用域:
    在源代码中变量所在的区域,就是这个变量的作用域,变量在这个区域内可被访问操作。在全局作用域上定义的变量叫全局变量,在函数内定义的变量叫局部变量

2. 作用域链

作用域链(Scope Chain)是javascript内部中一种变量、函数查找机制,它决定了变量和函数的作用范围,即作用域。每个作用域都有一条对应的作用域链,链头是全局作用域,链尾是当前函数作用域。

3. 作用域链的作用

是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。

4. 当全局变量跟局部变量重名的时候,局部变量会覆盖掉全局变量(变量提升)

在函数中存在同名的局部变量,如何使用全局变量?window.全局变量名,客户端中,window为全局对象,不在任何函数内的js代码,也可以使用this来引用全局对象,如:var global = this。
不加var,相当于给全局对象创建一个同名属性。

5. var和不加var的全局变量区别:

  1. 在严格模式和非严格模式下不同,严格模式下,给一个没有声明的变量赋值会报错。
  2. 《js权威P58》使用var声明一个变量,创建的全局对象的属性是不可配置的,也就是说无法通过delete运算符删除;而没有使用严格模式并给一个未声明的变量赋值的话,JavaScript会自动创建一个全局变量,以这种方式创建的变量是全局对象的正常的可配置属性,并可以删除它们。
    js全局变量是全局对象的属性。this可以引用全局对象。

二. JS构造函数、原型和原型链

1. 构造函数

构造函数模式的目的就是为了创建一个自定义类,并且创建这个类的实例。构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立的,即实例识别。
构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写。另外就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用

2. 原型

在JavaScript中,每当定义一个函数数据类型(普通函数、类)时候,都会天生自带一个prototype属性,这个属性指向函数的原型对象,并且这个属性是一个对象数据类型的值。

3. 原型链

  • 在JavaScript中万物都是对象,对象和对象之间也有关系,并不是孤立存在的。对象之间的继承关系,JavaScript中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的条,专业术语称之为原型链。
    举例说明:person → Person → Object ,普通人继承人类,人类继承对象类
  • 原型链查找过程
    当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined。

4. 写一个原型链继承的例子

// 动物
function Animal() {
    this.eat = function () {
        console.log('animal eat')
    }
}
// 狗
function Dog() {
    this.bark = function () {
        console.log('dog bark')
    }
}
Dog.prototype = new Animal()
// 哈士奇
var hashqi = new Dog()

5. 描述new一个对象的过程

  1. 创建一个新对象
  2. this 指向这个新对象
  3. 执行代码,即对 this 赋值
  4. 返回 this
function Foo(neme, age) {
    this.name = name
    this.age = age
    this.class= 'class-1'
    // return this // 默认有这一行
}
var f = new Foo('zhangsan', 20)
  • 如何判断一个变量是数组类型
var arr = []
arr instanceof Array //true
typeof arr //object,typeof是无法判断是否是数组的

三. js闭包、继承

1. 闭包:

闭包是能够读取其他函数内部变量的函数,是函数内部和函数外部连接起来的桥梁。闭包连接起来了一个函数和当函数声明时的词法作用域。

  • 闭包最大用处有两个
    1. 读取函数内部的变量;
    2. 让这些变量的值始终保存在内存中。
      使用闭包主要是为了设计私有的方法和变量。
  • 闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
    在js中,函数即闭包,只有函数才会产生作用域的概念

  • 闭包有三个特性:

    1. 函数嵌套函数(外部嵌套的函数将嵌套的函数对象作为返回值返回)
    2. 函数内部可以引用外部的参数和变量(子函数可以使用父函数的变量,反之则不能)
    3. 参数和变量不会被垃圾回收机制回收(会让变量的值始终在内存中)

2. ES6中闭包:

  • let命令
    块级作用域;不存在变量提升:var定义变量:可以先使用,后声明;而let定义变量:只可先声明,后使用;
    暂时性死区
    只要一进入当前作用域,所要使用的变量就已经存在,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量;不允许重复声明
  • const命令
    const命令用来声明常量,一旦声明,其值就不能改变;在声明时必须初始化;块级作用域,所以其变量只在块级作用域内使用或其中的闭包使用;不存在变量提升;不可重复声明常量;变量名指向的地址不变,并不保证该地址的数据不变;

3. 继承:

创建的子类将继承超类的所有属性和方法,包括构造函数及方法的实现。子类还可添加超类中没有的新属性和方法,也可以覆盖超类的属性和方法。

继承的方式:

  1. 原型链继承:
    将子类的prototype属性赋值为父类实例对象,则子类的proto属性继承父类。
  2. 借用构造函数继承:用.call()和.apply()将父类构造函数引入子类函数;
    call() 方法:它的第一个参数是用作 this 的对象,其他参数都直接传递给函数自身,B.call(A, args1,args2);即A对象调用B对象的方法;
    apply() 方法:用作 this 的对象和要传递给函数的参数的数组 B.apply(A, arguments);即A对象应用B对象的方法混合方式:
  3. 组合继承(组合原型链继承和借用构造函数继承)
  4. 对象冒充
  5. 寄生式继承

给两个构造函数A和B,如何实现A继承B?

function A(…) {} A.prototype…
function B(…) {} B.prototype…
A.prototype = Object.create(B.prototype);

为什么要用Object.create(B.prototype)?
因为如果这里用new B()的话,函数B的构造函数的参数就传了undefined,里面的一些函数可能会误执行,如果构造函数里面创建了一些对象的话,可能会造成内存泄漏。

call()、apply()、和bind()的区别

js改变this指向的方法
var obj = {name:”wang”}

  • 函数对象的call()方法,用于指定this调用函数,第一参数是指定的this对象,从第二个参数开始,是调用自身函数所传递的参数,this指向obj
    test.call(obj,1,3);
  • 函数对象的apply()方法,也用于指定this调用函数,第一个参数也是指定的this对象,和call的区别是:第二个参数是一个数组,数组中存放本次调用要传递的参数,this指向obj
    test.apply(obj,[1,3]);
  • 函数对象的bind()方法,不会调用本函数,而是生成一个新的函数,这个函数的代码逻辑和原函数一样,但是this指向不一样,指向.bind(demo),demo
this.name="jack";
var demo={
name:"rose",
getName:function(){return this.name;}
}
console.log(demo.getName());//输出rose  这里的this指向demo
var another=demo.getName;
console.log(another())//输出jack  这里的this指向全局对象=
//运用bind方法更改this指向
var another2=another.bind(demo);
console.log(another2());//输出rose  这里this指向了demo对象了;

四. 基础数据类型:

  • 基本类型:String(任意字符串) Number(任意数字) boolean(true/false) null(null) undefined(undefined)
  • 对象类型(引用类型):Object(任意对象) Array(一种特别对象,数值下表,内部数据有序) Function(一种特别对象)
  • 分类:typeof instanceof
    • typeof:可以判断number string boolean undefined function;不可以判断:array与object、null与object;返回数据类型是字符串表达式
    • instanceof:判断对象的具体类型;可以判断undefined和null

五. JS ES6特性

ECMAScript 6 简称 ES6,是 JavaScript 语言的下一代标准,已经在2015年6月正式发布。
新特性:

  1. let、const块级作用域
  2. import导入模块、export导出模块
  3. ES6引入class(构造函数)、extends(继承)、super(原型)
  4. arrow functions (箭头函数)不需要 function 关键字来创建函数,省略 return 关键字,继承当上下文的 this 关键字
  5. template string (模板字符串)用{${name}}嵌入
  6. destructuring (解构)解构能让我们从对象或者数组里取出数据存为变量
  7. default 函数默认参数
  8. rest arguments (rest参数\剩余参数:拿到除开始参数外的参数)function func(a, …rest)
  9. Spread Operator (展开运算符)(…person)
  10. 对象初始化简写
  11. Promise:它主要用于处理异步回调代码,让代码不至于陷入回调嵌套的死路中。构造函数有callreject/resolve这几个方法,reject/resolve两个参数,将处理信息传递给promise函数
  12. Generators:生成器( generator)是能返回一个迭代器的函数。

六. JS 同步和异步的区别

javascript语言是一门“单线程”的语言,所谓单线程就是按次序执行,执行完一个任务再执行下一个。同步和异步的差别就在于这单线程上各个流程的执行顺序不同。

  • 同步:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
  • 异步:不进入主线程、而进入”任务队列”(task queue)的任务,自己做自己的任务,只有等主线程任务执行完毕,自己下面的任务也做完了,”任务队列”开始通知主线程,请求执行任务,该任务才会进入主线程执行。

  • 在JS中,异步编程只有四种情况:

    1. 定时器都是异步编程的,setTimeout和setInterval函数;setTimeout(function(){ alert(“Hello”); }, 3000);
    2. 所有的事件绑定都是异步编程的,click事件等;
    3. Ajax读取数据都是异步编程的,我们一般设置为异步编程;
    4. 回调函数callback都是异步编程的;

七. 深拷贝与浅拷贝

  • 浅拷贝:是指只复制一层对象,当对象的属性是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化(就是假设B复制了A,当修改A时,B也变化了)
  • 深拷贝:是指复制对象的所有层级(当修改A时,B没变化)。
  • 实现方法
    1. 通过递归实现,递归去复制所有层级属性
    2. 通过JSON解析实现,借用JSON对象的parse和stringify
    3. 借用JQ的extend方法

八. JS 的引用赋值与传值赋值

  • number,string类型都是基本类型是通过传值赋值的,而基本类型存放在栈区,访问时按值访问,赋值是按照普通方式赋值;
  • 对象和数组是通过引用来赋值的,所以改变arr1的同时arr2也会跟着改变;

九. js内存泄漏

  • 内存泄漏可以定义为一个应用,由于某些原因不再需要的内存没有被操作系统或者空闲内存池回收。
  • 常见的JavaScript泄漏

    1. 意外的全局变量
      js对未声明变量会在全局最高对象上创建它的引用,(是以属性存在的,而不是变量),如果在游览器上就是window对象,如果在node环境下就是global;如果未声明的变量缓存大量的数据,它可能只有在页面被刷新或者被关闭的时候才会释放内存,这样就造成了内存意外泄漏
      针对上面类型的内存泄漏我们在平时一定要声明变量,不要有全局直接引用。(在JavaScript文件中添加 ‘use strict’,开启严格模式,可以有效地避免上述问题。)

    2. 被遗忘的计时器或回调
      js中常用的定时器setInterval()、setTimeout().他们都是规定延迟一定的时间执行某个代码,而其中setInterval()和链式setTimeout()在使用完之后如果没有手动关闭,会一直存在执行占用内存,所以在不用的时候我们可以通过clearInterval()、clearTimeout() 来关闭其对应的定时器,释放内存。

    3. 超出DOM引用
      有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除。

    4. 闭包
      闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。

    5. console.log
      作为前端平时使用console.log在控制台打出相对应的信息可以说是非常常见。但如果没有去掉console.log可能会存在内存泄漏。因为在代码运行之后需要在开发工具能查看对象信息,所以传递给console.log的对象是不能被垃圾回收。

十. click和onclick的区别

  • click 是方法,由程序员写语句调用;onclick是事件;方法触发事件
  • click本身是方法作用是触发onclick事件,只要执行了元素的click()方法,就会触发onclick事件;

十一. DOM操作——怎样添加、移除、移动、复制、创建和查找节点。

1. 创建新节点

createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点

2. 添加、移除、替换、插入

appendChild()
removeChild()
replaceChild()
insertBefore() //在已有的子节点前插入一个新的子节点

3. 查找

getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值(IE容错能力较强,会得到一个数组,其中包括id等于name值的)
getElementById() //通过元素Id,唯一性

4. 向页面中添加30000个li标签

https://www.jianshu.com/p/15c9f38e07cc

  • 当li数量少的时候可以直接循环插入节点
<!-- html -->
<ul id="container"></ul>

<!-- js-->
let container = document.getElementById('container');
for (let i = 0; i < 4; i++) {
    let item = document.createElement('li');
    item.innerText = i + 1;
    ul.appendChild(item);
}
  • 当我们要添加的 li 标签的数量很多的时候,如果我们还是采取这种方法的话,页面加载的速度会很慢。

  • 采用分治处理 / 批处理
    分治处理是采取分治算法思想,将很多的事件处理分为若干次处理的思想。
    主要方法是创建文档碎片 :document.createDocumentFrag
    上面例子的改良:

let container = document.getElementById('container');

let totalCount = 30000;     // 总共的 li 数量
let oneCount = 4;       // 每次添加的 li 数量
let base = 0;       // 用于计数,目前执行添加了几次批处理
let preCount = totalCount / oneCount;       // 总共需要执行的批处理次数

while (base * oneCount < preCount) {
    let fragment = document.createDocumentFragment();
    for (let i = 0; i < 4; i++) {
        let item = document.createElement('li');
        item.innerText = (base * oneCount) + i + 1;
        fragment.appendChild(item);
    }
    container.appendChild(fragment);
    base = base + 1;
}

十二. setTimeOut,promise和主程序的执行顺序

then和settimeout执行顺序,即setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.then()在本轮“事件循环”结束时执行。因此then 函数先输出,settimeout后输出。

十三. 事件机制之冒泡、捕获、传播和委托。

  • DOM事件流(event flow )存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
    事件捕获(event capturing):通俗的理解就是,当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件。
    事件冒泡(dubbed bubbling):与事件捕获恰恰相反,事件冒泡顺序是由内到外进行事件传播,直到根节点。
  • dom标准事件流的触发的先后顺序为:先捕获再冒泡,即当触发dom事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。

  • 说到事件冒泡与捕获就不得不提一下两个用于事件绑定的方法addEventListener、attachEvent。当然还有其它的事件绑定的方式这里不做介绍。
    addEventListener(event, listener, useCapture)
    参数定义:event—(事件名称,如click,不带on),listener—事件监听函数,useCapture—是否采用事件捕获进行事件捕捉,默认为false,即采用事件冒泡方式
    addEventListener在 IE11、Chrome 、Firefox、Safari等浏览器都得到支持。
    attachEvent(event,listener)
    参数定义:event—(事件名称,如onclick,带on),listener—事件监听函数。
    attachEvent主要用于IE浏览器,并且仅在IE10及以下才支持,IE11已经废了这个方法了(微软还是挺识趣的,慢慢向标准靠拢)。

十四. Js阻塞及解决思路

  • Js阻塞机制,跟Js引擎的单线程处理方式有关,每个window一个JS线程。所谓单线程,在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码。
  • 由于浏览器是事件驱动的(Event driven),因此浏览器中很多行为是异步的,很容易有事件被同时或者连续触发。当异步事件发生时,会创建事件并放入执行队列中,等待当前代码执行完成之后再执行这些代码,如鼠标点击事件发生、XMLHttpRequest 完成回调、定时器触发事件发生这些事件,都会被放入执行队列中等待。
  • js执行会阻塞DOM树的解析和渲染,那么css加载不会阻塞DOM树的解析(结构),但是会阻塞DOM树的渲染(样式)。

  • 解决JS阻塞(不全):

    1. html5的defer和async关键字
      defer延迟js执行;
      async异步执行。
    2. 动态加载js。
      查询文档加载状态,在合适的时候加载js。
  • 详情参考http://blog.csdn.net/talking12391239/article/details/21168489

十五. JS无缝滚动效果实现方法分析效果:

1.默认缓慢往左滚动
2.放到左箭头上还是向左滚动,放到右箭头上向右滚动
3.放到图片上停止滚动,移出继续滚动
思路:
1.计算图片列表ul的宽度
2.开启定时器,使其向左边距不断增大,造成向左运动的效果
3.图片列表复制一份,向左移动时,当左边距大于一份的宽度时,把它的左边距拉回到0。向右移动时,当左边距大于0时,把它的左边距拉到整个两份图片列表一半的宽度(即一份的宽度)。(拉的瞬间很快,用户察觉不到,造成一种无缝滚动的假象)
4.offsetLeft值的正负决定往哪边移动
5.放到图片上停止这个定时器,移开再开启

十六. 轮播图的实现原理

在html ,css 已经写好的情况下。最主要的就是js的功能问题了。轮播图的功能步骤如下:

  1. 先让图片轮播起来。基本就是写一个 play函数里面加定时器,每次使图片的index对象加一,当index大于最值,设置index等于第 一张图片.这样轮播图就动起来了。
  2. 轮播图动起来基本就是只显示一张图片隐藏其他的图片。我上面使用的是opacity 。
  3. 图片下面的按钮。主要就是使下面的按钮与上面的图片一一对应。然后点击下面的按钮显示对应的图片。
  4. 左右的上一张和下一张按钮。点击左边的上一张按钮图片向前显示,实现原理就是使 index 对象减一。点击边下一张按钮图片向后显示,实现原理就是使 index 对象加一。
  5. 对应上一张和下一张连续点击的问题就是给这两个按钮加上延时器。
  6. 当鼠标放在轮播图区域时停止轮播,实现原理就是清除定时器,离开开始轮播就是加上定时器。
Lililich's Blog