JS基础知识
变量类型和计算
常见题目:
- js中使用typeof能得到的那些类型
- 何时使用=== 和== (a == null 与 a=== null || a === undefined 可以互相替换)
- js有哪些内置函数 (数据封装类对象):Object、Array、Boolean、Number、String、Function、Date、RegExp、Error(Math为js内置对象)
- js变量按照存储方式区分为哪些类型,并描述其特点
- 如何理解JSON(js中的内置对象):JSON.stringify()转换为字符串 JSON.parse('{"a": ""}')转换为对象
知识点:
- 变量类型: 值类型 vs 引用类型 、typeOf运算符详解
Number,Boolean,String,Undefined,Null
var a = 100
var b = a
a = 200
console.log(b) // 100
(2)引用类型(对象、数组、函数): (每个变量只能存储一个指针,指针指向被存储的值)
特点:可以无限制扩展属性(节省空间)
var a = { name: '小明'}
var b = a // a和b指向同一个对象
a.name = '老王'
console.log(b.name) // 老黄(3)typeof运算符typeof undefined // undefined
typeof 'abc' // string
typeof 123 // number
typeof true // Boolean
typeof {} // object
typeof [] // object
typeof null // object
typeof console.log // function- 变量计算
原型和原型链
常见题目:
- 如何准确判断一个变量是数组类型
var arr = []
arr instanceof Arry // true
typeOf arr // Object- 写一个原型链继承的例子
// 不建议使用 只是进行较为简单的例子展示
function Animal() {
this.eat = function (name) {
console.log(name + 'eat')
}
}
// 狗
function Dog() {
this.bark = function () {
console.log('dos bark')
}
}
Dog.prototype = new Animal()
// 泰迪
var taidi = new Dog()
taidi.eat('taidi')
taidi.bark()
taidi.__proto__ === Dog.prototype // true
taidi.__proto__.__proto__ === Dog.prototype._proto_ === Animal.prototype // true
// 原生设置innerHtml/监听事件
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 Element('div1')
div1.html('插入文本')
div1.on('click', funtion() {
console.log('绑定事件')
})
// 链式操作
div1.html('插入文本').on('click', funtion() {
console.log('绑定事件')
}).html('由于返回了this')
- 描述new一个对象的过程: 创建一个新对象、this指向这个新对象、执行代码(对this进行赋值)、返回this
var Dog = function(name) {
this.name = name
}
Dog.prototype.bark = function () {
console.log('wangwang')
}
Dog.prototype.sayName = function () {
console.log('my name is' + this.name)
}
let sanma = new Dog('三毛')
sanmao.sayName()
sanmao.bark()
// new 的作用
// 创建一个新对象obj
// 把obj的_proto_指向Dog.prototype实现继承
// 执行构造函数,传递参数, 改变this指向 Dog.call(obj, ...args)
// 最后把obj赋值给新变量
var _new = function () {
let constructor = Array.prototype.shift.call(arguments) // 取第一个参数
let args = arguments
const obj = new Object()
obj._proto_ = constructor.prototype
constructor.call(obj, ...args)
return obj
}
var simao = _new (Dog, 'simao')
simao.bark()
simao.sayName()
console.log(simao instanceof Dog) // true- zepto(或者其他框架)源码中如何使用原型链
知识点:
- 构造函数(在定义构造函数的时候养成良好的代码习惯,首字母大写)
function Example(name, age) {
// 先是默认 this为一个空对象
this.name = name
this.age = age
this.sex = 'women'
// return this // 默认有这一行
}
var f = new Example('老王', '40')
var f2 = new Example('小明', '8') // 创建多个对象- 构造函数扩展
(1)var a = {} 其实是var a = new Object()的语法糖
(2)var a = [] 其实是var a = new Array()的语法糖
(3)function Example() {...} 其实是var Example = new Function()的语法糖
(4)使用instanceof判断一个函数是否是一个变量的构造函数
- 原型规则和示例
(1)所有的引用类型(数组、对象、函数),都具有对象特性,且具有自由扩展属性(除null);
var obj = {};obj.a = 100;
var arr = [];arr.a = 100;
function fn() {}
fn.a = 100(2)所有的引用类型(数组、对象、函数),都有一个_proto_(隐式原型)属性,属性值是一个普通的对象(浏览器自己创建);
obj._proto_
arr._proto_
fn._proto_(3)所有的函数,都有一个prototype(显示原型)属性,属性值也是一个普通的对象(浏览器自己创建)
fn.prototype(4)所有的引用类型(数组、对象、函数),_proto_属性值指向它的构造函数“prototype”属性值;
obj._proto_ === Object.prototype // true(5)当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去他的——proto_(即它的构造函数的prototype)中寻找。
function Example(name, age) {
// 先是默认 this为一个空对象
this.name = name
this.age = age
this.sex = 'women'
// return this // 默认有这一行
}
Example.prototype.alertName = function() {
console.log(this.name) // alertName 是构造函数的属性
}
var f = new Example('老王', '40')
f.printName = function() {
console.log(this.name) // printName 是f本身的一个属性
}
f.printName() // 老王
f.alertName() // 老王
// 通过属性的方法调用不论是自身的属性还是构造函数的属性其中的this都指向调用者
for(var item in f) {
// 高级浏览器已经在 for in中屏蔽了构造函数中的属性
if(f.hasOwnProperty(item)) { // 判断是否为f本身的属性
console.log(item) // 不包含alertName
}
}- 原型链
f.toString() // 要去f._proto_._proto_中查找- instanceof(用于判断引用类型属于哪个构造函数的方法)
判断逻辑: f的_proto_一层一层往上,能否对应到Example.prototype
f instanceof Example // true
f instanceof Object // true作用域和闭包
常见题目:
- 说一下对变量提升的理解
- 说明this几种不同的使用场景
- 创建10个<a>标签,点击的时候弹出对应的序号
for (var i = 0; i<10; i++) {
(function(i) {
var a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(1)
})
document.body.appendChild(a)
})(i)
}- 如何理解作用域
- 实际开发中闭包的应用
// 闭包实际应用中主要应用于封装变量,收敛权限
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
知识点:
- 执行上下文
范围: 一段<script>或者一个函数
全局:变量定义、函数声明 一段<script>
console.log(a) // undefined
var a = 1
fn('老王')
function fn(name) { // 函数声明
age = 40
console.log(name, age)
var age
}
fn2('小明') // Uncaught TypeError: fn2 is not a function
var fn2 = function fn(name) { // 函数表达式
age = 8
console.log(name, age)
var age
}函数:变量定义、函数声明、this、arguments 函数
注意: 函数声明和函数表达式的区别
- this
(1)作为构造函数执行
function F() {
this.name = name
}
var f = new F('老王')(2)作为对象属性执行
var a = {
name: '老王',
fn: function() {
console.log(this.name)
}
}
a.fn() // this === a(3)作为普通函数执行 // windowfuntion f() {
console.log(this) // window
}
f()(3)call apply bind (call和apply 很相似只是传参方式不同)
function fn(name, age) {
console.log(name,age,this)
}
fn.call({x: 100}, '老王', 40) //老王 40 {x: 100}
fn.apply({x: 100}, ['老王', 40]) // 老王 40 {x: 100}
var fn2 = function (name, age) {
console.log(name,age,this)
}.bind({y: 200}) // 函数声明不能使用.bind 只能采用函数表达式方式
fn2('老王', 40) // 老王 40 {y: 200}
this要在执行是才能确认值,定义时无法确认
var a = {
name: '老王',
fn: function() {
console.log(this.name)
}
}
a.fn() // this === a
a.fn.call({name: '小明'}) // this === {name: 'B'}
var fn1 = a.fn fn1() // this === window- 作用域
(1)没有块级作用域 (ES6新增了块级作用域)
(2)函数和全局作用域
- 作用域链(一个函数的父级作用域为定义时的作用域而不是执行时的作用域)
var a = 1
function f1() {
var b = 2
function f2() {
var c =3
console.log(a) // a 是自由变量 1
console.log(b) // b 是自由变量 2
console.log(c) // 3
}
f2()
}
f1()- 闭包
(1) 函数作为返回值
function F1() {
var a = 100
return function () {
console.log(a)
}
}
var f1 = F1()
var a = 200
f1() // 100(2)函数作为参数传值
function F1() {
var a = 100
return function () {
console.log(a) // 自由变量父级作用域寻找
}
}
var f1 = F1()
function F2(fn) {
var a = 200
fn()
}
F2(f1)异步和单线程
常见题目:
- 同步和异步的区别是什么?分别举一个同步和异步的例子
- 一个关于setTimeout的笔试题
- 前端使用异步的场景有哪些
知识点:
- 什么是异步(对比同步)
同步会阻塞代码运行,异步不会
// 异步
console.log(100)
setTimeout(function() {
console.log(200)
}, 1000)
console.log(300)
// 100 300 200
// 同步
console.log(100)
alert(200) // 弹窗点击确认后才会执行下面的代码
console.log(300)
- 前端使用异步场景
(1)在可能发生等待的情况
(2)等待过程中不能像alert一样阻塞程序运行
(3)需要等待的情况下主要异步
具体使用场景:
(1)定时任务:setTimeout、setInverval
(2)网络请求: ajax请求、动态img加载
(3)事件绑定
- 异步和单线程
其他知识
常见题目:
- 获取2017-06-10格式的日期
- 获取随取数,要求长度一致的字符串格式
- 写一个能遍历对象和数组通用forEach函数
已知如下数组:
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组
// 第一种方法
Array.from(new Set(arr.flat(Infinity)).sort((a, b) => {
return a - b
}))
// 第二种方法
new Set(arr.toString.split(',')).sort((a, b) => { return a-b })知识点:
- 日期
Date.now() // 获取当前时间毫秒数 1970年
var dt = new Date()
dt.getTime() // 获取毫秒数
dt.getFullYear() // 年
dt.getMonth() // 月(0-11)
dt.getDate() // 日(0-31)
dt.getHours() // 小时(0-23)
dt.getMinutes() // 分鐘(0-59)
dt.getSeconds() // 秒(0-59)- Math
Math.random() // 获取随机数- 数组API
数组详细可见:juejin.cn/post/684490…
(1)forEach遍历所有元素
(2)every 判断所有元素是否都符合条件
(3)some 判断是否有至少一个元素符合条件
(4)sort 排序
(5)map 对元素重新组装,生成新数组
(5)filter 过滤符合条件的元素
- 对象API ( for in)
常说的JS(浏览器执行的JS)包含两部分:
(1)JS基础知识(ECMA标准)
(2)JS-Web-Api (W3C标准)
JS-Web-API
DOM操作:(Document Object Model)
常见题目:
- DOM是哪种基本的数据结构?(树)
- DOM操作的常用API有哪些?
- DOM节点的attr和property有何区别?
知识点:
- DOM的本质 (树)
DOM可以理解为:浏览器把拿到的html代码,结构化一个浏览器能识别并且js可操作的一个模型而已。
- DOM节点操作
(1)获取DOM节点
var dom1 = document.getElementById('#id') // 元素
var domList = document.getElementsByTagName('tagName') // 集合
var domList = document.getElementsByClassName('.className') // 集合
var domList =document.querySelectorAll('') // 集合(2)prototype(JS属性)
var pList = document.querySelectorAll('p')
var p = pList[0]
console.log(p.style) // 获取样式
p.style.width = '100px' console.log(p.className) // 获取class
p.className = 'p1' // 修改class
// 获取nodeName 和 nodeType
console.log(p.nodeName) console.log(p.nodeType)(3)Attribute(html标签属性)
var pList = document.querySelectorAll('p')
var p = pList[0]
p.getAttribute('data-name')
p.setAttribute('data-name', 'icon')
p.getAttribute('style')
p.setAttribute('style', 'font-size: 30px')- DOM结构操作
(1)新增节点
var div = document.getElementById('div')
// 添加新节点
var p1 = document.createElement('p')
p1.innerHTML = 'this is p1'
div.appendChild(p1)
// 移动已有节点
var p2 = document.getElementById('p2')
div.appendChild(p2)(2)获取父元素(parentElement)
(3)获取子元素(childNodes集合)
(4)删除节点(removeChild)
BOM操作:(Browser Object Model)
常见题目:
- 如何检测浏览器的类型(安卓、苹果)
- 拆解url的各部分
知识点:
- navigator
var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome') // 判断是否为谷歌浏览器 实际情况判断条件可能较复杂一些- screen (获取屏幕的相关数据)
console.log(screen.width)
console.log(screen.height)
- location
console.log(location.href) // 地址
console.log(location.protocol) // 协议
console.log(location.origin) // 域名
console.log(location.pathname) // 路径
console.log(location.search) // 问号后面
console.log(location.hash) // 井号#后面- history
history.back() // 后退
history.forward() // 前进事件:
常见题目:
- 编写一个通用的事件监听函数
- 描述事件冒泡流程
- 对于一个无限下拉加载图片的页面,如何给每个图片绑定事件
知识点:
- 通用事件绑定
var btn = document.getElementById('btn')
btn.addEventListener('click', function(event) {
console.log('clicked')
})
IE低版本使用attachEvent绑定事件,和w3c标准不一样
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
var a= document.getElementById('a')
bindEvent(a, 'click', function(e) {
e.preventDefault() // 阻止默认行为
alert('clicked')
})- 事件冒泡(点击事件触发后会一层一层的根据树形结构向上触发)
var p1 = document.getElementById('p1')
var body = document.body
bindEvent(p1, 'click', function(e) {
e.stopPropatation()
alert('激活')
})
bindEvent(body, 'click', function(e) {
alert('取消')
})- 代理
<div id="div1"><a href="#"></a> <a href="#"></a> <a href="#"></a> <a href="#"></a> <a href="#"></a> <!-- 还会有更多的a标签 --> </div>// 通过事件冒泡 并且通过target获取当前的点击标签
var div1 = document.getElementById('div1')
div1.addEventListener('click', function(e){
var target = e.target
if (target.node === 'A') {
alert(target.innerHTML)
}
})- 完善通用绑定事件的函数
function bindEvent(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(div1, 'click', 'a', function(e) { console.log(this.innerHTML) }) var a = document.getElementById('a1') bindEvent(div1, 'click',function(e) { console.log(a.innerHTML) })Ajax
常见题目:
- 手动编写一个ajax,不依赖第三方库
- 跨域的几种实现方式
知识点:
- XMLHttpRequest
IE低版本使用ActiveXObject, 与w3c不一样
var xhr = new XMLHttpRequest()
xhr.open("GET", "/api", false) // false 异步
xhr.onreadystatechange = function() {
// 这里的函数异步执行
if (xhr.readyState == 4) {
if (xhr.status == 200) {
console.log(xhr.responseText)
}
}
} xhr.send(null)- 状态码说明
(1)readyState
0=>(未初始化)还没有调用send()方法
1=>(载入)已调用send()方法,正在发送请求
2=>(载入完成)send()方法执行完成,已经接收到全部响应内容
3=> (交互)正在解析响应内容
4=>(完成)响应内容解析完成,可以在客户端调用了
(2)status
2XX=>表示成功处理请求。如200
3XX=>需要重定向,浏览器直接跳转
4XX=>客户端请求错误,如404
5XX=>服务端错误
- 跨域
(1)什么是跨域
浏览器有同源策略,不允许ajax访问其他域接口
跨域条件:协议、域名、端口(域名冒号后面的:http80/https443),有一个不同就算跨域
有三个标签允许跨域加载资源:img(可以用于打点统计,统计网站可能是其他域如:百度统计)/link(可以使用CDN、script也可以)/script(可以用于JSONP)
所有的跨域请求都必须经过信息提供方允许
(2) JSONP
<script> window.callBack = function(data) { // 这是我们跨域得到信息 console.log(data) } </script> <script src="{{跨域请求的地址}}"></script>(3)服务器端设置http header(另外一种解决跨域的简洁方法,需要服务器端来做)
存储
常见题目:
- 请描述一下cookie、sessionStorage和localStorage的区别?(容量、Ajax请求是否携带、使用性)
知识点:
- cookie
(1)本身用于客户端和服务端通信
(2)但是它有本地存储的功能,于是就被借用
(3)使用document.cookie = 获取和修改
(4)缺点:
- 存储量小,只有4KB
- 所有http请求都带着,会影响获取资源的效率
- API简单,需要才能用document.cookie = ...
- sessionStorage (会话储存)和localStorage(ios safari 隐藏模式下会报错建议使用try catch)
(1)HTML5专门为存储而设计,最大容量5M
(2)API简单易用:localStorage.setItem(key,value);localStorage.getItem(key)
运行环境
(1)浏览器可以通过访问链接来得到页面的内容
(2)通过绘制和渲染,显示出页面最终的样子
页面加载过程
常见题目:
- 从输入url到得到html的详细过程
- window.onload和DOMContentLoaded的区别
window.addEventListener('load', function () { // window.onload
// 页面的全部资源加载完才会执行,包括图片、视频等
})
window.addEventListener('DOMContentLoaded', function () {
// DOM 渲染完即可执行,此时图片、视频还可能没有加载完
})知识点:
- 加载资源的形式
- 输入url(或跳转页面)加载html
- 加载html的静态资源(js、css、图片)
- 加载一个资源的过程
- 浏览器根据DNS服务器得到域名的IP地址
- 向这个IP的机器发送http请求
- 服务器收到、处理并返回http请求
- 浏览器得到返回内容
- 浏览器渲染页面的过程
- 根据html结构生成DOM Tree
- 根据CSS生成CSSOM
- 将DOM和CSSOM整合形成RenderTree(比DOM Tree多了样式)
- 根据RenderTree开始渲染和展示
- 遇到script时,会执行并阻塞渲染(因为JS可以改变DOM结构)
思考:
为何要把css放在head中?为何把js放在body下面?
性能优化
原则:
- 多使用内存、缓存或者其他方法
- 减少CPU计算、减少网络请求(减少IO硬盘读取)
从哪些入手?
- 加载页面和静态资源
(1)静态资源的压缩合并
(2)静态资源缓存
(3)使用CDN让资源加载更快
(4)使用SSR即Server-Side Rendering(服务器端渲染)后端渲染,数据直接输出到HTML中
- 页面渲染
(1)css放前面,JS放后面
(2)懒加载(图片懒加载,下拉加载更多)
(3)减少DOM查询,对DOM查询做缓存
(4)减少DOM操作,多个操作尽量合并在一起执行
(5)事件节流
(6)尽早执行操作(如DOMContentLoaded)
示例:
(1)资源合并(webpack commonJs)使用构建工具
(2)缓存(浏览器缓存)
- 通过链接名称控制缓存
- 只有内容改变的时候,链接名称才会改变
(3)CDN
(4)使用SSR后端渲染(可以把数据直接输出到HTML中)
(5)懒加载
<img id="img1" src="preview.png" data-realsrc="abc.png" alt="">
<script>
var img1 = document.getElementById('img1')
img1.src = img1.getAttribute('data-realsrc')
</script>
(6)缓存DOM查询
for (var i = 0; i < document.getElementsByTagName('p').length; i++) {
// 查询了10次DOM
}
var pList = document.getElementsByTagName('p')
for (var i = 0; i < pList.length; i++) { // 查询了1次DOM }(7)合并DOM插入
var list = document.getElementById('list') // 要插入10个li标签 // 创建一个片段
var frag = document.createDocumentFragment()
var x, li for (x = 0; x<10; x++) {
li = document.createElement('li')
li.innerHTML = "list item" + x
frag.appendChild(li)
}// 最后操作插入一次DOM
list.appendChild(frag)(8)事件节流和防抖
安全性(不重要)
知识点:
- XSS跨站请求攻击
- XSRF跨站请求伪造