JS基础
变量
面试题
1、JS使用typeof能得到哪些类型?
考点: JS变量类型
typeof undefined, //undefined
typeof 'abc' ,//string
typeof 123 ,//number
typeof true ,//boolean
typeof {} ,//object
typeof [] ,//object
typeof null ,//object
typeof console.log ,//function
typeof Symbol(1) // symbol
2、何时使用 === 何时使用 == ?
考点: 强类型转换
if(obj.a == null){
//这里相当于 obj.a === null || obj.a === undefined,简写形式
//这是 jquery 源码中推荐的写法
}
3、JS中有哪些内置函数?
Object、Array、Boolean、Number、String、Function、Date、RegExp、Error、Math、JSON、window、document等。。
4、JS变量按照存储方式区分哪些类型,并描述其特点
//值类型
//占用空间固定,值保存在栈中
//保存与复制的是值本身
var a = 10;
var b = a;
a = 11
console.log(b) //10
//引用类型
//占用空间不固定,值保存在堆中
//保存与复制的是指向对象的一个指针
var obj1 = {x: 100};
var obj2 = obj1;
obj1.x = 200;
console.log(obj2.x) //200
5、如何理解JSON?
JSON 只不过是一个 JS 对象而已
JSON.stringify({a:10, b:20})
JSON.parse('{"a": 10, "b": 20 }')
变量类型
——链接
-
值类型(内存中存具体值) vs 引用类型(内存中存的指针)
-
引用类型:对象,数组,函数
-
引用类型特点:不限制扩张属性
-
因为引用类型占据空间较大,所以用指针来共享空间
-
-
typeof运算符详解
- typeof只能区分值类型的详细类型
-
使用instanceof判断变量的详细类型
- arr instanceof Array; //true
-
使用Object.prototype.toString判断。
- 数值:返回[object Number]。
- 字符串:返回[object String]。
- 布尔值:返回[object Boolean]。
- undefined:返回[object Undefined]。
- null:返回[object Null]。
- 数组:返回[object Array]。
- arguments 对象:返回[object Arguments]。
- 函数:返回[object Function]。
- Error 对象:返回[object Error]。
- Date 对象:返回[object Date]。
- RegExp 对象:返回[object RegExp]。
- 其他对象:返回[object Object]。
变量计算
-
强制类型转换(值类型)
-
字符串拼接
-
== 运算符
-
使用 == 的情况,除了以下情况,其他一律用 ===
-
查看对象的属性是否存在 》
if (obj.a == null){ }
-
查看函数中的参数是否存在 》
function(a,b) {if (a == null) {...} }
var obj = {}; if(obj.a == null){ //这里相当于 obj.a === null || obj.a === undefined,简写形式 //这是 jquery 源码中推荐的写法 }
-
-
if语句
-
逻辑运算 (&&、||、!(not))
-
0、NaN、’’、""、null、undefined、false 自动转换为false
//字符串拼接 var a = 100 + 10 //110 var b = 100 + '10' //"10010" // == 运算符 100 == '100' //true 0 == '' //true //if语句 if(100) {} //把数字转换为true if('') {} //把字符串转换为false //逻辑运算 console.log(10&&0) //0 把10转换为true console.log(''||'abc') // "abc" 把‘’转换为false console.log(!window.abc)//true window.abc是undefined,把非undefined 转换 为true //判断一个变量会被当做true还是false var a = 100 console.log(!!a); // true
-
原型 原型链
面试题
1、如何准确判断一个变量是数组类型
?
变量 instanceof
Array
var arr = [];
arr instanceof Array //true
typeof arr //"object", typeof 是无法判断是否是数组的
2、写一个原型链继承的例子
//写一个封装DOM的例子
function Elem(id){
this.elem = document.getElementById(id);
}
Elem.prototype.html = function (val) {
var elem = this.elem;
if(val){
elem.innerHTML = val;
return this; //链式操作, 可有可无
}else{
return elem.innerHTML;
}
}
Elem.prototype.on = function(type, fn){
var elem = this.elem;
elem.addEventListener(type, fn);
return this;
}
var div1 = new Elem('div1');
//console.log(div1.html())
div1.html('<p>hello world<p>').on('click', function(){
alert('clicked');
}).html('<p>javascript<p>'
3、描述 new 一个对象的过程
- 创建一个新对象
- 将构造函数的作用域赋给新对象(this指向这个新对象)
- 执行构造函数中的代码,即对this赋值
- 返回新对象,即返回this
function Foo(name, age) {
this.name = name
this.age = age
//return this;
}
var f = new Foo('zhangsan', age)
4、zepto(或其他框架)源码中如何使用原型链
构造函数
//基本碰到首字母大写的函数, 就是构造函数,so构造函数尽量大写字母开头
function Foo(name, age) {
this.name = name
this.age = age
this.class = 'class-1'
//return this //默认有这一行
}
var f = new Foo('zhangsan', 20)
//var f1 = new Foo('lisi', 22) //创建多个对象
构造函数——扩展
- var a = {} 其实是 var a = new Object() 的语法糖
- var a = [] 其实是 var a = new Array() 的语法糖
- function Foo() {…} 其实是 var Foo = new Function(…)
- 使用 instanceof 判断一个函数是否是一个变量的构造函数
原型规则和实例
5条原型规则
-
所有的
引用类型
(数组、对象、函数),都具有对象的特性、即可自由扩展属性(除了 “null” 意外) -
所有的
引用类型
(数组、对象、函数),都有一个__proto__
(隐式原型) 属性,属性值是一个普通的对象 -
所有的
函数
,都有一个prototype
(显示原型) 属性, 属性值也是一个普通的对象 -
所有
引用类型
(数组、对象、函数),__proto__
属性值都指向它的构造函数的prototype
属性值 -
当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的
__proto__
(即它的构造函数的prototype) 中寻找。
var obj = {}; obj.a = 100; //1
var arr = []; arr.a = 100;
function Fn() {}
Fn.a = 100;
console.log(obj.__proto__);//2
console.log(arr.__proto__);
console.log(Fn.__proto__);
console.log(Fn.prototype);//3
console.log(obj.__proto__ === Object.prototype)//4 5
//构造函数
function Foo(name, age) {
this.name = name;
}
Foo.prototype.alertName = function() {
alert(this.name);
}
//创建示例
var f = new Foo('zhangsan')
f.printName = function() {
console.log(this.name)
}
//无论是函数f自身属性,还是从原型得到的属性,this永远指向f自己本身
f.printName();
f.alertName();
f.toString(); //要去 f.__proto__.__proto__ 寻找
//循环对象自身的属性
var item
//for in 对函数属性进行循环
for (item in f) {
//高级浏览器已经存在 for in 中屏蔽了来自原型的属性
//但是这里建议加上这个判断,保证程序的健壮性
if(f.hasOwnProperty(item)) {
console.log(item)
}
}
原型链
JavaScript对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xbtUgEGA-1590071179192)(https://i.loli.net/2019/05/21/5ce3f4978a28565863.png)]
instanceof
instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置
f
instanceof
Foo 的判断逻辑是:f 的
__proto__
一层层往上,能否对应到 Foo.prototype在试着判断 f
instanceof
Object
作用域 闭包
面试题
1、说一下对变量提升的理解
-
分为变量定义和函数声明(和函数表达式的区别)
-
全局作用域和函数作用域或者是(ES6)中块级作用域,变量声明(var)和函数声明会被提升到作用域顶部,而函数表达式不会,只是当做一个var变量提升,函数没有被提升,另外ES6 中 let 和 const标识符定义的变量也不会发生提升(暂时性死区 TDZ)
2、说明this几种不同的使用场景
- 作为构造函数执行
- 作为对象属性执行
- 作为普通函数执行
- call apply bind
- 箭头函数中(ES6)
this指的是当前运行的环境,即执行的时候
3、创建10个a标签,点击的时候弹出来对应的序号
var i;
for(i=0, i<10,i++) {
var a = document.createElement('a');
(function(i) {
a.innerHTML= a +'<br>';
a.addEventListener('click', function(e) {
e.preventDefault();
alert(i)
})
document.body.appendChild(a);
})(i)
}
4、如何理解作用域
- 自由变量: 在当前作用域没有被定义的变量
- 作用域链:若在当前作用域找不到,则往父级作用域查找,自由变量的查找
- 父级作用域:函数定义时所在的上下文
- 闭包两个场景,作为参数传入和函数返回值
5、实际开发中闭包的应用
-
闭包实际引用中主要用于封装变量,收敛权限
function isFirstLoad() { var _list = [] return function(id){ if(_list.indexOf(id) >= 0){ return false; }else { _list.push(id) return true } } } var firstload = isFirstLoad(); firstload(10); //true firstload(10); //false firstload(20); //true
执行上下文
范围:一段
全局:变量定义、函数声明
函数:变量定义、函数声明、this、arguments、函数
- 函数声明:function fnName () {…};
- 函数表达式 var fnName = function () {…};
- 匿名函数:function () {};
- 匿名函数属于函数表达式
PS:注意“函数声明”和“函数表达式”的区别
- JS解析时会提升当前作用域的函数声明,而函数表达式必须等到JS引擎执行到时,才会解析函数表达式。
- 函数表达式后面可以加括号立即调用该函数,函数声明不可以
function fn(name) {
//函数 开头默认创建this arguments
console.log(this)
console.log(arguments)
age = 20 //声明提前
console.log(name, age)
var age
bar(100)
function bar(num) {
console.log(num)
}
}
- 变量定义会把声明提前(undefined)未赋值,函数声明会把整个函数提前,函数表达式只会把函数名提前
console.log(a)//undefined
var a = 100
fn('zhangsan') //'zhangsan' 20
function fn(name) {
age = 20
console.log(name, age)
var age
}
this
在全局环境中,this 永远指向 window。
-
this
要在执行时才能确认值,定义时无法确认var a = { name: 'A', fn: function() { console.log(this.name) } } a.fn() //this === 'a' a.fn.call({name: 'B'}) //this === {name: 'B'} var fn1 = a.fn fn1() //this === window
-
作为构造函数执行
-
作为对象属性执行
-
作为普通函数执行 (this指向window)
-
call
apply
bind
function Foo(name) {
this.name = name
}
//由一个函数new出来的对象,this就代表即将new出来的这个对象
var f = new Foo('zhangsan')
var obj = {
name: 'A'
printName: function() {
console.log(this.name)//构造函数中的this
}
}
obj.printName() //对象中的this指向该对象 this === 'A'
function fn() {
console.log(this) //this === window 普通函数中的this永远指向window。
}
fn()
//call apply bind
function fn1(name , age) {
alert(name)
console.log(this)
}
fn1.call({x:100}, 'zhangsan', 20) //常用
fn1.apply({x:100}, ['zhangsan', 20])
//bind 要用函数表达式
var fn2 = function (name, age) {
alert(name)
alert(age)
console.log(this)
}.bind({y: 200}, 'lisi', 11);
fn2('zhangsan', 20)
作用域
- JS没有块级作用域,不要块中定义变量
- 只有函数和全局作用域
作用域链
自由变量不停的往父级作用域查找,就是所谓的作用域链
var a = 100
function F1() {
var b = 200
//当前作用域没有定义的变量,即“自由变量”
function F2() {
console.log(a) // a是自由变量
console.log(b) // b是自由变量
console.log(c)
}
F2()
}
闭包
有权访问另一个函数作用域中的变量的函数
-
函数作为返回值,在父作用域去找自由变量的值
-
函数作为参数传递
function F1() {
var a = 100
//返回一个函数 (函数作为返回值)
return function() {
console.log(a)
}
}
//f1的得到一个函数
var f1 = F1()
var a = 200
F1()// 打印 100
function F2(fn) { //函数作为参数传递
var a = 200
fn()
}
F2(f1) // 打印 100
异步单线程
面试题目
- 同步和异步的区别是什么?分别举一个同步和异步的例子
- 同步会阻塞代码执行,而异步不会
- alert是同步,setTimeout是异步
- 一个关于setTimeout的笔试题
- 前端使用异步的场景有哪些?
- 定时任务:setTimeout setInverval
- 网络请求:ajax 动态加载
- 事件绑定
什么是异步(对比同步)
前端使用异步的场景
- 在可能发生等待的情况,等待过程中不能相当alert一样阻塞程序运行,因此在发生等待的时候需要异步。
- 定时任务:
setTimeout
,setInverval
- 网络请求:ajax请求,动态加载
- 事件绑定
console.log('start')
//网络请求
$.get('./data1.json',function(data1) {
console.log(data1)
})
//图片加载
var img= document.createElement('img')
img.onload = function() {
console.log('loaded')
}
img.src = '/xxx.png'
//事件绑定
document.getElementById('btn1').addEventListener('click',function() {
alert('clicked')
})
console.log('end')
异步和单线程
其他知识
面试题
-
获取 2017-06-10格式的日期
function formatDate(dt) { if(!dt) { dt = new Date() } var year = dt.getFullYear() var month = dt.getMonth() var date = dt.getDate() if(month < 10){ month = '0' + month } if(date < 10){ date = '0' + date } return year + '-' + month + '-' + date } console.log(formatDate(new Date()))
-
获取随机数,要求是长度一致的字符串格式
var random = Math.random() random = random + '0000000000'; random = random.slice(0, 10); console.log(random)
-
写一个能遍历对象和数组的通用 forEach 函数
function forEach(obj, fn){ var key if(obj instanceof Array){ obj.forEach(function(item, index) { fn(index, item) }) }else { if(obj.hasOwnProperty(key)){ for(key in obj) { fn(key, obj[key]) } } } } var arr = [1,2,3] forEach(arr, function(index, item) { console.log(index, item) }) var obj = {x:100, y:200} forEach(obj, function(key, val) { console.log(key, val) })
日期
Date.now() //获取当前毫秒数
var dt = new Date()
dt.getTime() //获取毫秒数
dt.getFullYear() //年
dt.getMonth() //月 (0 - 11) 要加上 +1
dt.getDate() //日 (0 - 31)
dt.getHours() //小时 (0 - 23)
dt.getMinutes() //分钟 (0 - 59)
dt.getSeconds() //秒 (0 - 59)
Math
Math.random() //获取随机数 在链接后加随机数,可以清除缓存
数组API
- forEach 遍历所有元素
- every 判断所有元素是否都符合条件
- some 判断是否有至少一个元素符合条件
- sort 排序
- map 对元素重新组装,生成新数组
- filter 过滤符合条件的元素
var arr = [1,2,3]
arr.forEach(function(item, index) {
//遍历数组的所有元素
console.log(index, item)
});
var result1 = arr.every(function(item, index) {
//用来判断所有的数组元素,都满足一个条件
if(item < 4){
return true
}
})
console.log(result1)
var result2 = arr.some(function(item, index) {
//用来判断所有的数组元素,只要有一个满足条件即可
if(item < 2) {
return true
}
})
console.log(result2)
var arr1 = [1,4,2,3,5]
var arr2 = arr1.sort(function(a, b) {
// 从小到大排序
//return a - b
//从大到小排序
return b - a
})
console.log(arr2)
var arr3 = [1,2,3,4]
var arr4 = arr.map(function(item, index) {
//将元素重新组装,并返回
return '<b>' + item +'</b>'
})
console.log(arr4)
var arr5 = arr.filter(function (item, index) {
//通过某一个条件过滤数组
if(item >= 2) {
return true
}
})
console.log(arr5)
对象API
- for(key in obj) {}
var obj = {
x: 100,
y: 200,
z: 300
}
var key
for(key in obj){
//注意这里的 hasOwnProperty ,过滤掉原型链中的属性
if(obj.hasOwnProperty(key)) {
console.log(key, obj[key])
}
}
JS-WEB-AIP
常说的JS(浏览器执行的JS)包含两部分
- JS基础知识:ECMA 262标准
- JS-Web-API:W3C 标准,
- 没有规定任何JS基础相关的东西(变量类型、原型、作用域和异步。。。)
- 只管定义用于浏览器中JS操作页面的API和全局变量
DOM操作
-
DOM是哪种基本的数据结构
- 树
-
DOM操作的常用API有哪些
- 获取DOM节点,以及节点property和Attribute
- 获取父节点,获取子节点
- 新增节点,删除节点
-
DOM节点的Attribute 和property有和区别
- property是DOM中的属性,是Javascript里的对象;
- Attribute是HTML上的特性,它的值必须是字符串或者null
- 自定义的Property与Attribute不同步,不相等
- 非自定义的DOM property与 attributes 是有条件同步的
- 在IE<9中,浏览器会把所有的property和attribute强制映射
var elem = document.getElementById( 'id' ); //Attribute 添加,删除,获取 elem.getAttribute('gameid') elem.getAttribute('gameid') elem.removeAttribute('gameid') //property 添加,删除,获取 elem.gameid = 880; // 添加 console.log( elem.gameid ) // 获取 delete elem.gameid // 删除
DOM的本质
Element节点
-
新增节点
var div1 = document.getElementById('div1') //添加新节点 var p1 = document.createElement('p') p1.innerHTML = 'this is p1' div1.appendChild(p1) //添加新创建的元素 //移动已有节点 p2 = document.getElementById('p2') div1.appendChild('p2')
-
获取父元素
var div1 = document.getElementById('div1') var parent = div1.parentElement; var child = div1.childNodes div1.removeChild(child[0])
-
获取子元素
-
删除节点
获取DOM节点
var div1 = document.getElementById('div1')//元素
var divList = document.getElementsByTagName('div') //集合
console.log(divList.length)
console.log(divList[0])
var containerList = document.getElementsByClassName('.container')//集合
var plist = document.querySelectorAll('p') //集合
节点属性property
- style
- className
- nodeName
- nodeType
Attribute属性操作
- Element.attributes
- Element.getAttribute() 读取某个属性的值
- Element.getAttributeNames() 返回当前元素的所有属性名
- Element.setAttribute() 写入属性值
- Element.hasAttribute() 某个属性是否存在
- Element.hasAttributes() 当前元素是否有属性
- Element.removeAttribute() 删除属性
BOM
- 如何检测浏览器的类型
- 拆解url的各部分
navigator(浏览器)
- navigator.userAgent (简称ua)
screen
- screen.width
- screen.height
location
- location.href //地址
- location.protocol //协议
- location.host //域名
- location.pathname //路径
- location.search //查询字符串
- location.hash //哈希
history
- history.back() //返回
- history.forward() //前进
事件绑定
题目
- 编写一个通用的事件监听函数
var btn = document.getElementById('btn1')
btn.addEventListener('click', function(event) {
console.log('clicked')
})
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
var a = document.getElementById('link1')
bindEvent(a , 'click', function(e) {
e.preventDefault()//阻止默认行为
alert('clicked')
})
- 描述事件冒泡流程
- 对于一个无限下拉加载图片的页面,如何给每个图片绑定事件
DOM事件流
DOM事件流(event flow )存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。 dom标准事件流的触发的先后顺序为:先捕获再冒泡
- 事件捕获(event capturing):当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件。
//addEventListener最后一个参数,为true则代表使用事件捕获模式,
//false则表示使用事件冒泡模式。即默认模式
window.addEventListener('click', function() {
console.log('4. You click window');
}, true);
- **事件冒泡(dubbed bubbling):**与事件捕获恰恰相反,事件冒泡顺序是由内到外进行事件传播,从目标元素直到根节点。
- 阻止事件冒泡 stopPropagation()
关于IE低版本的兼容性
- IE低版本使用attachEvent绑定事件,和W3C标准不一样
- IE低版本使用量已经非常少,很多网站都早已不支持
- 建议对IE低版本的兼容性:了解即可,无需深究
- 如果遇到对IE低版本要求苛刻的面试,果断放弃
- IE10及以下不支持捕获型事件,所以就少了一个事件捕获阶段,IE11、Chrome 、Firefox、Safari等浏览器则同时存在。
通用事件绑定与解绑
代理
事件委托
JS开发环境
- IDE(写代码的效率)
版本管理GIT
JS模块化
打包工具
运行环境
页面渲染
性能优化
JS面试题
6、window.onload 和 DOMConetentLoaded 的区别?
考点:浏览器渲染过程
7、用JS创建10个标签, 点击的时候弹出来对应的序号?
考点:作用域
8、简述如何实现一个模块加载器, 实现类似require.js的基本功能?
考点:JS模块化
9、实现数组的随机排序?
考点:JS基础算法
HTTP
同源策略
同源策略可防止 JavaScript 发起跨域请求。源被定义为 URI、主机名和端口号的组合。此策略可防止页面上的恶意脚本通过该页面的文档对象模型,访问另一个网页上的敏感数据。
跨域
原因:浏览器同源策略导致了跨域
作用:隔离恶意文件的重要安全机制
解决:
- jsonp,允许script加载第三方资源
- 反向代理(nginx服务器配置Access-Control-Allow-Origin)
- cors前后端协议头设置Access-Control-Allow-Origin
- iframe嵌套通讯,postmessage,浏览器插件