1. JavaScript有哪些数据类型,它们的区别?
JavaScript一共有八种数据类型,分别是undefined、null、Boolean、number、string、object、Symbol、BigInt
其中Symbol
和BigInt
是es6
中新增的数据类型
Symbol
代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。BigInt
是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。
这些数据可以分为原始数据类型和引用数据类型
- 栈:栈存放原始数据类型
undefined、null、boolean、number、string
- 堆:堆存放应用数据类型(对象、数组、函数)
2. 说说你对堆区和栈区的理解
两种类型的区别在于存储位置的不同:
- 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
- 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:
- 在数据结构中,栈中数据的存取方式为先进后出。
- 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。
在操作系统中,内存被分为栈区和堆区:
- 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
- 堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。
3. 数据类型检测的方式有哪些
(1) typeof
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof 'str'); // string
console.log(typeof []); // object
console.log(typeof function(){}); // function
console.log(typeof {}); // object
console.log(typeof undefined); // undefined
console.log(typeof null); // object
其中数组、对象、null都会被判断为object,其他判断都正确
(2) instanceofinstanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上
function A(){}
let a = new A()
a instanceof A //true
Function instanceof Object //true
Object instanceof Function //true
A instanceof Function //true
a instanceof Object //true
[] instanceof Array //true
true instanceof Boolean // false
2 instanceof Number // false
'str' instanceof String // false
instanceof
只能正确判断引用数据类型,而不能判断基本数据类型。instanceof
运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype
属性
(3) constructor
对象的constructor
属性用于返回创建该对象的函数
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
(4)Object.prototype.toString.call()Object.prototype.toString.call()
使用 Object 对象的原型方法 toString
来判断数据类型
var a = Object.prototype.toString;
console.log(a.call(2)); //[object Number]
console.log(a.call(true)); //[object Boolean]
console.log(a.call('str')); //[object String]
console.log(a.call([])); //[object Array]
console.log(a.call(function(){})); //[object Function]
console.log(a.call({})); //[object Object]
console.log(a.call(undefined)); //[object Undefined]
console.log(a.call(null)); //[object Null]
4. Object.toString和Object.prototype.toString之间有所不同
Object是一个构造函数,函数都是Function的实例对象,如果是直接Object.toString的话根据原型链的查找规则Object会沿着__proto__
往上查找到Function.prototype对象里面的toString方法
Object.toString === Function.prototype.toString //true
Object.toString === Object.prototype.toString //false
//hasOwnProperty 验证属性是对象自身的还是原型链上的
Object.hasOwnProperty('toString') //false
Object.prototype.hasOwnProperty('toString') //true
5. 判断数组的方式有哪些
1. 通过Object.prototype.toString.call()做判断
Object.prototype.toString.call(obj) === '[object Array]'
2. 通过原型链做判断
arr.__proto__ === Array.prototype //true
3. instanceof
[] instanceof Array //true
4. Array.isArray()
Array.isArrray([]); //true
5. 通过constructor判断
[].constructor === Array //true
6. null和undefined区别
首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化
undefined 在 JavaScript 中不是一个保留字
7. typeof null 的结果是什么,为什么?
//typeof null 的结果是Object
typeof null //object
在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:
000: object - 当前存储的数据指向一个对象。
1: int - 当前存储的数据是一个 31 位的有符号整数。
010: double - 当前存储的数据指向一个双精度的浮点数。
100: string - 当前存储的数据指向一个字符串。
110: boolean - 当前存储的数据是布尔值。
如果最低位是 1,则类型标签标志位的长度只有一位;如果最低位是 0,则类型标签标志位的长度占三位,为存储其他四种数据类型提供了额外两个 bit 的长度。
有两种特殊数据类型:
- undefined的值是 (-2)30(一个超出整数范围的数字);
- null 的值是机器码 NULL 指针(null 指针的值全是 0)
那也就是说null的类型标签也是000,和Object的类型标签一样,所以会被判定为Object
8. intanceof 操作符的实现原理及实现
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上
function myInstanceof(left, right) {
// 获取对象的原型
let proto = Object.getPrototypeOf(left)
// 获取构造函数的 prototype 对象
let prototype = right.prototype;
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
// 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
proto = Object.getPrototypeOf(proto);
}
}
9. 为什么0.1+0.2 ! == 0.3,如何让其相等
这个和数据存储的方式有关系,浮点数是以双精度64位存储的,这64位由三部分组成,第1位是符号位,中间11位是指数位,最后52位为尾数位,浮点数在计算时都会转成二进制 ,问题是浮点数以二进制表示是无穷的,按 0舍1入保留52位,这样就造成了精度丢失。
解决这个问题可以采用扩大倍数法,目的是将小数变成整数后做运算。有多少位小数就扩大到10的n次方,然后在将结果除以10的n次方。
10. 如何获取安全的 undefined 值?
因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。表达式 void _ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。因此可以用 void 0 来获得 undefined。
11. typeof NaN 的结果是什么?
NaN是(Not a Number)的缩写,它表示储存数据的数据类型。但是它是属于数值类型,数学运算没有成功,这是失败后返回的结果,在执行算术运算时,只要有NaN参与其结果就是NaN;NaN 是一个特殊值,它和自身不相等
typeof NaN; // "number"
12. isNaN 和 Number.isNaN 函数的区别?
window.isNaN
接收参数后,会将参数转换为数值,不能被转换为数值的值就会返回true
,会影响到NaN
的判断Number.isNaN
只有对于NaN
才返回true
,非NaN一律返回false
,这种方法对于 NaN
的判断更为准确
isNaN('字符串') // true
isNaN(NaN) // true
isNaN('1') // false
Number.isNaN(NaN) //true
Number.isNaN('字符串') //false
13. 其他值到字符串的转换规则?
Null 和 Undefined 类型 ,null 转换为 "null",undefined 转换为 "undefined",
Boolean 类型,true 转换为 "true",false 转换为 "false"。
Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式。
Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。
对普通对象来说,除非自行定义 toString() 方法,否则会调用 toString()(Object.prototype.toString())来返回内部属性[[Class]]
的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。
14. 其他值到数字值的转换规则?
Undefined
类型的值转换为 NaN
。Null
类型的值转换为 0
。Boolean
类型的值,true
转换为 1
,false
转换为 0
。
String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。
Symbol 类型的值不能转换为数字,会报错。
对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。
15. 其他值到布尔类型的值的转换规则?
以下这些是假值:
Boolean(undefined) //false
Boolean(null) //false
Boolean(false) //false
Boolean(0) //false
Boolean(-0) //false
Boolean(+0) //false
Boolean('') //false
Boolean(NaN) //false
假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。
16. || 和 && 操作符的返回值?
|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。
- 对于
||
来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。 &&
则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。
|| 和 && 返回它们其中一个操作数的值,而非条件判断的结果
let test = 0 || 2
let test2 = 1 && 2
console.log(test,test2) //2,2
17. Object.is() 与比较操作符 “=== ”、“==” 的区别?
- 双等号
==
判断时,两边的类型不一样就会隐式转换然后再进行比较 - 三等号
===
判断时,两边的类型不一样也不会隐式转换,直接返回false - 使用
Object.is
来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 是相等的
18. 什么是 JavaScript 中的包装类型?
在JavaScript中,基本数据类型是没有属性和方法的,但是为了便于操作基本数据类型的值,在调用基本类型的属性或者方法时,js会在后台隐式的把基本类型转为对象
JavaScript也可以使用Object函数显式地将基本类型转换为包装类型:
var a = 'abc'
Object(a) // String {"abc"}
也可以使用valueOf方法将包装类型倒转成基本类型:
var a = 'abc'
var b = Object(a)
var c = b.valueOf() // 'abc'
看看如下代码会打印出什么:
var a = new Boolean( false );
if (!a) {
console.log( "Oops" ); // never runs
}
答案是什么都不会打印,因为虽然包裹的基本类型是false,
但是false被包裹成包装类型后就成了对象,所以其非值为false,所以循环体中的内容不会运行。
19. JavaScript 中如何进行隐式类型转换?
js隐式转换类型主要发生在运算的时候,而这些运算符只能操作基本类型值,所以在进行这些运算前的第一步就是将两边的值用ToPrimitive转换成基本类型,再进行操作,toPrimitive
, 此方法接收两个参数,一个参数为需要转换的对象,另一个方法接收一个期望类型,string或number。默认是number
当期望值为number时会调用valueOf方法,如果返回的值不是原始值,则继续调用toString方法。
当期望值为string时会调用toString方法,如果返回的值不是原始值,则继续调用valueOf方法。
默认会先调用valueOf() 然后再toString()
[].valueOf() //返回数组自身
{}.valueOf() //返回对象自身
true.valueOf() //true
20. 为什么会有BigInt的提案?
JavaScript中Number.MAX_SAFE_INTEGER表示最大安全数字,即在这个数范围内不会出现精度丢失(⼩数除外)。但是⼀旦超过这个范围,js就会出现计算不准确的情况,这在⼤数计算的时候不得不依靠⼀些第三⽅库进⾏解决,因此官⽅提出了BigInt来解决此问题
21. + 操作符什么时候用于字符串的拼接?
如果在二元运算中的其中一方是字符串类型的数据,则就会"+"就会进行字符串得拼接操作。
如果其中一方的操作数是数组或者对象,则会使用toPrimitive
进行抽象转换,先转化为字符串类型,如果不行,则再转化为数字类型的数据。
对于除了加法运算的其他运算,都会先转化为数字类型。
22. object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别
// 扩展运算符:
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
/*=============================================*/
// Object.assign():
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
可以看到,两者都是浅拷贝。
- Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。
- 扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。
根据转换规则 字符串通过valueOf()转换也是字符串本身
23 .ES6新增了那些新特性
- const 和 let
- 解构赋值
- 模板字符串
- 函数的rest参数
- 箭头函数
- 扩展运算符
- 对象属性的简写
- Symbol
- Set、Map
- Promise
- async await
- Class
- es6模块化Module
- Proxy
24. let、const、var的区别
(1)块级作用域: 块作用域由 {}
包括, let
和const
具有块级作用域,var
不存在块级作用域。块级作用域解决了ES5中的两个问题:
- 内层变量可能覆盖外层变量
- 用来计数的循环变量泄露为全局变量
(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。
(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。
(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。
(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。
25. 为什么const、let定义的变量不能二次定义
每次使用const
或者let
去初始化一个变量的时候,会首先遍历当前的内存栈,看看有没有重名变量,有的话就返回错误
26. const对象的属性可以修改吗
-
回答1: 变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了
-
回答2: 当我们定义一个const对象的时候, 我们说的常量其实是指针,就是const对象对应的堆内存指向是不变的,但是堆内存中的数据本身的大小或者属性是可变的。而对于const定义的基础变量而言,这个值就相当于const对象的指针,是不可变
27. new操作符做了什么
- 先创建了一个新的对象
newObj
- 将新对象
newObj
与构造函数通过原型链进行连接 - 将构造函数的
this
绑定到新对象newObj
- 最后再返回这个新对象
function newFunc(Func,...args) {
// 1.创建一个新对象
let newObj = {}
// 2.将新对象和构造函数通过原型链连接
newObj.__proto__ = Func.prototype
// 3.将构造函数的this绑定到新对象上
const result = Func.apply(newObj,args)
// 4.根据返回值类型判断,如果是值类型返回newObj,如果是引用类型返回正常引用类型
return result instanceof Object ? result : newObj
}
28. 如果new一个箭头函数的会怎么样
箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数
29. 箭头函数与普通函数的区别
(1)箭头函数比普通函数更加简洁
如果没有参数,就直接写一个空括号即可
如果只有一个参数,可以省去参数的括号
如果有多个参数,用逗号分割
如果函数体的返回值只有一句,可以省略大括号
如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常见的就是调用一个函数:
let fn = () => void Fn();
(2)箭头函数没有自己的this
箭头函数不会创建自己的this,所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变
(3)箭头函数继承来的this指向永远不会改变
(4)call()、apply()、bind()等方法不能改变箭头函数中this的指向
var id = 'Global';
let fun1 = () => {
console.log(this.id)
};
fun1(); // 'Global'
fun1.call({id: 'Obj'}); // 'Global'
fun1.apply({id: 'Obj'}); // 'Global'
fun1.bind({id: 'Obj'})(); // 'Global'
(5)箭头函数不能作为构造函数使用
因为new的时候需要把函数中的this指向该对象,但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。
(6)箭头函数没有自己的arguments
(7)箭头函数没有prototype
(8)箭头函数不能用作Generator函数,不能使用yeild关键字
30. 谈谈你对this的理解?
this是js的一个关键字,随着函数使用场合不同,this的值会发生变化。 但是有一个总原则,那就是this指的是调用函数的那个对象。 this一般情况下:是全局对象Global。 作为方法调用,那么this就是指这个对象
31. 箭头函数的this指向
箭头函数它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。
32. 扩展运算符的作用及使用场景
(1)对象扩展运算符
对象的扩展运算符(...
)用于取出参数对象中的所有可枚举属性,拷贝到当前对象之中。
let bar = { a: 1, b: 2 };
let baz = { ...bar }; // { a: 1, b: 2 }
(2)数组扩展运算符
数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。
console.log(...[1, 2, 3])
// 1 2 3
console.log(...[1, [2, 3, 4], 5])
// 1 [2, 3, 4] 5
合并数组
如果想在数组内合并数组,可以这样:
const arr1 = ['two', 'three'];
const arr2 = ['one', ...arr1, 'four', 'five'];// ["one", "two", "three", "four", "five"]
将字符串转为真正的数组
[...'hello'] // [ "h", "e", "l", "l", "o" ]
使用Math函数获取数组中特定的值
const numbers = [9, 4, 7, 1];
Math.min(...numbers); // 1
Math.max(...numbers); // 9
33. 说一下对象与数组的解构赋值的理解
解构是 ES6 提供的一种新的提取数据的模式,这种模式能够从对象或数组里有针对性地拿到想要的数值。 1)数组的解构 在解构数组时,以元素的位置为匹配条件来提取想要的数据的:
数组的解构:
const [a, b, c] = [1, 2, 3]
对象的解构:
const stu = {
name: '张三',
age: 18,
}
const { name, age } = stu
34. 对 rest 参数的理解
ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中
function mutiple(...args) {
console.log(args)
}
mutiple(1, 2, 3, 4) // [1, 2, 3, 4]
跟arguments
的区别在于什么:
arguments是一个类似数组的对象,通常情况下我们需要使用Array.from先转成数组,rest参数不存在这个问题,因为他就是一个真正的数组
35. ES6中模板语法
es6的模板字符串简化了字符串拼接,代码的可读性也提高了,使用反引号``
来包括字符串,如果需要拼接上变量,那拼接的格式是使用${}包裹变量即可,在模板字符串中,空格、缩进、换行都会被保留
36. map和Object的区别
Object是最常用的一种应用类型数据,存储方式是键值对
Map是es6新增的,是键值对的集合,采用Hash结构存储
共同点: 键值对的动态集合,支持增加和删除键值对
不同点:
1.键的类型:
- Object键的类型必须是String或者Symbol,如果非String类型会进行数据类型转换,
- Map键的类型可以是任何类型,包括对象,数组,函数等,不会进行类型转换,特殊情况 键值是NaN时会被后续的覆盖
2.键的顺序:
- Object的key是无序的不会按照添加的顺序返回,对于大于0的整数,会按照大小进行排序,对于小数和负数会当做字符串处理
- Map的key是有序的,按照插入的顺序进行返回
3.键值对的size
- Object 的键值对个数只能手动计算
- Map 的键值对个数可以轻易地通过size属性获取
4.迭代器for of
- Object本身没有iterator,默认情况下不能使用for of遍历
- Map结构的
keys() values() entries()
方法的返回值都具有iterator特性
5.JSON序列化
- Object类型可以通过
JSON.stringify
进行序列化 - Map结构不能直接进行JSON序列化
// object
let obj = {}
obj.name = '张三'
console.log('obj :>> ', obj);
console.log('JSON.stringify(obj) :>> ', JSON.stringify(obj)); // "{"name":"张三"}"
//map
let map = new Map()
map.set('name','张三')
console.log('Map :>> ', map);
console.log('JSON.stringify(map) :>> ', JSON.stringify(map)); // {}
37. map和weakMap的区别
- Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
- WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。但是 WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。
38. JavaScript有哪些内置对象
String、Number、Math、Date、Object、Array、Function、Boolean、Symbol、Error、Promise、Map、Set、WeakMap、WeakSet
39. 对JSON的理解
JSON 是一种基于文本的轻量级的数据交换格式。它可以被任何的编程语言读取和作为数据格式来传递。
在项目开发中,使用 JSON 作为前后端数据交换的方式。在前端通过将一个符合 JSON 格式的数据结构序列化为
JSON 字符串,然后将它传递到后端,后端通过 JSON 格式的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。
因为 JSON 的语法是基于 js 的,因此很容易将 JSON 和 js 中的对象弄混,但是应该注意的是 JSON 和 js 中的对象不是一回事,JSON 中对象格式更加严格,比如说在 JSON 中属性值不能为函数,不能出现 NaN 这样的属性值等,因此大多数的 js 对象是不符合 JSON 对象的格式的。
在 js 中提供了两个函数来实现 js 数据结构和 JSON 格式的转换处理,JSON.stringify()
和JSON.parse()
40. JavaScript脚本延迟加载的方式有哪些?
延迟加载就是等页面加载完毕后再执行js脚本, js 延迟加载有助于提高页面加载速度。
一般有以下几种方式:
- defer 属性: 给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件
- async 属性: 给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞
- 动态创建 DOM 方式: 动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本
- 让 JS 最后加载: 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行,跟添加defer同效果
41. JavaScript 类数组对象
一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法,常见的类数组对象有 arguments
和 DOM 方法的返回结果例如:document.querySelectorAll();document.getElementsByClassName()
常见的类数组转换为数组的方法有这样几种:
(1)通过 Array.from 方法来实现转换
Array.from(arrayLike);
(2)通过 apply 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([], arrayLike);
(3)使用es6拓展运算符
let newArr = [...arrayLike]
42. 数组有哪些原生方法?
push()
向数组的末尾添加一个或更多元素,并返回新的长度。unshift()
向数组的开头添加一个或更多元素,并返回新的长度。shift()
删除并返回数组的第一个元素。pop()
删除数组的最后一个元素并返回删除的元素。reverse()
反转数组的元素顺序。join()
把数组的所有元素放入一个字符串。includes()
判断一个数组是否包含一个指定的值。indexOf()
搜索数组中的元素,并返回它所在的位置。没找到返回-1
lastIndexOf()
搜索数组中的元素,并返回它最后出现的位置。splice()
从数组中添加或删除元素。reduce()
将数组元素计算为一个值(从左到右)。flat()
会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回,传入参数Infinity
表示全部拉平
迭代方法 every()、some()、filter()、map()、find()、findIndex()
和 forEach()
方法
43. Unicode、UTF-8、UTF-32的区别?
unicode是编码字符集,而UTF-8、UTF-16、UTF-32是字符集编码
Unicode
Unicode 分配给字符一个唯一的数字编号,这个编号范围从 0x000000 到 0x10FFFF (十六进制),有 110 多万,每个字符都有一个唯一的 Unicode 编号,这个编号一般写成 16 进制,在前面加上 U+。
Unicode 就相当于一张表,建立了字符与编号之间的联系
UTF-32
UTF-32 就是字符所对应编号的整数二进制形式,每个字符占四个字节,这个是直接进行转换的。该编码方式占用的储存空间较多,所以使用较少。
UTF-8
UTF-8是使用最广泛的Unicode编码方式,它是一种可变长的编码方式,可以是1—4个字节不等,它可以完全兼容ASCII码的128个字符
总结的不是很好详细请看b站讲解
44. 为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?
arguments
是一个对象,它的属性是从 0 开始依次递增的数字,还有callee和length等属性,与数组相似;但是它却没有数组常见的方法属性,如forEach, reduce等,所以叫它们类数组。
要遍历类数组:
(1)使用Array.from方法将类数组转化成数组:
function foo(){
const arrArgs = Array.from(arguments)
arrArgs.forEach(a => console.log(a))
}
(2)使用展开运算符将类数组转化成数组:
function foo(){
const arrArgs = [...arguments]
arrArgs.forEach(a => console.log(a))
}
45. 什么是 DOM 和 BOM?
- DOM 全称是 Document Object Model 也就是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。
- BOM 是 Browser Object Model,浏览器对象模型。刚才说过 DOM 是为了操作文档出现的接口,那 BOM 顾名思义其实就是为了控制浏览器的行为而出现的接口,BOM的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象
46. encodeURI、encodeURIComponent 的区别
区别:encodeURI
是对url中的查询字符串部分进行转义encodeURIComponent
对整个url进行转义,包括空格、英文冒号、斜杠等
至于decodeURI
和decodeURIComponent
,只要知道decodeURI
和encodeURI
是互逆操作,decodeURIComponent
和encodeURIComponent
是互逆操作就可以了
47. 怎么使用 js 动态生成海报?
将目标 DOM 节点绘制到 canvas 画布,然后利用 canvas 相关的 API 以图片形式导出。
可简单标记为绘制阶段和导出阶段两个步骤:
绘制阶段:选择希望绘制的 DOM 节点,根据 DOM 的 nodeType
属性调用 canvas
对象的对应 API,将目标 DOM 节点绘制到 canvas
画布(例如对于 img
标签的绘制使用 drawImage
方法)。
导出阶段:通过 canvas
的 toDataURL
或 getImageData
等对外接口,最终实现画布内容的导出。
48. 对AJAX的理解,实现一个AJAX请求
AJAX是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页
创建AJAX请求的步骤:
- 创建一个XMLHttpRequest对象
- 在这个对象上使用 open 方法创建一个 HTTP 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息
- 在发起请求前,可以为这个对象添加一些信息和监听函数。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果
- 当对象的属性和监听函数设置完成后,最后调用 sent 方法来向服务器发起请求,可以传入参数作为发送的数据体
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", 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);
49. JavaScript为什么要进行变量提升,它导致了什么问题
通俗来说,变量提升是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的行为。变量被提升后,会给变量设置默认值为 undefined
优点:
- 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间;
- 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行。
缺点:
变量被覆盖,在函数中,原本是要打印出外层的变量,但是因为变量提升的问题,内层定义的变量被提到函数内部的最顶部,相当于覆盖了外层的变量,所以打印结果为undefined。
50. 什么是尾调用,使用尾调用有什么好处?
尾调用指的是函数的最后一步调用另一个函数。代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步,所以这时可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
51. ES6模块与CommonJS模块有什么异同?
CommonJS是对模块的浅拷⻉,ES6 Module是对模块的引⽤,即ES6 Module只存只读,不能改变其值,也就是指针指向不能变,类似const;
CommonJS模块是运行时加载,ES6模块是编译时输出接口
因为CommonJS加载的是一个对象(module.exports),该对象只有脚本执行完才会生成,而ES6模块不是对象,它的对外接口是一种静态定义,在代码静态解析阶段就会生成
52.严格模式
在js脚本第一行加上"use strict"就是开启了严格模式
严格模式主要有以下限制。
变量必须声明后再使用
函数的参数不能有同名属性,否则报错
不能使用with
语句
不能对只读属性赋值,否则报错
不能使用前缀 0 表示八进制数,否则报错
不能删除不可删除的属性,否则报错
不能删除变量delete prop
,会报错,只能删除属性delete global[prop]
eval
不会在它的外层作用域引入变量eval
和arguments
不能被重新赋值arguments
不会自动反映函数参数的变化
不能使用arguments.callee
不能使用arguments.caller
禁止this指向全局对象
不能使用fn.caller
和fn.arguments
获取函数调用的堆栈
增加了保留字(比如protected
、static
和interface
)
53. 常见的DOM操作有哪些
(1)DOM节点获取
document.getElementById('id') // 按照 id 查询
document.getElementsByTagName('span') // 按照标签名查询
document.getElementsByClassName('class') // 按照类名查询
document.querySelectorAll('.class') // 按照 css 选择器查询
(2)DOM 节点的创建
document.createElement('span') // 创建新节点
parent.appendChild(targetSpan) // 把新创建的元素塞进父节点里去
container.insertBefore(content, title) // 交换两个元素,把 content 置于 title 前面
(3)DOM 节点的删除
container.removeChild(targetNode) // 删除目标元素
54. 如何判断一个对象是否属于某个类?
- 第一种方式,使用 instanceof 运算符来判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
- 第二种方式,通过对象的 constructor 属性来判断,对象的 constructor 属性指向该对象的构造函数,但是这种方式不是很安全,因为 constructor 属性可以被改写。
- 第三种方式,如果需要判断的是某个内置的引用类型的话,可以使用 Object.prototype.toString() 方法来打印对象的
[[Class]]
属性来进行判断。
55. for…in和for…of的区别
for…of 是ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,和ES3中的for…in的区别如下
for…of
遍历获取的是对象的键值,for…in
获取的是对象的键名;for… in
会遍历对象的整个原型链,性能非常差不推荐使用,而for … of
只遍历当前对象不会遍历原型链;- 对于数组的遍历,
for…in
会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of
只返回数组的下标对应的属性值;
56. 如何使用for…of遍历对象
for…of
是作为ES6新增的遍历方式,允许遍历一个含有iterator
接口的数据结构(数组、对象等)并且返回各项的值,普通的对象用for..of
遍历是会报错的。
如果不是类数组对象,就给对象添加一个[Symbol.iterator]
属性,并指向一个迭代器即可。
57. ajax、axios、fetch的区别
(1)AJAX:
它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。
(2)Fetch:
fetch号称是AJAX的替代品,是在ES6出现的,使用了ES6中的promise对象。Fetch是基于promise设计的。Fetch的代码结构比起ajax简单多。fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。
(3)Axios Axios 是一种基于Promise封装的HTTP客户端 浏览器环境和node环境都可以使用
58. 数组的遍历方法有哪些
方法 | 是否改变原数组 | 特点 |
---|---|---|
forEach() | 否 | 数组方法,不改变原数组,没有返回值 |
map() | 否 | 数组方法,不改变原数组,有返回值,可链式调用 |
filter() | 否 | 数组方法,过滤数组,返回包含符合条件的元素的数组,可链式调用 |
for...of | 否 | for...of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象,将异步循环变成同步循环 |
every() 和 some() | 否 | 数组方法,some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false. |
find() 和 findIndex() | 否 | 数组方法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值 |
reduce() 和 reduceRight() | 否 | 数组方法,reduce()对数组正序操作;reduceRight()对数组逆序操作 |
59. 对原型、原型链的理解
js中使用构造函数来新建一个对象,每一个构造函数内部都有一个prototype
属性,prototype
也是一个对象这就是平时说的原型对象,这个对象主要作用就是,让多个对象实例共享prototype
原型对象上的属性和方法,当使用构造函数新建一个对象后,这个对象的内部会有一个指针__proto__
,用来指向构造函数的prototype
原型对象
原型链:就是实例对象和原型对象之间的链接,每一个对象都有原型,原型本身又是对象,原型又有原型,以此类推形成一个链式结构.称为原型链
60. 原型修改、重写
function Person(name) {
this.name = name
}
// 修改原型
Person.prototype.getName = function() {}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
// 重写原型
Person.prototype = {
getName: function() {}
}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false
可以看到修改原型的时候p的构造函数不是指向Person了,因为直接给Person的原型对象直接用对象赋值时,它的构造函数指向的了根构造函数Object,所以这时候p.constructor === Object
,而不是p.constructor === Person
。要想成立,就要用constructor指回来:
Person.prototype = {
getName: function() {}
}
var p = new Person('hello')
p.constructor = Person
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
61. 原型链的终点是什么?如何打印出原型链的终点?
由于Object是构造函数,原型链终点是Object.prototype.__proto__
,而Object.prototype.__proto__=== null // true
,所以,原型链的终点是null。原型链上的所有原型都是对象,所有的对象最终都是由Object构造的,而Object.prototype
的下一级是Object.prototype.__proto__
。
62. 如何获得对象非原型链上的属性?
使用后hasOwnProperty()
方法来判断属性是否属于原型链的属性:
function Fn(){}
Fn.prototype.name = '张三'
let fnObj = new Fn()
fnObj.myname = '李四'
console.log('myname', fnObj.hasOwnProperty('myname')); //true
console.log('name', fnObj.hasOwnProperty('name')); //false
63. 对闭包的理解
闭包指的是一个有权访问另一个函数作用域中变量的函数,简单理解就是函数中还有一个函数内部函数能够获取到外部函数的变量,形参等
闭包有两个常用的用途;
- 闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
- 闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
64. 作用域链
作用域链: 在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到window对象就被终止,这一层层的关系就是作用域链
65. 执行上下文
简单来说执行上下文就是指:
在执行一点JS代码之前,需要先解析代码。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。这一步执行完了,才开始正式的执行程序。
66. 执行上下文栈
JavaScript引擎使用执行上下文栈来管理执行上下文,当JavaScript执行代码时,首先遇到全局代码,会创建一个全局执行上下文并且压入执行栈中,每当遇到一个函数调用,就会为该函数创建一个新的执行上下文并压入栈顶,引擎会执行位于执行上下文栈顶的函数,当函数执行完成之后,执行上下文从栈中弹出,继续执行下一个上下文。当所有的代码都执行完毕之后,从栈中弹出全局执行上下文
原型对象有什么做用呢
用来存放实例对象的公有属性和公有方法。在实例化的时候,就会重复创建一次相同的属性和方法,属于代码的冗余。所以把共用属性和方法放在原型对象里共享
标签:面试题,log,对象,Object,js,数组,prototype,函数 From: https://www.cnblogs.com/Lucky-daisy/p/16825850.html