JS面试相关问题整理

标题

  • 前言
  • 参考
  • JS基本数据类型和引用数据类型
  • 什么是堆?什么是栈?它们之间有什么区别和联系?
  • 内部属性 [[Class]] 是什么?
  • 介绍 js 有哪些内置对象?
  • null 和 undefined 的区别?
  • 从(内存)来看 null 和 undefined 本质的区别是什么?
  • 如何获取安全的 undefined 值?
  • JavaScript 原型,原型链? 有什么特点?
    • 调用 person1 的“实际定义在 Object 上”的方法时,会发生什么?
    • 获取原型的方法?
    • 在 js 中不同进制数字的表示方式, 整数的安全范围是多少?
  • typeof NaN 的结果是什么?
  • 其他值到布尔类型的值的转换规则?
  • 操作符系列
    • `+ `操作符什么时候用于字符串的拼接?
    • `~ `操作符的作用?
    • `== `操作符的强制类型转换规则?
  • 箭头函数和普通函数有什么区别
  • 正则表达式
  • 如何将浮点数点左边的数每三位添加一个逗号,如 12000000.11 转化为『12,000,000.11』?
  • 正则匹配url
    • 匹配url参数 (错误)
    • url参数转对象
    • js使用正则获取当前页面url指定参数
  • 请用js去除字符串空格
  • 常用正则表达式
  • 生成随机数的各种方法?
  • 如何实现数组的随机排序?
  • 用split()来颠倒字符串顺序
  • 交换字符串中的两个单词
  • str.search(reg)和str.match与reg.exec区别,搜索下标
  • JavaScript 继承的几种实现方式?
  • Javascript 的作用域链?
  • JS 作用域及作用域链
  • 谈谈 This 对象的理解。
  • eval 是做什么的?
  • 事件委托详解
  • e.target与e.currentTarget
  • ["1", "2", "3"].map(parseInt) 答案是多少?
  • 什么是闭包,为什么要用它?
  • "use strict"; 是什么意思
  • 如何判断一个对象是否属于某个类?
  • 对于 JSON 的了解?
  • 页面js 延迟加载的方式有哪些?
  • Ajax 是什么? 如何创建一个 Ajax?

前言

参考

Front-end-basic-knowledge2
Front-End-Interview-Notebook1
FE-Interview-Questions
https://muyiy.cn/blog/0
JS前端进阶/面试

JS基本数据类型和引用数据类型

JS分两种数据类型:

  • 原始数据类型: Number、String、Boolean、Null、 Undefined、Symbol(ES6)
  • 引用数据类型: Object (对象、数组和函数)

两种类型的区别是:存储位置不同

  • 原始数据类型直接存储在(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
  • 引用数据类型存储在(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在
    栈中存储了指针
    ,当查询引用类型的变量时, 先从栈中读取内存地址, 然后再通过地址找到堆中的值

JS基本数据类型和引用数据类型的区别及深浅拷贝

什么是堆?什么是栈?它们之间有什么区别和联系?

堆和栈的概念存在于数据结构中和操作系统内存中。

在数据结构中,栈中数据的存取方式为先进后出。而堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。完全二叉树是堆的一种实现方式。

在操作系统中,内存被分为栈区和堆区。

  • 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

  • 堆区内存一般由程序员分配释放,若程序员不释放,程序结束时可能由垃圾回收机制回收(标记清除)。

内部属性 [[Class]] 是什么?

所有 typeof返回值为 “object” 的对象(如数组)都包含一个内部属性[[Class]](我们可以把它看作一个内部的分类,而非传统的面向对象意义上的类)。这个属性无法直接访问,一般通过 Object.prototype.toString(..)来查看。例如:

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

Object.prototype.toString.call( /regex-literal/i );
// "[object RegExp]"

介绍 js 有哪些内置对象?

回答:

js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。一般我们经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象。

涉及知识点:

全局的对象( global objects )或称标准内置对象,不要和 "全局对象(global object)" 混淆。这里说的全局的对象是说在
全局作用域里的对象。全局作用域中的其他对象可以由用户的脚本创建或由宿主程序提供。

标准内置对象的分类

(1)值属性,这些全局属性返回一个简单值,这些值没有自己的属性和方法。

例如 Infinity、NaN、undefined、null 字面量

(2)函数属性,全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。

例如 eval()、parseFloat()、parseInt() 等

(3)基本对象,基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。

例如 Object、Function、Boolean、Symbol、Error 等

(4)数字和日期对象,用来表示数字、日期和执行数学计算的对象。

例如 Number、Math、Date

(5)字符串,用来表示和操作字符串的对象。

例如 String、RegExp

(6)可索引的集合对象,这些对象表示按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。例如 Array

(7)使用键的集合对象,这些集合对象在存储数据时会使用到键,支持按照插入顺序来迭代元素。

例如 Map、Set、WeakMap、WeakSet

(8)矢量集合,SIMD 矢量集合中的数据会被组织为一个数据序列。

例如 SIMD 等

(9)结构化数据,这些对象用来表示和操作结构化的缓冲区数据,或使用 JSON 编码的数据。

例如 JSON 等

(10)控制抽象对象

例如 Promise、Generator 等

(11)反射

例如 Reflect、Proxy

(12)国际化,为了支持多语言处理而加入 ECMAScript 的对象。

例如 Intl、Intl.Collator 等

(13)WebAssembly

(14)其他

例如 arguments

null 和 undefined 的区别?

首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。

undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。

当我们对两种类型使用 typeof 进行判断的时候,Null 类型化会返回“object”,这是一个历史遗留的问题。

当我们使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。

从(内存)来看 null 和 undefined 本质的区别是什么?

Null 只有一个值,是 null。不存在的对象。
Undefined 只有一个值,是undefined。没有初始化。undefined 是从 null 中派生出来的。
简单理解就是:undefined 是没有定义的,null 是定义了但是为空。
解答:
(内存)
给一个全局变量赋值为null,相当于将这个变量的指针对象以及清空,如果是给对象的属性 赋值为null,或者局部变量赋值为null,相当于给这个属性分配了一块空的内存,然后为null, JS会回收全局变量为null的对象。

给一个全局变量赋值为undefined,相当于将这个对象的清空,但是这个对象依旧存在,如果是给对象的属性赋值 为undefined,说明这个为空值

如何获取安全的 undefined 值?

因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。

表达式 void ___ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。

按惯例我们用 void 0 来获得 undefined。

JavaScript 原型,原型链? 有什么特点?

JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象(构造函数的prototype),对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

作用:

  • 1.共享数据,节省空间
  • 2.继承

原型:

在 js 中我们是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性值,这个属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当我们使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype属性对应的值,在 ES5 中这个指针被称为对象的原型。(实例对象的__proto__)

一般来说我们是不应该能够获取到这个值的,但是现在浏览器中都实现了 __proto__属性来让我们访问这个属性,但是我们最好不要使用这个属性,因为它不是规范中规定的。ES5 中新增了一个 Object.getPrototypeOf()方法,我们可以通过这个方法来获取对象的原型。

原型链:

当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,直到终点NULL,也就是原型链的概念。(通过__proto__)
JS面试相关问题整理_第1张图片
特点:
JavaScript 对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

调用 person1 的“实际定义在 Object 上”的方法时,会发生什么?

person1.valueOf()

这个方法仅仅返回了被调用对象的值。在这个例子中发生了如下过程:

  • 浏览器首先检查,person1 对象是否具有可用的 valueOf()方法。
  • 如果没有,则浏览器检查 person1 对象的原型对象(即 Person构造函数的prototype属性所指向的对象)是否具有可用的valueof() 方法。
  • 如果也没有,则浏览器检查 Person() 构造函数的prototype属性所指向的对象的原型对象(即 Object构造函数的prototype属性所指向的对象)是否具有可用的valueOf() 方法。这里有这个方法,于是该方法被调用。

重要:prototype 属性大概是 JavaScript 中最容易混淆的名称之一。你可能会认为,this 关键字指向当前对象的原型对象,其实不是(还记得么?原型对象是一个内部对象,应当使用 __proto__ 访问)。prototype 属性包含(指向)一个对象,你在这个对象中定义需要被继承的成员。

获取原型的方法?

p.proto
p.constructor.prototype
Object.getPrototypeOf(p)
Reflect.getPrototypeOf(p)

对象原型

MDN:原型

在 js 中不同进制数字的表示方式, 整数的安全范围是多少?

以 0X、0x 开头的表示为十六进制。

以 0、0O、0o 开头的表示为八进制。

以 0B、0b 开头的表示为二进制格式。

安全整数指的是,在这个范围内的整数转化为二进制存储的时候不会出现精度丢失,能够被“安全”呈现的最大整数是 2^53 - 1,即9007199254740991

typeof NaN 的结果是什么?

NaN 意指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出
数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。

typeof NaN; // "number"

NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即x === x不成立)的值。即 NaN != NaN
为 true。

其他值到布尔类型的值的转换规则?

ES5 规范 9.2 节中定义了抽象操作 ToBoolean,列举了布尔强制类型转换所有可能出现的结果。

以下这些是假值:
• undefined
• null
• false
• +0、-0 和 NaN
• “”

假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值

操作符系列

+操作符什么时候用于字符串的拼接?

JavaScript 加号运算符详解

console.log('1' + 2 + 2) // '122'
console.log(1 + '2' + 2) // '122'
console.log(1 + 2 + '0' + 1 + 2) // '3012'

let ds = new Date()
console.log(+ds) // 1587914346349
console.log('2' + ds) //2Sun Apr 26 2020 23:19:06 GMT+0800 (GMT+08:00)
console.log(1 + ds) // 1Sun Apr 26 2020 23:19:06 GMT+0800 (GMT+08:00)

console.log(parseFloat('12.3b')) //12.3
console.log(parseInt('12.3b')) // 12
console.log(+'12.3b') // NaN
console.log(Number('12.3b')) // NaN

简单概括为下面4个步骤:

  1. 值进行GetValue()操作。

  2. 值进行ToPrimitive()操作,

  3. 若一方为String类型,2个值都进行ToString()转换,最后进行字符串连接操作。

  4. 若都不是String类型,2个值都进行ToNumber()转换,最后进行算数加法运算。

~操作符的作用?

~ 返回 2 的补码,并且~会将数字转换为 32 位整数,因此我们可以使用~来进行取整操作

~x大致等同于 -(x+1)。

let a = -10.11
console.log(~a + 1) // 10

console.log(~~a)

==操作符的强制类型转换规则?

(1)字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。

(2)其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。

(3)null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。

(4)对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。

(5)如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。

(6)如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true,否则,返回 false。

详细资料可以参考: 《JavaScript 字符串间的比较》

箭头函数和普通函数有什么区别

(箭头函数是根据外层(函数或者全局)作用域(词法作用域)来决定this。)

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象,用call apply bind也不能改变this指向
  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
    箭头函数没有原型对象prototype

正则表达式

如何将浮点数点左边的数每三位添加一个逗号,如 12000000.11 转化为『12,000,000.11』?

// 这是个原来的答案
function format(number) {
     
  return number && number.replace(/(?!^)(?=(\d{3})+\.)/g, ",");
}

这是网上,经过自己改进的

// 初版- 只匹配整数
console.log('12345'.replace(/(?!^)(?=(\d{3})+$)/g, ','))
// 12,345
// 完善版 - 匹配小数
console.log('1234567.123456'.replace(/\B(?=(\d{3})+(\.|$))/g, ','))
// /\B(?=(\d{3})+\b)/g
// 1,234,567.123,456
/*
用$因为,?=是匹配后面带有\B(?=(\d{3})+(\.|$))这串东西前面的空位,
所以用$表示从后面匹配3位, 用\B 因为 `.`也是边界,所以小数也能匹配
*/

这是网上原版1

// 这个小数也会加,整数也正常
'78961324.123456'.split('').reverse().join('')
  .replace(/(\d{3}\B)/g, '$1,').split('').reverse().join('')
// 只能只能匹配小数
function format(number) {
     
  return number && number.replace(/(?!^)(?=(\d{3})+)/g, ',')
}

/*
\B- 匹配不是边界, ( 遇到. - 等符号,或者单词末尾 )这些都是边界, 所以不能匹配到
*/
(?!^)匹配 后面没有’^'的空位(匹配不是开头的空位)

var cc = '789654.123456'
var ccr = /(\d{3}\B)/g
console.log(cc.match(ccr)) // [ '789', '123' ]
//如上, 匹配连续3个数字,但不为边界,因为'.'是边界,所以不能匹配 654

var hhr = /(?!^)\d{3}/g
var ggr = /\B\d{3}/g
console.log(cc.match(hhr)) // [ '896', '123', '456' ]
console.log(cc.match(ggr)) // [ '896', '234']

另外的方法

function format(str) {
     
    let s = ''
    let count = 0
    for (let i = str.length - 1; i >= 0; i--) {
     
        s = str[i] + s
        count++
        if (count % 3 == 0 && i != 0) {
     
            s = ',' + s
        }
    }
    return s
}
function format(str) {
     
    return str.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
}

正则匹配url

var str = "http://www.runoob.com:80/html/html-tutorial.html";
var patt1 = /(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)/;
arr = str.match(patt1);

第三行代码str.match(patt1)返回一个数组,实例中的数组包含 5 个元素,索引 0 对应的是整个字符串,索引 1 对应第一个匹配符(括号内),以此类推。

  • 第一个括号子表达式捕获 Web 地址的协议部分。该子表达式匹配在冒号和两个正斜杠前面的任何单词。

  • 第二个括号子表达式捕获地址的域地址部分。子表达式匹配非:/之后的一个或多个字符。

  • 第三个括号子表达式捕获端口号(如果指定了的话)。该子表达式匹配冒号后面的零个或多个数字。只能重复一次该子表达式。

  • 最后,第四个括号子表达式捕获 Web 地址指定的路径和/或页信息。该子表达式能匹配不包括#或空格字符的任何字符序列。

将正则表达式应用到上面的 URI,各子匹配项包含下面的内容:

  • 第一个括号子表达式包含 http
  • 第二个括号子表达式包含 www.runoob.com
  • 第三个括号子表达式包含 :80
  • 第四个括号子表达式包含 /html/html-tutorial.html

匹配url参数 (错误)

var str = 'http://www.localhost.com:80/index.php?ab=1&c=2&dd=456#g'
var patt1 = /(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)\?((?:&??\w+={1}\w*)*)/
console.log(str.match(patt1))
// \?((?:&?\w+={1}\w*)*) 为匹配? 后面的参数, 使用 ?: 是防止保存最后一个参数的引用(&dd=456)

url参数转对象

方法一:

var str = 'http://www.localhost.com:80/index.php?AB=1&c=2&dd=456'

function url2Obj(url) {
     
  let obj = {
     }
  let query = url.split('?')[1]
  let arr = query.split('&')
  for (let k of arr) {
     
    let [name, value] = k.split('=') // 解构
    obj[name] = value
  }
  return obj
}
let obj = url2Obj(str)
console.log(obj)

// 处理重复参数
var search="?uname=dingdin&upwd=12345&favs=swimming&favs=running&favs=music";
      function searchObj(str){
     
         //去掉?
         var str=str.slice(1);
         //根据“&”分割字符串
         var arr=str.split("&");
         //定义空的obj,保存对象
         var obj={
     };
         //循环遍历分割后的数组
         for(var p of arr){
     
            //根据“=”分割
           var arr2=p.split("=");
           //解构
           var [name,value]=arr2;
           //如果obj中的name为undefined就把值填进去,否则就连接
           if(obj[name]==undefined){
     
               obj[name]=value;
           }else{
     
               obj[name]=[].concat(value,obj[name])
           }
         }
         return obj;
      }
     var a= searchObj(search);
     console.log(a);

方法二:

	var keywords = window.location.search.substr(1);
	var result = keywords.replace(/&/g, '","').replace(/=/g, '":"');
	var reqDataString = '{"' + result + '"}';
	var aaa = JSON.parse(reqDataString); 

js处理url将url参数转为对象(有重复参数)
将url后面的参数转换成对象格式
Js将对象转换为URL参数及将URL参数转换为对象的方法

js使用正则获取当前页面url指定参数

function QueryString(item) {
     
    var sValue = location.search.match(new RegExp("[\?\&]" + item + "=([^\&]*)(\&?)", "i"));
    // 这句是我的,为什么可以不用'\'号?
   // var sVal = str.match(new RegExp(`[?&]${query}=([^&]*)`, 'i'))
    return sValue ? sValue[1] : sValue;
}

请用js去除字符串空格

去除所有空格

str.replace(/\s/g, '')

去除两边空格

str.replace(/^\s+|\s+$/g, '')

// 原生方法
str.trim()

常用正则表达式

// (1)匹配 16 进制颜色值
var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;

// (2)匹配日期,如 yyyy-mm-dd 格式
var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;

// (3)匹配 qq 号
var regex = /^[1-9][0-9]{4,10}$/g;

// (4)手机号码正则
var regex = /^1[34578]\d{9}$/g;

// (5)用户名正则
var regex = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;

详细资料可以参考:
正则表达式:菜鸟
推荐这个,看评论区和案例,特别是(?=)
《前端表单验证常用的 15 个 JS 正则表达式》
《JS 常用正则汇总》
正则表达式实例
通过实例学习正则表达式

常用JS正则大全(2019年11月12日更新)

生成随机数的各种方法?

《JS - 生成随机数的方法汇总(不同范围、类型的随机数)》

如何实现数组的随机排序?

// (1)使用数组 sort 方法对数组元素随机排序,让 Math.random() 出来的数与 0.5 比较,如果大于就返回 1 交换位置,如果小于就返回 -1,不交换位置。

function randomSort(a, b) {
     
  return Math.random() > 0.5 ? -1 : 1;
}

//  缺点:每个元素被派到新数组的位置不是随机的,原因是 sort() 方法是依次比较的。

// (2)随机从原数组抽取一个元素,加入到新数组

function randomSort(arr) {
     
  var result = [];

  while (arr.length > 0) {
     
    var randomIndex = Math.floor(Math.random() * arr.length);
    result.push(arr[randomIndex]);
    arr.splice(randomIndex, 1);
  }

  return result;
}

// (3)随机交换数组内的元素(洗牌算法类似)

function randomSort(arr) {
     
  var index,
    randomIndex,
    temp,
    len = arr.length;

  for (index = 0; index < len; index++) {
     
    randomIndex = Math.floor(Math.random() * (len - index)) + index;

    temp = arr[index];
    arr[index] = arr[randomIndex];
    arr[randomIndex] = temp;
  }

  return arr;
}

// es6
function randomSort(array) {
     
  let length = array.length;

  if (!Array.isArray(array) || length <= 1) return;

  for (let index = 0; index < length - 1; index++) {
     
    let randomIndex = Math.floor(Math.random() * (length - index)) + index;

    [array[index], array[randomIndex]] = [array[randomIndex], array[index]];
  }

  return array;
}

详细资料可以参考: 《Fisher and Yates 的原始版》
《javascript 实现数组随机排序?》
《JavaScript 学习笔记:数组随机排序》

用split()来颠倒字符串顺序

注意这并非一种很健壮的逆转字符串的方法:

const str = 'asdfghjkl';
const strReverse = str.split('').reverse().join(''); // 'lkjhgfdsa'

交换字符串中的两个单词

var re = /(\w+)\s(\w+)/;
var str = "John Smith";
var newstr = str.replace(re, "$2, $1");
// Smith, John
console.log(newstr);

str.search(reg)和str.match与reg.exec区别,搜索下标

var str = 'hey JudE'
var re = /[A-Z]/g
var re2 = /[.]/g
console.log(str.search(re)) // returns 4, which is the index of the first capital letter "J"
console.log(str.search(re2)) // returns -1 cannot find '.' dot punctuation
console.log(str.indexOf('J')) // 4
console.log(re.exec(str)) // [ 'J', index: 4, input: 'hey JudE', groups: undefined ]
console.log(str.match(re)) // [ 'J', 'E' ]
console.log(str.match(/[A-Z]/)) // [ 'J', index: 4, input: 'hey JudE', groups: undefined ]

JavaScript 继承的几种实现方式?

我了解的 js 中实现继承的几种方式有:

(1)第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。

(2)第二种方式是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。

(3)第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。

(4)第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。

(5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。

(6)第六种方式是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。

详细资料可以参考: 《JavaScript 深入理解之继承》

Javascript 的作用域链?

作用域链的作用保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和函数。

作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。

当我们查找一个变量时,如果当前执行环境中没有找到,我们可以沿着作用域链向后查找。

作用域链的创建过程跟执行上下文的建立有关…
详细资料可以参考: 《JavaScript 深入理解之作用域链》

JS 作用域及作用域链

上面的通俗理解:
JS 作用域及作用域链
一、作用域
在 Javascript 中,作用域分为 全局作用域 和 函数作用域

全局作用域:代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域。
函数作用域: 在固定的代码片段才能被访问

JS面试相关问题整理_第2张图片
作用域有上下级关系,上下级关系的确定就看函数是在哪个作用域下创建的。如上,fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。

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

变量取值:到创建 这个变量 的函数的作用域中取值

二、作用域链
定义:
一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。

但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
JS面试相关问题整理_第3张图片

谈谈 This 对象的理解。

这个可以看下木易杨的。。。。。。。

this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断。

  • 1.第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。

  • 2.第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。

  • 3.第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。

  • 4.第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。

这四种方式,使用构造器调用模式的优先级最高,然后是 apply 、 call 和 bind 调用模式,然后是方法调用模式,然后
是函数调用模式。

JS面试相关问题整理_第4张图片
JS面试相关问题整理_第5张图片

《JavaScript 深入理解之 this 详解》

eval 是做什么的?

它的功能是把对应的字符串解析成 JS 代码并运行。

应该避免使用 eval,不安全,非常耗性能(2次,一次解析成 js 语句,一次执行)。

事件委托详解

JavaScript 事件委托详解
事件委托的优点

  1. 减少内存消耗
  2. 动态绑定事件

完整函数:

function eventDelegate (parentSelector, targetSelector, events, foo) {
     

  // 触发执行的函数
  function triFunction (e) {
     
    // 兼容性处理
    var event = e || window.event;

    // 获取到目标阶段指向的元素
    var target = event.target || event.srcElement;

    // 获取到代理事件的函数
    var currentTarget = event.currentTarget;

    // 处理 matches 的兼容性
    if (!Element.prototype.matches) {
     
      Element.prototype.matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function(s) {
     
          var matches = (this.document || this.ownerDocument).querySelectorAll(s),
            i = matches.length;
          while (--i >= 0 && matches.item(i) !== this) {
     }
          return i > -1;            
        };
    }

    // 遍历外层并且匹配
    while (target !== currentTarget) {
     
      // 判断是否匹配到我们所需要的元素上
      if (target.matches(targetSelector)) {
     
        var sTarget = target;
        // 执行绑定的函数,注意 this
        foo.call(sTarget, Array.prototype.slice.call(arguments))
      }

      target = target.parentNode;
    }
  }

  // 如果有多个事件的话需要全部一一绑定事件
  events.split('.').forEach(function (evt) {
     
    // 多个父层元素的话也需要一一绑定
    Array.prototype.slice.call(document.querySelectorAll(parentSelector)).forEach(function ($p) {
     
      $p.addEventListener(evt, triFunction);
    });
  });
}

e.target与e.currentTarget

点击B

<body>
    <div class="a">
        a
        <div class="b">b</div>
    </div>
    <script>
        function handler(e) {
     
            console.log(e.target)
            console.log(e.currentTarget)
        }
        let a = document.querySelector('.a')
        a.addEventListener('click', handler)
    </script>
</body>

在这里插入图片描述
currentTarget始终是监听事件者,即 直接调用addEventlistener那个节点
target是事件的真正发出者, 即 触发事件的节点,在click事件中就是被点击的节点
e.target与e.currentTarget

[“1”, “2”, “3”].map(parseInt) 答案是多少?

parseInt() 函数能解析一个字符串,并返回一个整数,需要两个参数 (val, radix),其中 radix 表示要解析的数字的基数。(该值介于 2 ~ 36 之间,并且字符串中的数字不能大于 radix 才能正确返回数字结果值)。

此处 map 传了 3 个参数 (element, index, array),默认第三个参数被忽略掉

let a = ['1', '2', '3'].map(parseInt)
console.log(a)
// [ 1, NaN, NaN ]

原文结论错误,正确的是:
最后的结论完全就是不知所云!!!正确的理解应该是如下!
[“1”, “2”, “3”].map(parseInt) 应该对应的是: [parseInt("1", 0), parseInt("2", 1), parseInt("3", 2)] parseInt(“3”, 2) 的第二个参数是界于 2-36 之间的,之所以返回 NaN 是因为 字符串 “3” 里面没有合法的二进制数,所以 NaN。

《为什么 [“1”, “2”, “3”].map(parseInt) 返回 [1,NaN,NaN]?》

什么是闭包,为什么要用它?

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

闭包有两个常用的用途。

闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,我们可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。

函数的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。

其实闭包的本质就是作用域链的一个特殊的应用,只要了解了作用域链的创建过程,就能够理解闭包的实现原理。

执行上下文创建过程
《JavaScript 深入理解之闭包》

“use strict”; 是什么意思

相关知识点:

use strict 是一种 ECMAscript5 添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行。

设立"严格模式"的目的,主要有以下几个:
消除 Javascript 语法的一些不合理、不严谨之处,减少一些怪异行为;
消除代码运行的一些不安全之处,保证代码运行的安全;
提高编译器效率,增加运行速度;
为未来新版本的 Javascript 做好铺垫。
区别:

  • 1.禁止使用 with 语句。
  • 2.禁止 this 关键字指向全局对象。
  • 3.对象不能有重名的属性。

回答:

use strict 指的是严格运行模式,在这种模式对 js 的使用添加了一些限制。比如说禁止 this 指向全局对象,还有禁止使用 with 语句等。设立严格模式的目的,主要是为了消除代码使用中的一些不安全的使用方式,也是为了消除 js 语法本身的一些不合理的地方,以此来减少一些运行时的怪异的行为。同时使用严格运行模式也能够提高编译的效率,从而提高代码的运行速度。
我认为严格模式代表了 js 一种更合理、更安全、更严谨的发展方向。
详细资料可以参考: 《Javascript 严格模式详解》

如何判断一个对象是否属于某个类?

第一种方式是使用 instanceof 运算符来判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。

第二种方式可以通过对象的 constructor 属性来判断,对象的 constructor 属性指向该对象的构造函数,但是这种方式不是很安全,因为 constructor 属性可以被改写。

第三种方式,如果需要判断的是某个内置的引用类型的话,可以使用 Object.prototype.toString() 方法来打印对象的
[[Class]] 属性来进行判断。

详细资料可以参考: 《js 判断一个对象是否属于某一类》

对于 JSON 的了解?

相关知识点:

JSON 是一种数据交换格式,基于文本,优于轻量,用于交换数据。

JSON 可以表示数字、布尔值、字符串、null、数组(值的有序序列),以及由这些值(或数组、对象)所组成的对象(字符串与
值的映射)。

JSON 使用 JavaScript 语法,但是 JSON 格式仅仅是一个文本。文本可以被任何编程语言读取及作为数据格式传递。

回答:

JSON 是一种基于文本的轻量级的数据交换格式。它可以被任何的编程语言读取和作为数据格式来传递。

在项目开发中,我们使用 JSON 作为前后端数据交换的方式。在前端我们通过将一个符合 JSON 格式的数据结构序列化为 JSON 字符串,然后将它传递到后端,后端通过 JSON 格式的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。

因为 JSON 的语法是基于 js 的,因此很容易将 JSON 和 js 中的对象弄混,但是我们应该注意的是 JSON 和 js 中的对象不是一回事,JSON 中对象格式更加严格,比如说在 JSON 中属性值不能为函数,不能出现 NaN 这样的属性值等,因此大多数的 js 对象是不符合 JSON 对象的格式的。

在 js 中提供了两个函数来实现 js 数据结构和 JSON 格式的转换处理,一个是 JSON.stringify 函数,通过传入一个符合 JSON 格式的数据结构,将其转换为一个 JSON 字符串。如果传入的数据结构不符合 JSON 格式,那么在序列化的时候会对这些值进行对应的特殊处理,使其符合规范。在前端向后端发送数据时,我们可以调用这个函数将数据对象转化为 JSON 格式的字符串。

另一个函数 JSON.parse() 函数,这个函数用来将 JSON 格式的字符串转换为一个 js 数据结构,如果传入的字符串不是标准的 JSON 格式的字符串的话,将会抛出错误。当我们从后端接收到 JSON 格式的字符串时,我们可以通过这个方法来将其解析为一个 js 数据结构,以此来进行数据的访问。

页面js 延迟加载的方式有哪些?

js 延迟加载,也就是等页面加载完成之后再加载 JavaScript 文件。 js 延迟加载有助于提高页面加载速度。

一般有以下几种方式:

  • defer 属性
  • async 属性
  • 动态创建 DOM 方式
  • 使用 setTimeout 延迟方法
  • 让 JS 最后加载

回答:

js 的加载、解析和执行会阻塞页面的渲染过程,因此我们希望 js 脚本能够尽可能的延迟加载,提高页面的渲染速度。

我了解到的几种方式是:

第一种方式是我们一般采用的是将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。

第二种方式是给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。

第三种方式是给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。

第四种方式是动态创建 DOM 标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。

Ajax 是什么? 如何创建一个 Ajax?

相关知识点:

2005 年 2 月,AJAX 这个词第一次正式提出,它是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。

具体来说,AJAX 包括以下几个步骤。

1.创建 XMLHttpRequest 对象,也就是创建一个异步调用对象
2.创建一个新的 HTTP 请求,并指定该 HTTP 请求的方法、URL 及验证信息
3.设置响应 HTTP 请求状态变化的函数
4.发送 HTTP 请求
5.获取异步调用返回的数据
6.使用 JavaScript 和 DOM 实现局部刷新

const SERVER_URL = "/server";

let xhr = new XMLHttpRequest();

// 创建 Http 请求
xhr.open("GET", SERVER_URL, true);

// 设置状态监听函数
xhr.onreadystatechange = function() {
     
  if (this.readyState !== 4) return;

  // 当请求成功时
  if (this.status === 200) {
     
    handle(this.response);
  } else {
     
    console.error(this.statusText);
  }
};

// 设置请求失败时的监听函数
xhr.onerror = function() {
     
  console.error(this.statusText);
};

// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");

// 发送 Http 请求
xhr.send(null);

// promise 封装实现:

function getJSON(url) {
     
  // 创建一个 promise 对象
  let promise = new Promise(function(resolve, reject) {
     
    let xhr = new XMLHttpRequest();

    // 新建一个 http 请求
    xhr.open("GET", url, true);

    // 设置状态的监听函数
    xhr.onreadystatechange = function() {
     
      if (this.readyState !== 4) return;

      // 当请求成功或失败时,改变 promise 的状态
      if (this.status === 200) {
     
        resolve(this.response);
      } else {
     
        reject(new Error(this.statusText));
      }
    };

    // 设置错误监听函数
    xhr.onerror = function() {
     
      reject(new Error(this.statusText));
    };

    // 设置响应的数据类型
    xhr.responseType = "json";

    // 设置请求头信息
    xhr.setRequestHeader("Accept", "application/json");

    // 发送 http 请求
    xhr.send(null);
  });

  return promise;
}

回答

我对 ajax 的理解是,它是一种异步通信的方法,通过直接由 js 脚本向服务器发起 http 通信,然后根据服务器返回的数据,更新网页的相应部分,而不用刷新整个页面的一种方法。

创建一个 ajax 有这样几个步骤

首先是创建一个 XMLHttpRequest 对象。

然后在这个对象上使用 open 方法创建一个 http 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。

在发起请求前,我们可以为这个对象添加一些信息和监听函数。比如说我们可以通过 setRequestHeader 方法来为请求添加头信息。我们还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange 事件,我们可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候我们可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候我们就可以通过 response 中的数据来对页面进行更新了。

当对象的属性和监听函数设置完成后,最后我们调用 sent 方法来向服务器发起请求,可以传入参数作为发送的数据体。

你可能感兴趣的:(面试,前端)