前端面试 -- JS基础知识
JS基础知识
1-变量类型和计算
1-1 题目
- JS中使用typeof能得到哪些类型
- 何时使用=== ,何时使用 ==
- JS中有哪些内置函数
- JS变量按照存储方式区分为哪些类型,并描述其特点
- 如何理解JSON
1-2 知识点
- 变量类型
- 变量计算 - 强制类型转换
1-2-1 变量类型
- 值类型 vs 引用类型
- 值类型
特点:不会随赋值而改变
// 值类型 let a = 100; let b = a; a = 200; console.log('a = ' + a); // a = 200 console.log('b = ' + b); // b = 100
- 引用类型:对象、数组、函数
特点:会随着赋值而改变,赋值时拷贝的是指向具体内容的指针;可以无限扩展属性
// 引用类型:对象 let a = {age: 20}; let b = a; console.log(a); // {age: 20} console.log(b); // {age: 20} b.age = 21; console.log(a); // {age: 21} console.log(b); // {age: 21} // 引用类型:数组 let arr = [1, 2, 3]; arr.age = 21; console.log(arr); // [1, 2, 3, age: 21] let arr2 = arr; console.log(arr2); // [1, 2, 3, age: 21] arr2.age = 22; console.log(arr); // [1, 2, 3, age: 22] // 引用类型:函数 function fun(){} fun.age = 21; let fun2 = fun; console.log(fun.age); // 21 console.log(fun2.age); // 21 fun2.age = 22; console.log(fun.age); // 22 console.log(fun2.age); // 22
- 值类型
- typeof 运算符详解
- 1、typeof只能区分值类型的详细类型–number,string,boolean,undefined
- 2、typeof不能区分引用类型的详细类型–typeof可以区分function,但是会把数组、对象和null均视为object
1-2-2 变量计算 - 强制类型转换
- 字符串拼接
- ==运算符
- if语句
在if语句中会被转换成false的情况,共有6种,分别是:0, NaN, ‘’/“”, null, undefined, false - 逻辑运算
// 2-2-2 变量计算 - 强制类型转换 // 字符串拼接 let a = 100 + 10; let b = 100 + '10'; console.log(a); // 110 console.log(b); // 10010 // ==运算符 console.log(100 == '100'); // true console.log(0 == ''); // true console.log(null == undefined); // true console.log(100 === '100'); // false console.log(0 === ''); // false console.log(null === undefined); // false // if语句 a = true; if(a) { console.log(a); // true } b = 100; if(b) { console.log(b); // 100 } let c = ''; if(c) { console.log(c); // 不执行 } // 逻辑运算 console.log(10 && 0); // 0 console.log('' || 'abc'); // 'abc' console.log(!window.abc); //true // 判断一个变量会被当做 true 还是 false a = 100; console.log(!!a); // true b = ''; console.log(!!b); // false
1-3 题目解答
- JS中使用typeof能得到哪些类型
// 问题:JS中使用typeof能得到哪些类型 console.log(typeof undefined); // undefined console.log(typeof 'undefined'); // string console.log(typeof 123); // number console.log(typeof true); // boolean console.log(typeof {}); // object console.log(typeof []); // object console.log(typeof null); // object console.log(typeof console.log); // function
- 何时使用=== ,何时使用 ==
// 问题:何时使用=== ,何时使用 == if(obj.a == null) { // 这里相当于 obj.a === null || obj.a === undefined, 简写形式 // 这是jQuery源码中推荐的写法,除此之外,都应该使用=== }
- JS中有哪些内置函数 – 数据封装对象 – 9个
- Boolean
- Number
- String
- Date
- Array
- Function
- Object
- RegExp
- Error
注意:
JS中有12个内置对象,除了上面的9个内置函数之外,还有Math, Global(在浏览器中的表现为window), JSON
- JS变量按照存储方式区分为哪些类型,并描述其特点
- 值类型
特点:不会随赋值而改变 - 引用类型:对象、数组、函数
特点:会随着赋值而改变,赋值时拷贝的是指向具体内容的指针;可以无限扩展属性
- 值类型
- 如何理解JSON
JSON 只不过是一个JS对象而已,同时也是一种文件格式。// 问题:如何理解JSON let str1 = JSON.stringify({a: 10, b: 20}); // json --> string let str2 = JSON.parse('{"a": 10, "b": 20}'); // string --> json console.log(str1); // {"a": 10, "b": 20} console.log(str2); // {a: 10, b: 20}
2-原型和原型链
2-1 题目
- 如何准确判断一个变量是数组类型
- 写一个原型链继承的例子
- 描述new一个对象的过程
- zepto(或其他框架)源码中如何使用原型链
2-2 知识点
- 构造函数
//构造函数 function Foo(name, age) { this.name = name; this.age = age; this.class = 'class-1'; // return this; // 默认有这一行 } let f = new Foo('zhangsan', 20); let f2 = new Foo('lisi', 22); //创建多个对象 console.log(f); // Foo {name: 'zhangsan', age: 20, class: 'class-1'} console.log(f2); // Foo {name: 'lisi', age: 22, class: 'class-1'}
- 构造函数 - 扩展
- var a = {} 其实是 var a = new Object() 的语法糖
- var a = [] 其实是 var a = new Array() 的语法糖
- function Foo(){…} 其实是 function Foo = new Function(){…} 的语法糖
- 使用 instanceof 判断一个函数是否是一个变量的构造函数
- 原型规则和示例 – 5条
- 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了"null"之外)
- 所有的引用类型(数组、对象、函数),都有一个__proto__属性(隐式原型),属性值是一个普通的对象
- 所有的函数,都有一个prototype属性(显式原型),属性值是一个普通的对象
- 所有的引用类型(数组、对象、函数),__proto__属性值指向它的构造函数的"prototype"属性值
// 原型规则和示例1-4 let obj = {}; obj.a = 100; let arr = []; arr.a = 100; function fn() {} fn.a = 100; console.log(obj.__proto__); // Object console.log(arr.__proto__); // Array console.log(fn.__proto__); // f () {[native code]} console.log(fn.prototype); // Object console.log(obj.__proto__ === Object.prototype); // true
- 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找。
注意:// 原型规则和示例5 // 构造函数 function Foo(name, age) { this.name = name; } Foo.prototype.alertName = function() { alert(this.name); } // 创建实例 let f = new Foo('zhangsan'); f.printName = function() { console.log(this.name); } // 测试 f.printName(); // zhangsan f.alertName();
- 原型规则和示例5中所有的this均指向f
- 循环对象自身的属性
// 循环对象自身的属性 for(let item in f) { // 高级浏览器已经在of in 中屏蔽了来自原型的属性 // 但是这里建议大家还是加上这个判断,保证程序的健壮性 if(f.hasOwnProperty(item)) { console.log(item); // name printName } }
- 原型链
// 原型链 // 构造函数 function Foo(name, age) { this.name = name; } Foo.prototype.alertName = function() { alert(this.name); } // 创建实例 let f = new Foo('zhangsan'); f.printName = function() { console.log(this.name); } // 测试 f.printName(); // zhangsan f.alertName(); f.toString(); // 要去 f.__proto__.__proto__中查找
- instanceof
- 用于判断引用类型属于哪个构造函数的方法
f instanceof Foo / Object 的判断逻辑是:
f的__proto__一层一层往上,能否对应到Foo.prototype / Object.prototype
- 用于判断引用类型属于哪个构造函数的方法
2-3 题目解答
-
如何准确判断一个变量是数组类型
// 问题:如何准确判断一个变量是数组类型 let arr = []; console.log(arr instanceof Array); // true console.log(typeof arr); // object
-
写一个原型链继承的例子
// 问题:写一个原型链继承的例子 // 动物 function Animal() { this.eat = function() { console.log('animal eat'); } } // 狗 function Dog() { this.bark = function() { console.log('dog bark'); } } Dog.prototype = new Animal(); // 哈士奇 let hashiqi = new Dog(); hashiqi.bark(); // dog bark hashiqi.eat(); // animal eat
==推荐下面这个更加贴近实战的原型继承示例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>2-3-原型链继承demo</title> </head> <body> <div id="div1">Hello World</div> <script> // 写一个封装DOM查询的例子 function Elem(id) { this.elem = document.getElementById(id); } Elem.prototype.html = function(val) { let elem = this.elem; if(val) { elem.innerHTML = val; return this; // 链式操作 } else { return elem.innerHTML; } } Elem.prototype.on = function(type, fn) { let elem = this.elem; elem.addEventListener(type, fn); return this; } let div1 = new Elem('div1'); // console.log(div1.html()); div1.html('<p>Hello Imooc</p>') .on('click', function() { alert('clicked'); }) .html('<p>javascript</p>'); </script> </body> </html>
-
描述new一个对象的过程
- 创建一个新对象
- this指向这个新对象
- 执行代码,即对this赋值
- 返回this
//构造函数 function Foo(name, age) { this.name = name; this.age = age; this.class = 'class-1'; // return this; // 默认有这一行 } let f = new Foo('zhangsan', 20); let f2 = new Foo('lisi', 22); //创建多个对象 console.log(f); // Foo {name: 'zhangsan', age: 20, class: 'class-1'} console.log(f2); // Foo {name: 'lisi', age: 22, class: 'class-1'}
-
zepto(或其他框架)源码中如何使用原型链
- 阅读源码是高效提高技能的方式
- 但不能“埋头苦钻”,有技巧在其中(阅读之前,可以先看一看别人分析这个源码的文章或视频)
- 慕课网搜索"zepto设计和源码分析"
3-作用域和闭包
3-1 题目
- 说一下对变量提升的理解
- 说明this几种不同的使用场景
- 创建10个<a>标签,点击的时候弹出来对应的序号
- 如何理解作用域
- 实际开发中闭包的作用
3-2 知识点
-
执行上下文
- 范围:一段<script>或者一个函数
- 全局:变量定义、函数声明 一段<script>
- 函数:变量定义、函数声明、this、arguments 函数
注意:"函数声明"和"函数表达式"的区别
// 执行上下文 /* 全局: 变量提升:var a = undefined 注意a = 100的赋值并不提前 函数声明提前 */ console.log(a); // undefined var a = 100; fn('zhangsan'); // 'zhangsan' 20 function fn(name) { age = 20; console.log(name, age); var age; bar(100); // 100 function bar(num) { console.log(num); } } // "函数声明"和"函数表达式"的区别 fn(); // 函数声明会提前,不报错 function fn() { // 声明 } fn1(); // 函数表达式不会提前,报错 var fn1 = function() { // 函数表达式 }
-
this
- this要在执行时才能确认值,定义时无法确认
// this var a = { name: 'A', fn: function() { console.log(this.name); } }; a.fn(); // A this === a a.fn.call({name: 'B'}); // B this === {name: 'B'} var fn1 = a.fn; fn1(); // 输出值为空 this === window
4种执行方式
- 作为构造函数执行
- 作为对象属性执行
- 作为普通函数执行
- call apply bind
// 作为构造函数执行 function Foo(name) { // this = {}; this.name = name; // return this; } var f = new Foo('zhangsan'); console.log(f); // Foo {name: 'zhangsan'} // 作为对象属性执行 var obj = { name: 'A', printName: function() { console.log(this.name); } } obj.printName(); // A // 作为普通函数执行 function fn() { console.log(this); } fn(); // Window // call apply bind function fn1(name, age) { alert(name); console.log(this); // {x: 100} } fn1.call({x: 100}, 'zhangsan', 20); // this === {x: 100} var fn2 = function (name, age) { alert(name); console.log(this); // {y: 200} }.bind({y: 200}); fn2('zhangsan', 20); // this === {y: 200}
-
作用域
- 没有块级作用域
- 只有函数和全局作用域
// 无块级作用域 // if(true) { // var name = 'zhangsan'; // } // 与上等同,推荐下面这种写法 var name; if(true) { name = 'zhangsan'; } console.log(name); // zhangsan // 只有函数和全局作用域 var a = 100; function fn() { var a = 200; console.log('fn', a); // fn 200 } console.log('global', a); // global 100 fn();
-
作用域链
- 当前作用域没有没有定义的变量,即“自由变量”,“自由变量”需要在父级作用域中找到
- 作用域链是在预编译阶段形成的
// 作用域链 // demo1 var a = 100; function fn() { var b = 200; // 当前作用域没有没有定义的变量,即“自由变量” console.log(a); // 100 在父级作用域中找到 console.log(b); // 200 } fn(); // demo2 var a = 100; function F1() { var b = 200; function F2() { var c = 300; console.log(a); // 100 a 是自由变量 console.log(b); // 200 b 是自由变量 console.log(c); // 300 } F2(); } F1();
-
闭包 – 两个使用场景
- 函数作为返回值
- 函数作为参数传递
// 函数作为返回值 function F1() { var a = 100; // 返回一个函数(函数作为一个返回值) return function() { console.log(a); // 自由变量,父级作用域寻找 } } // f1 得到一个函数 var f1 = F1(); var a = 200; f1(); // 100 // 函数作为参数传递 function F1() { var a = 100; // 返回一个函数(函数作为一个返回值) return function() { console.log(a); // 自由变量,父级作用域寻找 } } // f1 得到一个函数 var f1 = F1(); function F2(fn) { var a = 200; fn(); } F2(f1); // 100
3-3 题目解答
- 说一下对变量提升的理解
- 变量定义
- 函数声明(注意和函数表达式的区别)
- demo同3-2的执行上下文
- 说明this几种不同的使用场景
4种执行方式- 作为构造函数执行
- 作为对象属性执行
- 作为普通函数执行
- call apply bind
- 创建10个<a>标签,点击的时候弹出来对应的序号
// 问题:创建10个<a>标签,点击的时候弹出来对应的序号 // 这是一个错误的写法,无论点击哪个标签弹出的序号都是10 var i, a; for(i = 0; i < 10; i++) { a = document.createElement('a'); a.innerHTML = i + '<br>'; a.addEventListener('click', function(e) { e.preventDefault(); alert(i); // 自由变量,要去父作用域获取值 }); document.body.appendChild(a); } 这是正确的写法 var i, a; for(i = 0; i < 10; i++) { (function(i) { // 函数作用域 a = document.createElement('a'); a.innerHTML = i + '<br>'; a.addEventListener('click', function(e) { e.preventDefault(); alert(i); // 自由变量,要去父作用域获取值 }); document.body.appendChild(a); })(i); }
- 如何理解作用域
- 自由变量
- 作用域链,即自由变量的查找
- 闭包的两个使用场景
- 实际开发中闭包的作用
// 问题:闭包在实际开发中的应用:主要用于封装变量,收敛权限 function isFirstLoad() { var _list = []; // 在函数 isFirstLoad 外面,根本不可能修改掉 _list 的值 return function(id) { if(_list.indexOf(id) >= 0) { return false; } else { _list.push(id); return true; } } } // 使用 var firstLoad = isFirstLoad(); console.log(firstLoad(10)); // true console.log(firstLoad(10)); // false console.log(firstLoad(20)); // true console.log(firstLoad(10)); // false
4-异步和单线程
4-1 题目
- 同步和异步的区别是什么?分别举一个同步和异步的例子
- 一个关于setTimeout的笔试题
- 前端使用异步的场景有哪些
4-2 知识点
- 什么是异步(对比同步)
// 什么是异步 console.log(100); setTimeout(function() { console.log(200); }, 1000); console.log(300); // 执行结果:100 300 200 // 对比同步 console.log(100); alert(200); // 用户点击确认之后才会输出300 console.log(300);
- 前端使用异步的场景
- 何时需要异步
- 在可能发生等待的情况
- 等待过程中不能像alert一样阻塞程序运行
- 因此,所有的“等待的情况”都需要异步
- 前端使用异步的场景
- 定时任务:setTimeout, setInterval
- 网络请求:ajax请求, 动态<img>加载
- 事件绑定
// ajax请求代码示例 console.log('start'); $.get('http://ku.qingnian8.com/wxList.php', function(res) { console.log(res); }); console.log('end'); // <img>加载示例 console.log('start'); let img = document.createElement('img'); img.onload = function() { console.log('loaded'); } img.src = 'https://pics2.baidu.com/feed/d1160924ab18972bbbff10c7d27c63829f510a35.jpeg@f_auto?token=cce7c10b15dc1d84cb721c44a961da08'; document.body.appendChild(img); console.log('end'); // 事件绑定示例 console.log('start'); document.getElementById('btn1').addEventListener('click', function() { alert('clicked'); }); console.log('end'); // 输出结果 start end start end start end loaded 网络请求获得的数据
- 何时需要异步
- 异步和单线程
// 异步和单线程 console.log(100); setTimeout(function() { console.log(200); });
- 上述程序执行过程如下:
- 执行第一行,打印100
- 执行setTimeout后,传入setTimeout的函数会被暂存起来,不会立即执行(单线程的特点,不能同时干两件事)
- 执行最后一行,打印300
- 待所有程序执行完,处于空闲状态时,会立马看有没有暂存起来的要执行。
- 发现暂存起来的setTimeout中的函数无需等待时间,就立即执行。
- 上述程序执行过程如下:
4-3 题目解答
- 同步和异步的区别是什么?分别举一个同步和异步的例子
- 同步会阻塞代码执行,而异步不会
- alert是同步,setTimeout是异步
- 一个关于setTimeout的笔试题
// 问题:一个关于setTimeout的笔试题 console.log(100); setTimeout(function() { console.log(200); }); console.log(300); setTimeout(function() { console.log(400); }, 1000); console.log(500); // 输出结果:100 300 500 200 400
- 前端使用异步的场景有哪些
- 定时任务:setTimeout, setInterval
- 网络请求:ajax请求, 动态<img>加载
- 事件绑定
5-JS常用内置对象API
5-1 题目
- 获取2017-06-10格式的日期
- 获取随机数,要求是长度一致的字符串格式
- 写一个能遍历对象和数组的通用forEach函数
5-2 知识点
- 日期API
// 日期API Date.now(); // 获取当前时间戳--毫秒数 let date = new Date(); // 获取系统当前时间--Fri Oct 14 2022 16:14:17 GMT+0800 (中国标准时间) date.getTime(); // 获取时间戳--毫秒数 // 获取日期分量API let year = date.getFullYear(); // 年 let month = date.getMonth(); // 月(0 - 11) let date1 = date.getDate(); // 日(1 - 31) let xingqi = date.getDay(); // 星期(0 - 6) let hour = date.getHours(); // 小时(0 - 23) let minute = date.getMinutes(); // 分钟(0 - 59) let second = date.getSeconds(); // 秒 (0 - 59) let millisecond = date.getMilliseconds(); // 毫秒(0 - 59) // 设置日期分量API date.setFullYear(year + 1); date.setMonth(month + 1); date.setDate(date1 + 1); date.setHours(hour + 1); date.setMinutes(minute + 1); date.setSeconds(second + 1); date.setMilliseconds(millisecond + 1);
- MathAPI
- 获取随机数Math.random()
- 数组API
- forEach 遍历所有元素
let arr = [1, 2, 3]; arr.forEach(function(item, index) { // 遍历数组的所有元素 console.log(index, item); }); /* 输出结果 0 1 1 2 2 3 */
- every 判断所有元素是否都符合条件
let arr = [1, 2, 3, 4]; let result = arr.every(function(item, index) { // 用来判断所有的数组元素,都满足一个条件 if(item < 4) { return true; } }); console.log(result); // false
- some 判断是否有至少一个元素符合条件
let arr = [1, 2, 3, 4]; let result = arr.some(function(item, index) { // 用来判断所有的数组元素,只要有一个满足条件即可 if(item < 2) { return true; } }); console.log(result); // true
- sort 排序:将数组排序,并将排序后的数组返回
let arr = [1, 4, 2, 3, 5]; let result = arr.sort(function(a, b) { // 升序 // return a - b; // 降序 return b - a; }); console.log(arr); // [5, 4, 3, 2, 1]
- map 对元素重新组装,生成新数组
let arr = [1, 2, 3, 4]; let result = arr.map(function(item, index) { // 将数组元素重新组装,并返回 return '<b>' + item + '</b>'; }); console.log(result); // ['<b>1</b>', '<b>2</b>', '<b>3</b>', '<b>4</b>']
- filter 过滤符合条件的元素
let arr = [1, 2, 3, 4]; let result = arr.filter((item, index) => { // 通过某一个条件过滤数组 if(item >= 2) { return true; } }); console.log(result); // [2, 3, 4]
- 对象API
let obj = { x: 100, y: 200, z: 300 }; for(let key in obj) { // 注意这里的 hasOwnProperty,在讲原型链的时候讲过了 if(obj.hasOwnProperty(key)) { console.log(key, obj[key]); } } /* 输出结果 x 100 y 200 z 300 */
5-3 题目解答
- 获取2017-06-10格式的日期
// 问题:获取2022-10-14格式的日期 function formatDate(date) { if(!date) { date = new Date(); } let year = date.getFullYear(); let month = date.getMonth() + 1; let date1 = date.getDate(); if(month < 10) { // 强制类型转焕 month = '0' + month; } if(date1 < 10) { // 强制类型转焕 date1 = '0' + date1; } // 强制类型转焕 return year + '-' + month + '-' + date1; } let date = new Date(); console.log(formatDate(date)); // 2022-10-14
- 获取随机数,要求是长度一致的字符串格式
// 问题:获取随机数,要求是长度一致的字符串格式 let random = Math.random(); random = random + '0000000000'; // 后面加上10个零 random = random.slice(0, 10); console.log(random);
- 写一个能遍历对象和数组的通用forEach函数
// 问题:写一个能遍历对象和数组的通用forEach函数 function forEach(obj, fn) { // 准确判断是不是数组 if(obj instanceof Array) { obj.forEach(function(item, index) { fn(index, item); }); } // 不是数组就是对象 else { for(let key in obj) { if(obj.hasOwnProperty(key)) { fn(key, obj[key]); } } } } let arr = [1, 2, 3]; // 注意,这里参数的顺序换了,为了和对象的遍历格式一致 forEach(arr, function(index, item) { console.log(index, item); }); /* 输出结果 0 1 1 2 2 3 */ let obj = {x: 100, y: 200}; forEach(obj, function(key, val) { console.log(key, val); }); /* 输出结果 x 100 y 200 */