天天看点

金三银四,求职季,而你必须要懂的原生JS(面试题)

  1. 原始类型有哪几种?null 是对象吗?原始数据类型和复杂数据类型存储有什么区别?

原始类型有6种,分别是undefined,null,bool,string,number,symbol(ES6新增)。

虽然 typeof null 返回的值是 object,但是null不是对象,而是基本数据类型的一种。

原始数据类型存储在栈内存,存储的是值。

复杂数据类型存储在堆内存,存储的是地址。当我们把对象赋值给另外一个变量的时候,复制的是地址,指向同一块内存空间,当其中一个对象改变时,另一个对象也会变化。

  1. typeof 是否正确判断类型? instanceof呢? instanceof 的实现原理是什么?

    首先 typeof 能够正确的判断基本数据类型,但是除了 null, typeof null输出的是对象。

    但是对象来说,typeof 不能正确的判断其类型, typeof 一个函数可以输出 ‘function’,而除此之外,输出的全是 object,这种情况下,我们无法准确的知道对象的类型。

    instanceof可以准确的判断复杂数据类型,但是不能正确判断基本数据类型。(正确判断数据类型请戳:github.com/YvetteLau/B…)

    instanceof 是通过原型链判断的,A instanceof B, 在A的原型链中层层查找,是否有原型等于B.prototype,如果一直找到A的原型链的顶端(null;即Object.prototype.proto),仍然不等于B.prototype,那么返回false,否则返回true.

    instanceof的实现代码:

    // L instanceof R

    function instance_of(L, R) {//L 表示左表达式,R 表示右表达式

    var O = R.prototype;// 取 R 的显式原型

    L = L.proto; // 取 L 的隐式原型

    while (true) {

    if (L === null) //已经找到顶层

    return false;

    if (O === L) //当 O 严格等于 L 时,返回 true

    return true;

    L = L.proto; //继续向上一层原型链查找

    }

    }

    复制代码

  2. for of , for in 和 forEach,map 的区别。

for…of循环:具有 iterator 接口,就可以用for…of循环遍历它的成员(属性值)。for…of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象、Generator 对象,以及字符串。for…of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。对于普通的对象,for…of结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。可以中断循环。

for…in循环:遍历对象自身的和继承的可枚举的属性, 不能直接获取属性值。可以中断循环。

forEach: 只能遍历数组,不能中断,没有返回值(或认为返回值是undefined),不修改原数组。

map: 只能遍历数组,不能中断,返回值是修改后的数组,不修改原数组。

PS: Object.keys():返回给定对象所有可枚举属性的字符串数组。

如还不了解 iterator 接口或 for…of, 请先阅读ES6文档: Iterator 和 for…of 循环

更多细节请戳: github.com/YvetteLau/B…

  1. 如何判断一个变量是不是数组?

使用 Array.isArray 判断,如果返回 true, 说明是数组

使用 instanceof Array 判断,如果返回true, 说明是数组

使用 Object.prototype.toString.call 判断,如果值是 [object Array], 说明是数组

通过 constructor 来判断,如果是数组,那么 arr.constructor === Array. (不准确,因为我们可以指定 obj.constructor = Array)

function fn() {

console.log(Array.isArray(arguments)); //false; 因为arguments是类数组,但不是数组

console.log(Array.isArray([1,2,3,4])); //true

console.log(arguments instanceof Array); //fasle

console.log([1,2,3,4] instanceof Array); //true

console.log(Object.prototype.toString.call(arguments)); //[object Arguments]

console.log(Object.prototype.toString.call([1,2,3,4])); //[object Array]

console.log(arguments.constructor === Array); //false

arguments.constructor = Array;

console.log(arguments.constructor === Array); //true

console.log(Array.isArray(arguments)); //false

}

fn(1,2,3,4);

复制代码

5. 类数组和数组的区别是什么?

类数组:

1)拥有length属性,其它属性(索引)为非负整数(对象中的索引会被当做字符串来处理);

2)不具有数组所具有的方法;

类数组是一个普通对象,而真实的数组是Array类型。

常见的类数组有: 函数的参数 arugments, DOM 对象列表(比如通过 document.querySelectorAll 得到的列表), jQuery 对象 (比如 $(“div”)).

类数组可以转换为数组:

//第一种方法

Array.prototype.slice.call(arrayLike, start);

//第二种方法

[…arrayLike];

//第三种方法:

Array.from(arrayLike);

复制代码PS: 任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组。

Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象。

  1. == 和 === 有什么区别?

    === 不需要进行类型转换,只有类型相同并且值相等时,才返回 true.

    == 如果两者类型不同,首先需要进行类型转换。具体流程如下:

首先判断两者类型是否相同,如果相等,判断值是否相等.

如果类型不同,进行类型转换

判断比较的是否是 null 或者是 undefined, 如果是, 返回 true .

判断两者类型是否为 string 和 number, 如果是, 将字符串转换成 number

判断其中一方是否为 boolean, 如果是, 将 boolean 转为 number 再进行判断

判断其中一方是否为 object 且另一方为 string、number 或者 symbol , 如果是, 将 object 转为原始类型再进行判断

let person1 = {

age: 25

}

let person2 = person1;

person2.gae = 20;

console.log(person1 === person2); //true,注意复杂数据类型,比较的是引用地址

复制代码思考: [] == ![]

我们来分析一下: [] == ![] 是true还是false?

首先,我们需要知道 ! 优先级是高于 == (更多运算符优先级可查看: 运算符优先级)

![] 引用类型转换成布尔值都是true,因此![]的是false

根据上面的比较步骤中的第五条,其中一方是 boolean,将 boolean 转为 number 再进行判断,false转换成 number,对应的值是 0.

根据上面比较步骤中的第六条,有一方是 number,那么将object也转换成Number,空数组转换成数字,对应的值是0.(空数组转换成数字,对应的值是0,如果数组中只有一个数字,那么转成number就是这个数字,其它情况,均为NaN)

0 == 0; 为true

  1. ES6中的class和ES5的类有什么区别?

ES6 class 内部所有定义的方法都是不可枚举的;

ES6 class 必须使用 new 调用;

ES6 class 不存在变量提升;

ES6 class 默认即是严格模式;

ES6 class 子类必须在父类的构造函数中调用super(),这样才有this对象;ES5中类继承的关系是相反的,先有子类的this,然后用父类的方法应用在this上。

  1. 数组的哪些API会改变原数组?

修改原数组的API有:

splice/reverse/fill/copyWithin/sort/push/pop/unshift/shift

不修改原数组的API有:

slice/map/forEach/every/filter/reduce/entry/entries/find

  1. let、const 以及 var 的区别是什么?

let 和 const 定义的变量不会出现变量提升,而 var 定义的变量会提升。

let 和 const 是JS中的块级作用域

let 和 const 不允许重复声明(会抛出错误)

let 和 const 定义的变量在定义语句之前,如果使用会抛出错误(形成了暂时性死区),而 var 不会。

const 声明一个只读的常量。一旦声明,常量的值就不能改变(如果声明是一个对象,那么不能改变的是对象的引用地址)

  1. 在JS中什么是变量提升?什么是暂时性死区?

    变量提升就是变量在声明之前就可以使用,值为undefined。

    在代码块内,使用 let/const 命令声明变量之前,该变量都是不可用的(会抛出错误)。这在语法上,称为“暂时性死区”。暂时性死区也意味着 typeof 不再是一个百分百安全的操作。

    typeof x; // ReferenceError(暂时性死区,抛错)

    let x;

    复制代码typeof y; // 值是undefined,不会报错

    复制代码暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

  2. 如何正确的判断this? 箭头函数的this是什么?

    this的绑定规则有四种:默认绑定,隐式绑定,显式绑定,new绑定.

函数是否在 new 中调用(new绑定),如果是,那么 this 绑定的是新创建的对象。

函数是否通过 call,apply 调用,或者使用了 bind (即硬绑定),如果是,那么this绑定的就是指定的对象。

函数是否在某个上下文对象中调用(隐式绑定),如果是的话,this 绑定的是那个上下文对象。一般是 obj.foo()

如果以上都不是,那么使用默认绑定。如果在严格模式下,则绑定到 undefined,否则绑定到全局对象。

如果把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind, 这些值在调用时会被忽略,实际应用的是默认绑定规则。

箭头函数没有自己的 this, 它的this继承于上一层代码块的this。

测试下是否已经成功Get了此知识点(浏览器执行环境):

var number = 5;

var obj = {

number: 3,

fn1: (function () {

var number;

this.number *= 2;

number = number * 2;

number = 3;

return function () {

var num = this.number;

this.number *= 2;

console.log(num);

number *= 3;

console.log(number);

}

})();

}

var fn1 = obj.fn1;

fn1.call(null);

obj.fn1();

console.log(window.number);

复制代码如果this的知识点,您还不太懂,请戳: 嗨,你真的懂this吗?

  1. 词法作用域和this的区别。

词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的

this 是在调用时被绑定的,this 指向什么,完全取决于函数的调用位置(关于this的指向问题,本文已经有说明)

  1. 谈谈你对JS执行上下文栈和作用域链的理解。

    执行上下文就是当前 JavaScript 代码被解析和执行时所在环境, JS执行上下文栈可以认为是一个存储函数调用的栈结构,遵循先进后出的原则。

JavaScript执行在单线程上,所有的代码都是排队执行。

一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。

每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行-完成后,当前函数的执行上下文出栈,并等待垃圾回收。

浏览器的JS执行引擎总是访问栈顶的执行上下文。

全局上下文只有唯一的一个,它在浏览器关闭时出栈。

作用域链: 无论是 LHS 还是 RHS 查询,都会在当前的作用域开始查找,如果没有找到,就会向上级作用域继续查找目标标识符,每次上升一个作用域,一直到全局作用域为止。

题难不难?不难!继续挑战一下!难!知道难,就更要继续了!

  1. 什么是闭包?闭包的作用是什么?闭包有哪些使用场景?

    闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包最常用的方式就是在一个函数内部创建另一个函数。

    闭包的作用有:

封装私有变量

模仿块级作用域(ES5中没有块级作用域)

实现JS的模块

  1. call、apply有什么区别?call,aplly和bind的内部是如何实现的?

    call 和 apply 的功能相同,区别在于传参的方式不一样:

fn.call(obj, arg1, arg2, …),调用一个函数, 具有一个指定的this值和分别地提供的参数(参数的列表)。

fn.apply(obj, [argsArray]),调用一个函数,具有一个指定的this值,以及作为一个数组(或类数组对象)提供的参数。

call核心:

将函数设为传入参数的属性

指定this到函数并传入给定参数执行函数

如果不传入参数或者参数为null,默认指向为 window / global

删除参数上的函数

Function.prototype.call = function (context) {

if (!context) {

//context为null或者是undefined

context = typeof window === ‘undefined’ ? global : window;

}

context.fn = this; //this指向的是当前的函数(Function的实例)

let rest = […arguments].slice(1);//获取除了this指向对象以外的参数, 空数组slice后返回的仍然是空数组

let result = context.fn(…rest); //隐式绑定,当前函数的this指向了context.

delete context.fn;

return result;

}

//测试代码

var foo = {

name: ‘Selina’

}

var name = ‘Chirs’;

function bar(job, age) {

console.log(this.name);

console.log(job, age);

}

bar.call(foo, ‘programmer’, 20);

// Selina programmer 20

bar.call(null, ‘teacher’, 25);

// 浏览器环境: Chirs teacher 25; node 环境: undefined teacher 25

复制代码

apply:

apply的实现和call很类似,但是需要注意他们的参数是不一样的,apply的第二个参数是数组或类数组.

Function.prototype.apply = function (context, rest) {

if (!context) {

//context为null或者是undefined时,设置默认值

context = typeof window === ‘undefined’ ? global : window;

}

context.fn = this;

let result = context.fn(…rest);

delete context.fn;

return result;

}

var foo = {

name: ‘Selina’

}

var name = ‘Chirs’;

function bar(job, age) {

console.log(this.name);

console.log(job, age);

}

bar.apply(foo, [‘programmer’, 20]);

// Selina programmer 20

bar.apply(null, [‘teacher’, 25]);

// 浏览器环境: Chirs programmer 20; node 环境: undefined teacher 25

复制代码

bind

bind 和 call/apply 有一个很重要的区别,一个函数被 call/apply 的时候,会直接调用,但是 bind 会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。

Function.prototype.bind = function(context) {

if(typeof this !== “function”){

throw new TypeError(“not a function”);

}

let self = this;

let args = […arguments].slice(1);

function Fn() {};

Fn.prototype = this.prototype;

let bound = function() {

let res = […args, …arguments]; //bind传递的参数和函数调用时传递的参数拼接

context = this instanceof Fn ? this : context || this;

return self.apply(context, res);

}

//原型链

bound.prototype = new Fn();

return bound;

}

var name = ‘Jack’;

function person(age, job, gender){

console.log(this.name , age, job, gender);

}

var Yve = {name : ‘Yvette’};

let result = person.bind(Yve, 22, ‘enginner’)(‘female’);

复制代码

小编今天就更新到这里了,如果想要获取更多的学习资料可以加2239966169这个美丽的小姐姐。