JS面试题
1.变量类型和计算
题目:
- typeof能判断哪些类型
- 何时使用=何时使用
- 值类型和引用类型的区别
- 手写深拷贝
知识点:
-
变量类型
- 值类型vs引用类型
//值类型
let a = 100;
let b = a;
a = 200;
console.log(b);//100
//常见的值类型
let a;//undefined
const s = 'abc'
const n = 100;
const b = true;
const s = Symbol('s');
// 引用类型
let a = {age:20};
let b = a;
b.age = 21;
console.log(a.age)//21
// 常见的引用类型
const obj = {x:100};
const arr = ['a','b','c'];
const n = null//特殊引用类型,指针指向为空地址
//特殊引用类型,但不用于存储数据,所以没有"拷贝,复制函数"这一说
function fn(){}
接上文……
-
typeof 运算符
- 识别所有值类型(太简单不演示了,注意特别的Symbol)
const s = Symbol('s'); typeof s//'symbol'
- 识别函数
- 判断是否是引用类型(不可再细分)
-
深拷贝
var obj1 = {
age:18,
arr:[0,1,2],
person:{
name:'yang',
sex:'nan'
}
};
var obj2= {};
function deepClone(obj1,obj2){
if (typeof obj1!=='object' || obj1 == null){
return obj1;
}else{
for(var k in obj1){
if (obj1.hasOwnProperty(k)){
if (typeof obj1[k] == 'object'){
//obj2[k] 赋值为数组或者对象,用于在下一轮循环中接收原对象中的数组或者对象
obj2[k] = obj1[k].constructor==Array?[]:{};
// obj2[k] = Object.prototype.toString.call(obj1[k]) == "Array" ? [] : {};
deepClone(obj1[k],obj2[k]);
} else{
obj2[k] = obj1[k];
}
}
}
}
}
deepClone(obj1,obj2);
console.log(obj2)
var obj1 = {//原始对象
age: 18,
arr: [1, 2, 3],
};
var obj2 = {};
function copy(obj1, obj2) {
for (var k in obj1) {
if (typeof obj1[k] == 'object') {
//obj2[k] 赋值为数组或者对象,用于在下一轮循环中接收原对象中的数组或者对象
obj2[k] = (obj1[k].constructor == Array) ? [] : {};
// obj2[k] = Object.prototype.toString.call(obj1[k]) == "Array" ? [] : {};
copy(obj1[k], obj2[k])
} else {
obj2[k] = obj1[k];
}
}
return obj2;
}
var obj2 = copy(obj1, obj2);
console.log(obj2);
obj1.age = 199;
obj1.arr[0] = 'zhangsan';
console.log(obj1, obj2)
//注意:深拷贝同时复制了基本类型和引用类型的属性,所以任何一个对象的引用类型属性或者基本类型属性改变都不会相互影响
//利用JSON深拷贝,原理还是递归
//var obj2 = JSON.parse(JSON.stringify(obj1));
/**
* 深拷贝
*/
const obj1 = {
age: 20,
name: 'xxx',
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用!!!
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
//浅拷贝对象
var obj1 = {
age: 18,
arr: [1, 2, 3]
};
var obj2 = {};
function copy(obj1) {
for (var k in obj1) {
obj2[k] = obj1[k];
}
return obj2;
}
var obj2 = copy(obj1);
console.log(obj2 == obj1);
obj1.age = 33;
obj1.arr[0] = 'ooo';
console.log(obj1, obj2)
//注意:浅拷贝只是复制了基本类型,所以其中一个对象的基本类型的属性改变是不会影响到另一个对象的,
// 但是两个对象的引用类型属性值发生改变还是会互相影响的
// 利用assign()浅拷贝
// var obj2 = {};
//Object.assign(目标对象, 原始对象)
// Object.assign(obj2, obj1);
// obj1.arr[0] = 'ooo';
// console.log(obj1, obj2)
-
特殊的值
undefined
和null
null
指空值(曾经赋过值,但是目前没有值),特殊关键字,不是标识符,不能当做变量使用和赋值undefined
指没有值(从未赋过值),是一个标识符,可以当做变量使用
-
特殊的数字
NaN
(不是数字的数字)- 理解为"无效数值","失败数值"或者"坏数值",指出数字类型中的错误情况即:"数学运算没有成功,这是失败后返回的结果"
NaN
是一个特殊值,它和自身不相等,但是NaN!=NaN//true
var a = 2/"foo"; a == NaN //false a === NaN //false window.isNaN(a) //true,有bug不可取 Number.isNaN(a) //true ES6工具函数,准确
-
假值(假值得布尔强制类型转换结果为false,但是假值得封装对象是真值)
undefined
null
false
+0,-0和NaN
""
-
真值:假值列表以外的值都是真值
-
变量计算-类型转换
-
字符串拼接
- 当需要将非数字当做数字来使用,比如数学运算,遵循以下规则:
true
转换为1false
转换为0undefined
转换为NaN
null
转换为0
const a = 100 + 10 //110 const b = 100 + '10' //'10010' const c = true + '10' //'true10' const d = undefined + 100 //NaN const e = 100 + null //100
- 当需要将非数字当做数字来使用,比如数学运算,遵循以下规则:
-
==
和===
运算-
==
是宽松相等:允许在相等比较中进行强制类型转换,而===
不允许 -
==
两边的值转换规则(两边的值分别转换成什么类型的规则)-
数字和字符串:(简单说:字符串转换成数字)
- 如果
Type(x)
是数字,Type(y)
是字符串,则返回x==ToNumber(y)
的结果 - 如果
Type(x)
是字符串,Type(y)
是数字,则返回ToNumber(x)==y
的结果
- 如果
-
其他类型和布尔类型:(简单说:布尔类型转成数字类型)
- 如果
Type(x)
是布尔类型,则返回ToNumber(x)==y
的结果 - 如果
Type(y)
是布尔类型,则返回x==ToNumber(y)
的结果
- 如果
-
null和undefined
- 如果
x
为null
,y为undefined
,则结果为true
- 如果
x
为undefined
,y
为null
,则结果为true
在
==
中null
和undefined
相等(与其自身也相等),除此之外其他值都不和它们相等
也就是说在==
中null
和undefined
是一回事,可以相互隐式强制类型转换。 - 如果
-
100=='100' //true 0 == '' //true 0 == false //true false == '' //true null == undefined //true NaN==NaN //false +0==-0 //true var a = '3.14'; var b = a - 0; b//3.14
-
-
if语句和逻辑运算
-
||
(或)和&&
(与)或许叫["选择运算符"]
更准确,因为它返回的是两个操作数中的一个- 比较规则:
- 先进行条件判断第一个值是true还是false,如果第一个值不是布尔值,那么就先将它进行强制类型转换,然后执行条件判断。
- 对于
||
来说,如果条件判断结果是true就返回第一个值,如果是false就返回第二个值 &&
正好相反,如果条件判断结果是true就返回第二个值,如果是false就返回第一个值
- 比较规则:
引述ES5规范11.11节:
&&
和||
运算符的返回值不一定是布尔类型,而是两个操作数其中一个的值:
见书《你不知道的JS》中册74页 -
2.原型和原型链
题目:
-
如何判断一个变量是不是数组?
var arr = [1,2,3]; function isArray(arr){ //instance of 回答的问题是:在arr整条【prototype】链中是否有Array.prototype指向的对象? // return arr instanceof Array?'数组':'' return arr.constructor == Array?'数组':'' }; console.log(isArray(arr))
-
手写一个简易的jQuery,考虑插件和扩展性
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> </style> </head> <p>一段文字 1</p> <p>一段文字 2</p> <p>一段文字 3</p> <body> <script> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> </style> </head> <p>一段文字 1</p> <p>一段文字 2</p> <p>一段文字 3</p> <body> <script> class jQuery { constructor(selector) { const result = document.querySelectorAll(selector); const length = result.length; for (let i = 0; i < length; i++) { this[i] = result[i] } this.length = length; this.selector = selector } get(index) { return this[index] } each(fn) { for (let i = 0; i < this.length; i++) { let elem = this[i]; fn(elem) } } on(type, fn) { return this.each(elem => { elem.addEventListener(type, fn, false) }) } // 扩展很多 DOM API } // 插件 jQuery.prototype.dialog = function (info) { alert(info) } // “造轮子” class myJQuery extends jQuery { constructor(selector) { super(selector) } // 扩展自己的方法 addClass(className) { } style(data) { } } const $p = new jQuery('p'); console.log($p.get(1)); $p.each((elem) => console.log(elem.nodeName)); $p.on('click', (elem) => alert(elem.target.innerHTML))
-
class的原型本质,怎么理解
- 实例的隐式原型
__proto__
=== (指向)对应的类的显示原型(prototype)
- 子类的隐式原型
__proto__
指向父类的显示原型prototype
- 实例的隐式原型
知识点:
-
class和继承
-
class
-
constructor
-
属性
-
方法
// 类 class Student { constructor(name, number) { this.name = name this.number = number // this.gender = 'male' } sayHi() { console.log( `姓名 ${this.name} ,学号 ${this.number}` ) // console.log( // '姓名 ' + this.name + ' ,学号 ' + this.number // ) } // study() { // } } // 通过类 new 对象/实例 const xialuo = new Student('夏洛', 100) console.log(xialuo.name) console.log(xialuo.number) xialuo.sayHi() const madongmei = new Student('马冬梅', 101) console.log(madongmei.name) console.log(madongmei.number) madongmei.sayHi() console.log(typeof Student); //function
-
-
继承
-
extends
-
super
-
扩展或重写方法
// 父类 class People { constructor(name) { this.name = name } eat() { console.log(`${this.name} eat something`) } } // 子类 class Student extends People { constructor(name, number) { super(name) this.number = number } sayHi() { console.log(`姓名 ${this.name} 学号 ${this.number}`) } } // 子类 class Teacher extends People { constructor(name, major) { super(name) this.major = major } teach() { console.log(`${this.name} 教授 ${this.major}`) } } // 实例 const xialuo = new Student('夏洛', 100) console.log(xialuo.name) console.log(xialuo.number) xialuo.sayHi() xialuo.eat() // 实例 const wanglaoshi = new Teacher('王老师', '语文') console.log(wanglaoshi.name) console.log(wanglaoshi.major) wanglaoshi.teach() wanglaoshi.eat()
-
-