JavaScript 基础面试题(1)

1,434 阅读5分钟

1. 图片优化(懒加载/预加载)

1、使用base_64编码图片或SVG图片代替原始的png与jpeg图片,再像素要求不高的情况下使用jpeg图片代替png图片

2、图片懒加载,在页面上的未可视区域可以添加一个滚动条事件,判断图片位置与浏览器顶端 的距离与页面的距离,如果前者小于后者,优先加载。

- 原生JavaScript实现图片的懒加载

- jQuery插件 jquery.lazy 

- vue中使用 vue-lazyload

- mint-ui 插件内置懒加载组件

3、图片的预加载,如果为幻灯片、相册等,可以使用图片预加载技术,将当前展示图片的前一张和后一张优先 下载。初始化的时候获得图片的src之后为每一个元素提前添加图片的地址路径。保证再第二张图片显示的时候已经加载到页面当中

实现方法:循环图片数据,实例化对象的方式创建图片元素 new image() 动态绑定每一个image的src属性,并追加到DOM流中

2.数据类型种类

  • JS 中分为七种内置类型,七种内置类型又分为两大类型:基本类型和对象(Object)。

  • 基本类型有六种: null,undefined,boolean,number,string,symbol。

  • 对象(Object)是引用类型,在使用过程中会遇到浅拷贝和深拷贝的问题。


let a = { name: 'FE' }
let b = a
b.name = 'EF'
console.log(a.name) // EF

3、typeof

  • typeof 对于基本类型,除了 null 都可以显示正确的类型

  • 想获得一个变量的正确类型,可以通过 Object.prototype.toString.call(xx)

4、 new 操作符的作用

-    新生成了一个对象

-    链接到原型

-    绑定this

-    返回新对象
function create() {
    // 创建一个空的对象
    let obj = new Object()
    // 获得构造函数
    let Con = [].shift.call(arguments)
    // 链接到原型
    obj.__proto__ = Con.prototype
    // 绑定 this,执行构造函数
    let result = Con.apply(obj, arguments)
    // 确保 new 出来的是个对象
    return typeof result === 'object' ? result : obj
}

5、instanceof操作符

判断对象属于某一个类,回去查找对象的constructor的prototype

6、this 环境上下文对象

function foo() {
	console.log(this.a)
}
var a = 1
foo()

var obj = {
	a: 2,
	foo: foo
}
obj.foo()

以上两者情况 this 只依赖于调用函数前的对象,优先级是第二个情况大于第一个情况

以下情况是优先级最高的,this 只会绑定在 c 上,不会被任何方式修改 this 指向

var c = new foo()
c.a = 3
console.log(c.a)

还有种就是利用 call,apply,bind 改变 this,这个优先级仅次于 new

function a() {
    return () => {
        return () => {
        	console.log(this)
        }
    }
}
console.log(a()()())

箭头函数其实是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,因为调用 a 符合前面代码中的第一个情况,所以 this 是 window。并且 this 一旦绑定了上下文,就不会被任何代码改变

7.作用域/作用域链

1、作用域

  • 种类:JS中有三种作用域,全局作用域,函数作用域,ES6新推出块级作用域

  • 概念:一个变量的可访问规则,再函数创建的时候就已经定义好作用域,整个的JS文件执行有一个最外层的全局作用域(window)

  • 使用: 本作用域内部的所有变量都可已再本作用域内部访问,外部无法访问。内部可访问上级作用域变量,本作用于内部所用使用var声明的变量会有一个作用域提升的过程,let与const声明的变量没有变量提升

2、作用域链

  • 一个变量的访问规则的链式操作

  • 可以把它理解成包含自身变量对象和上级变量对象的列表,通过 [[Scope]] 属性查找上级变量

  • 当访问一个变量的时候,先在本作用域内部进行查找,如果没有去上级作用域进行查找,直到全局作用域window下面,都没有,返回undefined

8.闭包(closure)

1、特点:

  • 内层作用域可以访问外层作用域的变量

  • 闭包就是能够读取其他函数内部变量的函数

  • 函数 A 返回了一个函数 B,并且函数 B 中使用了函数 A 的变量,函数 B 就被称为闭包。

  • 闭包函数引用的变量是存储在堆上的,所以说,当闭包函数弹出调用栈之后,闭包返回的函数依然能调用到闭包函数的变量

2、优点:

  • 使用闭包可以形成独立的空间,延长变量的生命周期,保存中间状态值

  • 可以封装一些私有变量,外部无法进行直接访问(例如用户登陆状态计数器)创建立即执行函数(闭包)实现JS模块化封装

  • 解决var声明的循环语句变量无法长久保存的问题

3、缺点:

  • 滥用闭包函数会造成内存泄露,因为闭包中引用到的包裹函数中定义的变量都 永远不会被释放,所以我们应该在必要的时候,及时释放这个闭包函数,将不再使用的闭包引用设置为null;

  • 由于函数内部的变量都被保存在内存中, 会导致内存消耗大;

4、相关代码:

for ( var i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}
// 每次循环都会输出 5 无法保存变量
for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j);
    }, j * 1000);
  })(i);
}
// 创建立即执行函数,形成闭包,保存每一次循环的变量
// 解决下列调用打印每次都是5的问题
var arr = []
for (var i = 0; i < 5; i++) {
    arr.push(function() { console.log(i) })
}

// 使用闭包的方式
for (var i = 0; i < 5; i++) {
    (function(i) {
        arr.push(function() {
            console.log(i)
        })
    })(i)
}

// 使用ES6的let创建块级作用域
for (let i = 0; i < 5; i++) {
    arr.push(function() {
        console.log(i)
    })
}

// 使用setTimeout的第三个参数
for (let i = 0; i < 5; i++) {
    setTimeout(function() {
        arr.push(function() {
            console.log(i)
        })
    }, 0,i)
}