面试题笔记

JS基础

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

以上代码2-4行,JS能够把值类型直接区分出来
5-8行是引用类型

何时使用===和==?
考点:强制类型转换

if(obj.a == null) {
  // 这里相当于 obj.a === null || obj.a === undefined
  // 这是 jquery 源码中推荐的写法
}

function(a, b){
  if(a == null){
    ...
    // 看 a 的参数是否存在时也可以用==
  }
}

以上两种情况都是在 a 定义之后才进行判断的,否则会报错
除了以上情况,其他情况全用===
===不会进行强制类型转换,所以有:

// 以下两种情况在 == 得到的是true
'' === 0
==> false

null === undefined
==> false

0、NaN、''、null、undefined、false它们的if会被转换成false

JS中有哪些内置函数?
考点:数据封装类对象

Object
Array
Boolean
Number
String
Function
Date
RegExp
Error

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

值类型的存储是把数值分块存在内存中
引用类型的存储是多个变量共用一个内存块,是为了节省空间才这么做的
值类型的存储不会相互影响,而引用类型赋值是变量指针的形式赋值,不是真正的值的拷贝,所以它们的值的修改是相互干预的
引用类型有对象、数组、函数

如何理解JSON?
考点:JSON 只不过是一个 JS 对象而已

// 把对象转换成字符串
JSON.Stringify({a:10, b:20})
// 把字符串转换成对象
JSON.parse('{"a":10, "b":20}')

JSON是一种数据格式,同时也是 JS 对象,在 JS 中具有以上两个api

如何准确判断一个变量是数组类型

var arr = []
arr instanceof Array // true
typeof arr // object, typeof 是无法判断是否为数组的

写一个原型链继承的例子

// 动物
function Animal() {
  this.eat = function () {
    console.log('animal eat')
  }
}
// 狗
function Dog() {
  this.bark = function () {
    console.log('dog bark')
  }
}
Dog.prototype = new Animal()
// 哈士奇
var hashiqi = new Dog()

一个更接近实际的例子

function Elem() {
  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')
div1.html('<p>hello imooc</p>').on('click',funtion() {
  alert('clicked')
}).html('<p>javascript</p>')

描述 new 一个对象的过程

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) // 创建多个对象

创建一个新对象
this 指向这个新对象
执行代码,即对 this 赋值
返回 this
zepto 源码中如何使用原型链

作用域和闭包

知识点

变量提升/执行上下文

// 函数声明
function fn(){...}
// 函数表达式
var fn1 = function(){...}

需要强调的是,全局函数作用域会将函数声明整个提升到代码最前面,而不会把函数表达式里面的内容提到最前,也就是说,在函数声明之前调用fn是有效的,可以得到我们想得到的结果,可是在函数表达式之前调用fn1是无效的,控制台或者浏览器都会报错,因为全局函数作用域只会这样进行变量提升:var fn1 = undefined


image.png

上图中,在代码未执行之前,先进行变量提升:将var a和function fn(name){...}提到所有代码前面,此时a并没有赋值,而是先将a用undefined占位,所以执行第一句代码时,a输出为undefined,而执行到第四句时整个fn函数已经声明,所以不会报错,正常执行,得到'zhangsan', 20。
以上总结为全局的执行上下文:变量定义、函数声明。
另外,在函数内执行上下文是这样的:变量定义、函数声明、this、argument

闭包

使用场景:

// 函数作为一个返回值传递
function F1() {
  var a = 100
  return function() {
    console.log(a)
  }
}
var f1 = F1()
var a = 200
F1() // 100
// 函数作为参数传递
function F1() {
  var a = 100
  return function() {
    console.log(a)
  }
}
var f1 = F1()
function F2(fn) {
  var a = 200
  fn()
}
F2(f1) // 100

说一下对变量提升的理解
用执行上下文的知识解释
说明this的几种不同的使用场景
作为构造函数执行

function Foo(name) {
  this.name = name
}
var f = new Foo('zhangsan') // 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)
}
fn1.call({x: 100}, zhangsan, 20) // Object {x: 100}
fn1.apply({x: 100}, [zhangsan, 20]) // Object {x: 100}

function fn2(name, age) {
  alert(name)
  console.log(this)
}.bind({y: 200})
// 以上bind代码会报错,bind只接受函数表达式
// 改为如下
var fn2 = function(name, age) {
  alert(name)
  console.log(this)
}.bind({y: 200})
fn2('zhangsan', 20) // Object{y: 200}

创建十个a标签,点击的时候弹出来对应的序号

var i
for(i = 0; i < 10; i++) {
  (function (i) {
    var a = document.creatElement('a')
    a.innderHTML = i + '<br>'
    a.addEventListener('click', function(e) {
      e.preventDefault()
      alert(i)
    })
    document.body.appenChild(a)
  })(i)
}

如何理解作用域

要点:
自由变量
作用域链,即自由变量的查找
闭包的两个场景

JS中,没有块级作用域这个概念,举个例子

if(name) {
  var name = 'zhangsan'
}
console.log(name)
//这段代码中,name输出为zhangsan,说明全局中可以访问if循环内部的变量,在java、c#等高级语言中,外部是访问不到内部的变量的,这种情况就是块级作用域
//因此,推荐以下写法,增加可读性
var name
if(name) {
  name = 'zhangsan'
}
var a = 100
function F1() {
  var b = 200
  function F2() {
    var c = 300
    console.log(a)// 自由变量
    console.log(b)// 自由变量
    console.log(c)
  }
  F2()
}
F1()

以上代码中执行到F2时输出a,由于在F2的作用域(F2函数内)没有找到a,会到它的父级作用域里面找,如果找不到,还会到父级作用域的父级作用域内去找,一直找到全局作用域直到找到为止,否则会报错。
像这样,一个自由变量一直不断地往它的父级作用域去找,形成一个链式结构,叫做作用域链
实际开发中闭包的应用

function isFirstLoad() {
  var _list = [] // 带下划线的变量表示私有
  return function (id) {
    if (_list >= 0) {
      ruturn false
    }else {
      _list.push(id)
      return true
    }
  }
}

// 使用
var firstLoad = isFirstLoad()
firstLoad(10) // true
firstLoad(10) // false
firstLoad(20) // true
firstLoad(20) // false

异步和单线程

知识点

什么是异步(对比同步)

// 执行第一行,打印100。
// 执行setTimeout后,传入setTimeout的函数会被暂时存起来,不会立即执行(单线程的特点,不能同时做两件事)。
// 执行最后一行,打印300。
// 待所有的程序执行完,处于空闲状态时,会立马看有没有暂存起来的任务要执行。
// 发现暂存起来的setTimeout中的函数无需等待时间,就立即拉回来执行。
console.log(100)
setTimeout(function () {
  console.log(200)
})
console.log(300)

异步和同步的区别是什么?分别举一个同步和异步的例子
同步会阻塞代码执行,异步不会
alert是同步,setTimeout是异步

// 同步
console.log(100)
alert('hold on')
console.log(200)

一个关于setTimeout的笔试题

console.log(1)
setTimeout(function () {
  console.log(2)
}, 0) // 进入暂存队列
console.log(3)
setTimeout(function () {
  console.log(4)
}, 1000) // 进入暂存队列
console.log(5)
// 1 3 5 2 4

前端使用异步的场景有哪些
定时任务:setTimeout,setInterval
网络请求:ajax请求,动态请求,动态< img >加载
事件绑定

// ajax请求代码示例
console.log('start')
$.get(./data1.json, function (data1) {
  console.log(data1)
})
console.log('end')
// <img>加载示例
console.log('start')
var img = document.creatElement('img')
img.onload = function () {
  console.log('loaded')
}
img.src = 'xxx.png'
console.log('end')
// 事件绑定示例
console.log('start')
document.getElementById('btn1').addEventListener('click', function () {
  alert('clicked')
})
console.log('end')

其他知识点

日期

Date.now() // 获取从1970年到当前时间的毫秒数
var dt = new Date()
dt.getTime() // 获取从声明dt时刻开始到现在的毫秒数
dt.getFullYear() // 获取声明dt时刻所处的年份
dt.getMonth() // 获取声明dt时刻所处的月份(0 - 11)
dt.getDate() // 获取声明dt时刻所处的日(0 - 31)
dt.getHours() // 获取声明dt时刻所处的小时(0 - 23)
dt.getMinutes() // 获取声明dt时刻所处的分钟(0 - 59)
dt.getSeconds() // 获取声明dt时刻所处的秒(0 - 59)

随机数

Math.random()在前端的应用是清除缓存,在一个链接中加入随机数避免浏览器从不变的链接地址中走缓存,访问不到实时更新的页面

数组API

forEach 遍历所有元素

var arr = ['a','b','c']
arr.forEach(function (item, index) {
  // 遍历数组所有元素
  console.log(index, item)
})

every 判断所有元素是否都符合条件

var arr = [1,2,3]
var result = arr.every(function (item, index) {
  //用来判断所有的数组元素是否都满足某个条件
  if(item < 4) {
    return true
  }
})
console.log(result)

some 判断是否至少有一个元素符合条件

var arr = [1,2,3]
var result = arr.every(function (item, index) {
  //用来判断所有的数组元素是否至少有一个满足了某个条件
  if(item < 2) {
    return true
  }
})
console.log(result)

sort 排序

var arr = [1,4,2,3,5]
var arr2 = arr.sort(function (a, b) { 
  // 从小到大排序
  return a - b
  
  // 从大到小排序
  // return b - a
})
console.log(arr2)

map 对元素重新组装,生成新数组

var arr = [1,2,3,4]
var arr2 = arr.map(function (item, index) {
  // 将所有的元素重新组装,生成新数组
  return '<b>' + item + '</b>'
})
console.log(arr2)

filter 过滤符合条件的元素

var arr = [1,2,3]
var arr2 = arr.filter(function (item, index) {
  // 通过某一个条件过滤数组
  if (item >= 2) {
    return true
  }
})
console.log(arr2)

对象API

var obj = {
  x:100,
  y:200,
  z:300
}
var key
for (key in obj) {
  // 注意这里的hasOwnproperty,有关原型链的知识
  // 判断是否是obj原生的属性而不是原型中拿过来的属性
  if(obj.hasOwnProperty(key)) {
  console.log(key, obj[key])
  }
}

获取2017-06-10格式的日期

function formatDate(dt) {
  if(!dt) {
    dt = new Date()
  }
  var year = dt.getFullYear()
  var month = dt.getMonth() + 1
  var date = dt.getDate()
  if (month < 10) {
    // 强制类型转换
    return '0' + month
  }
  if (date < 10) {
    // 强制类型转换
    return '0' + date
  }
  return year + '-' + month + '-' + date
}
var dt = new Date()
var formatDate = formatDate(dt)
console.log(formatDate)

获取随机数,要求是长度一致的字符串格式

var random = Math.ramdom()
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 {
    // 不是数组就是对象
    for (key in obj) {
      if (obj.hasOwnProperty(key) {
        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, value) {
  console.log(key, value)
})

dom本质

Document Object Model(文档对象模型)
DOM可以理解为:
浏览器把拿到的html代码,结构化成一个浏览器能识别并且js可操作的一个模型。
DOM是哪种基本的数据结构

DOM操作的常用API有哪些?
获取DOM节点,以及节点的property(js)和Attribute(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')
var p = pList[0]
console.log(p.style.width)
p.style.width = '100px'
console.log(p.className)
p.className = 'p1'

p.getAttribute('data-name')
p.setAttribute('data-name', 'dataName')
p.getAttribute('style')
p.setAttribute('style', 'font-size:30px')
// 增加新节点
var p1 = document.creatElement('p')
p1.innerHTML = 'this is p1'
// 移动已有节点
div1.appendChild(p1)
var p2 = document.getElementById('p2')
div1.appendChild(p2)
// 获取父元素和子元素
var parent = div1.parentElement
var child = div1.childNodes
div1.removeChild(child[0])

DOM节点的attr和property有何区别
property只是一个js对象的属性的获取和修改
attribute是对html标签属性的获取和修改

事件

知识点

通用事件绑定

var btn = document.getElementById('btn1')
btn.addEventListener('click', function (event) {
  console.log('clicked')
})

function binEvent(elem, type, selector, fn) {
  if (fn == null) {
    fn = selector
    selector = null
  }
  elem.addEventListener(type, function (e) {
    var target
    if (selector) {
      target = e.target
      if (target.matches(selector)) {
        fn.call(target, e)
      }
    } else {
      fn(e)
    }
  })
}
// 使用代理
var div1 = document.getElementById('div1')
bindEvent(a, 'click', 'a', function(e) {
  console.log(this.innerHTML)
})
// 不使用代理
var a = document.getElementById('a1')
bindEvent(a, 'click', function (e) {
  console.log(a.innerHTML)
})

代理的好处:
代码简洁
减少浏览器内存占用

冒泡事件流程

DOM树形结构
事件冒泡:当点击一个节点时,首先触发它绑定的事件,再触发它的父节点绑定的事件,一直由它本身触发到body节点上的事件。这就叫冒泡
阻止冒泡e.stopPropatation()
冒泡的应用:代理

Ajax

知识点

XMLHttpRequest

状态码说明

image.png
image.png

跨域

  • 跨域是JSONP:
    例如我的网站要跨域访问某网提供的一个接口
    某网给了我一个地址http://xxx.com/api.js
    返回的内容格式如callback({x: 100, y: 200})(可动态生成)
  • 浏览器有同源策略,不允许ajax访问其他域接口
  • 跨域条件:协议、域名、端口,有一个不同就算跨域
  • 可以跨域的三个标签:
    <img src = xxx>场景:用于打点统计,统计网站可能是其他域
    <link href = xxx>
    <script src = xxx>
    后两个可以使用CDN
  • 所有的跨域请求都必须经过提供方的允许
  • 服务器设置http header
// 一段服务器设置http header示例
response.setHeader("Access-Control-Allow-Origin", "http://a.com, http://b.com");
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With");
response.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS")

// 接受跨域的cookie
response.setHeader("Access-Control-Allow-Credentials", "true");

手动编写一个ajax,不依赖第三方库

var xhr = new XMLHttpRequest()
xhr.open("GET", "/api", false)
xhr.onreadystatechange = function () {
  // 这里的函数异步执行
  if (xhr.readyState == 4) {
    if (xhr.status == 200) {
      alert(xhr.responseText)
    }
  }
}
xhr.send(null)

跨域的几种实现方式
JSONP
服务器端设置http header

存储

描述cookie、sessionStorage和localStorage的区别
cookie本身用于客户端和服务端通信,但是它有本地存储的功能,于是被“借用了”
cookie存储量太小只有4kb、所有http请求都带着会影响获取资源的效率、api简单,需要封装才能用document.cookie = ...
localStorage和sessionStorage是HTML5专门为存储而设计,最大容量为5M(不是所有http请求都带着)、api简单易用:localStorage.setItem(key, value);localStorage.getItem(key)
localStorage和sessionStorage的区别在于如果浏览器关闭后者会清理数据而前者只有在手动清理的时候才会清理
需要注意的地方是,在ios系统下的浏览器safari,localStorage.getItem会报错,所以最好统一进行try-catch封装,避免程序崩溃
总结区别:
容量
是否会携带到ajax中
api易用性

开发环境

常用git命令

  • git add . // 提交更改
  • git checkout xxx // 还原某个文件
  • git commit -m "xxx" // 对修改文件进行提交备注
  • git push origin master // 提交到远程仓库
  • git pull origin master // 另一个协作代码的人下载了我提交的更改
  • git branch // 查看当前所处分支
  • git checkout -b xxx / git checkout xxx // 切换分支
  • git merge xxx // 把之前在xxx分支上的更改全部合并到当前分支
  • git diff // 看看较上次提交的更改,本次产生了哪些变动
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容