前端JavaScript面试技巧

一、JS基础知识

1.变量类型和计算

值类型

var a = 100 
var b = a
a = 200
console.log(b)  // 100

引用类型:(对象、组数、函数):

var a = {age: 20}
var b = a
b.age = 21
console.log(a.age)  // 21

特点:无限添加属性。设计原因:该三种类型可能较大,比较占用内存。

涉及问题:JS变量按照存储方式区分为哪些类型,并描述其特点

答案:值类型和引用类型;引用类型赋值会导致所有对该对象引用的值都发生变化。

 

typeof运算符

typeof undefined    // undefined
typeof 'abc'    // string
typeof 123    // number
typeof true    // boolean
typeof {}    // object
typeof []    // object
typeof null    // object
typeof console.log()    // function

注意“null”对象原型为object。

“typeof”只能识别值类型,但无法判断引用类型,但function例外。

 

字符串拼接

var a = 100 + 10    //110
var b = 100 + '10'  //10010

“==”运算符

100 == '100'    //true
0 == ''  //true
null == undefined    //true

若以上均换成“===”,结果均为false。

涉及问题:何时使用“===”,何时使用“==”

除了以下用法,其他情况均使用“===”:

if (obj.a == null) {
    // 这里相当于 obj.a === null || obj.a === undefined , 简写形式
    // 这是jQuery源码中推荐的写法
    // 大概原因是null是引用类型;undefined为值类型
}

if语句:(会对判断内容强转换)

var a = true
if (a) {}   //执行
var b = 100
if (b) {}   //执行
var c = ''
if (c) {}   //不执行

逻辑运算符:

console.log(10 && 0)   // 0
console.log('' || 'abc')   // 'abc'
console.log(!window.adb)   // true

var a = 100
console.log(!!a)   // true (!!a转换成Boolean类型)

 

问题:JS中有哪些内置函数 —— 数据封装对象。(都是创建特定类型的函数)

Object
Array
Boolean
Number
String
Function
Date
RegExp
Error

问题:如何理解JSON

答案:只是JS的对象。也是一个数据格式。

// JSON两个常用内置函数
JSON.stringifg({a:10, b:20})    //转化成字符串
JSON.parse('{a:10, b:20}')  //解释字符串为对象

 

2.原型和原型链

构造函数

function Foo(name, age) {
    this.name = name
    this.age = age
    this.class = 'class-1'
    // return this //默认有这行(因为创建对象的时候就是返回this)
}
var f = new Foo('zhangsan', 20)
// var f1 = new Foo('lisi', 21)

var a = {} 其实是 var a = new Object() 的语法糖。其他类型也是。

问题:如何判断一个变量是否为“数组”

答案:使用”a instanceof Object”得到布尔值。

 

关于 引用对象

1、所有的引用类型都具有对象特性,即可以自由扩展属性(“null”除外

var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {}
fn.a = 100;

2、所有的引用类型都有一个”__proto__属性(隐式原型),属性值是一个普通的对象

console.log(obj.__proto__);    // {}
console.log(arr.__proto__);    // []
console.log(fn.__proto__);    // [Function]

3、所有的函数都有一个”prototype属性(显示原型),属性值也是一个普通的对象

console.log(fn.prototype);    // undefined

4、“__proto__”属性值指向它的构造函数的“prototype”属性值

console.log(obj.__proto__ === Object.prototype) // true

5、当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找

// 构造函数
function Foo(name) {
    this.name = name
}
// 扩展 构造函数"FOO"的prototype的属性
Foo.prototype.alertName = function () {
    alert(this.name)
}
// 创建实例
var f = new Foo('zhangsan')
f.printName = function () {
    console.log(this.name)
}
// 测试
f.printName()   // 执行console.log
f.alertName()   // 执行alert

(针对上实例)循环对象自身的属性:

var item
for (item in f) {
    // 高级浏览器已经在 for in 中屏蔽了来自原型的属性
    // 但为了保证程序的健壮性,还是建议使用 .hasOwnPrototype() 判断是否属于自身属性
    if (f.hasOwnPrototype(item)) {
        console.log(item)
    }
}

简单的原型链例子:

// 构造函数
function Foo(name) {
    this.name = name
}
// 扩展构造函数"FOO"的prototype的属性
Foo.prototype.alertName = function () {
    alert(this.name)
}
// 创建实例
var f = new Foo('zhangsan')
f.printName = function () {
    console.log(this.name)
}
// 测试
f.printName()   // 执行console.log
f.alertName()   // 执行alert
f.toString()    // 要去f.__proto__.__proto__ 中查找

上实例的逻辑如下图:

前端JavaScript面试技巧_第1张图片

即,子类的__proto__指向父类的prototype。

给子类的__proto__添加新属性/方法,其他同属兄弟子类均可用该属性/方法;

给父类的prototype添加新属性/方法,其子类均可用该属性/方法。

 

问题:如何准确判断一个变量时数组类型

var arr = []
arr instanceof Array    // true

问题:写一个原型链继承的例子

function Ele (id) {
    this.ele = document.getElementById(id)
}
Ele.prototype.html = function (val) {
    var ele = this.ele  // 获取上级的ele
    if (val) {
        ele.innerHTML = val
    }
    return this     // 链式操作
}
Ele.prototype.on = function (e, fn) {
    var ele = this.ele
    if (!!e && !!fn) {
        ele.addEventListener(e, fn)
    }
    return this
}
var div1 = new Ele('test')
div1.html('这是修改后的内容').on('click', function () {
    alert('触发单击事件')
})

问题:描述new一个对象的过程

答案:  1、创建一个新对象

             2、this指向这个新对象

             3、执行代码,即对this赋值

             4、返回this

 

问题:zepto(或其他框架)源码中如何使用原型链

答案:

 

3.作用域和闭包

“this”

“this”要在执行时才能确认值,定义时无法确认。

var obj = {
    name: 'A',
    fn: function () {
        console.log(this.name)
    }
}
obj.fn()    // this === obj
obj.fn.call({name:'B'}) // this === {name:'B'}
var fn1 = obj.fn()
fn1()   // this === window

“var”定义变量,无块级作用域:

// 无块级作用域
if (ture) {
    var name = 'zhangsan'
}
console.log(name)   // zhangsan

闭包:

闭包必要条件

1、内部函数应访问其外部函数声明的私有变量、参数或其他函数内部函数。

2、外部函数“return”该内部函数

3、定义与该外部函数同级的变量,把该外部函数赋值给它。

function fn (x) {
    var a = x
    var b = function() {
        return a    // 访问外部变量a
    }
    a++
    return b	// 需要把闭包体抛出
}
var c = fn(10)
console.log(c)    // 11
// 无闭包的情况
function fn1 (x) {
    var a1 = x
    var b1 = a1
    a1++
    return b1
}
var c1 = fn1(10)
console.log(c1)  // 10

问题:创建5个标签,点击弹出对应的序号

var fragment = document.createDocumentFragment()
// 若使用'let'定义i,可以不使用闭包
for (var i = 0; i < 10; i++) {
    (function (i) {
        var a = document.createElement('a')
        a.innerHTML = i + 1 + '
' a.addEventListener('click', function(e) { e.preventDefault() // 取消事件的默认行为 alert(i + 1) }) fragment .appendChild(a) }) (i) // 接收 i 并把 i 作为私有变量 } document.body.appendChild(fragment)

问题:实际开发中闭包的应用。(封装变量,收敛权限)

// 闭包实际应用中主要用于 封装变量,收敛权限
function isFristLoad () {
    var _list = []
    return function (id) {
        if (_list.indexOf(id) > -1) {
            return false
        } else {
            _list.push(id)
            return true
        }
    }
}
// 使用
var fl = isFristLoad()
fl(10)  //true
fl(10)  //false
fl(20)  //true

 

4.异步和单线程

JS是 异步 单线程

前端使用异步的场景:

1、定时任务:setTimeout、setInterval

2、网络请求:Ajax请求、动态加载

3、事件绑定

 

问题:同步和异步的区别是什么

答案:同步会阻塞代码执行,而异步不会。(alert和setTimeout)

 

问题:一个关于setTimeout的笔试题

console.log(1)
setTimeout(() => {
    console.log(2)
}, 0);
setTimeout(() => {
    console.log(3)
}, 1000);
console.log(4)

答案:1 → 4 → 2 → 3 。

 

5.其他知识点

日期

var dt = new Date()
dt.getTime()    // 获取毫秒数,与Date.now()一样
dt.getFullYear()    //年
dt.getMonth() + 1   // 月
dt.getDate()    // 日
dt.getHours()   // 小时
dt.getMinutes() // 分
dt.getSeconds() // 秒

涉及 问题:获取 2017-06-10格式的日期

思路:主要涉及判断“月”和“日”小于10需要拼接“0”。

 

“Math”方法:

// 获取随机数 (前端多用于判断清除缓存)
Math.random()   // 0到1之间的随机数

涉及问题:获取随机数,要求是长度一致的字符串格式

思路:拼接数个“0”,再用” .slice(0, n) ”得到指定长度。

 

数组API

var arr = [1, 4, 2, 3, 5]

forEach 遍历所有元素

arr.forEach((item, index) => {
    console.log(index, item)
})

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

var result = arr.every((item, index) => {
    if (item < 5) {
        return ture
    }
})
console.log(result) // false

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

var result1 = arr.some((item, index) => {
    if (item < 5) {
        return true
    }
}) 
console.log(result1)    // true

sort 排序

var arr2 = arr.sort((a, b) => {
    return a-b  // 升序
    // return b-a  // 降序
})
console.log(arr2)   // [1, 2, 3, 4, 5]

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

var arr3 = arr.map((item, indxe) => {
    return '' + item + ''
})
console.log(arr3)   // 每个元素都包裹了b标签

filter 过滤符合条件的元素,生成新数组

var arr4 = arr.filter((item, index) => {
    if (item > 2) {
        return true
    }
})
console.log(arr4)   // [4, 3, 5]

 

对象API

只有一个for…in循环:

var obj = {a:100, b:200, c:300}
for (var key in obj) {
    if (obj.hasOwnPrototype(key)) {
        console.log(key, obj[key])
    }
}

涉及问题:写一个能遍历对象和数组的通用forEach函数

思路:自定义函数内先用 instanceof 判断类型,入口参数为对象+回调函数,再用forEach/for遍历。

 

 

二、JS-Web-API

常说的JS(浏览器执行的JS)包含两部分:

       1、JS基础知识(ECMA262标准

       2、JS-Web-API(W3C标准

1.DOM

数据结构:树状结构。       HTML代码本质是字符串。

DOM可以理解为:

浏览器把拿到的HTML代码结构化一个浏览器能识别并且JS可操作的一个模型。

获取DOM

var ele1 = document.getElementById('id')  // 元素
var ele2 = document.getElementsByTagName('div')  // 集合
var ele3 = document.getElementsByClassName('class')  // 集合
// .querySelectorAll入口参数为CSS选择器的字符串
var ele4 = document.querySelectorAll('div.note, div.alert')  // 集合

property:(JS对象的属性)

ele1.style.width = '100px'  // 修改样式
ele1.classNmae = 'ele1'     // 设置自定义属性

attribute:(DOM元素的属性)

ele1.getAttribute('style')
ele1.setAttribute('style', 'font-size:12px')

涉及问题:DOM节点的attribute和property有何区别

答案:property只是一个JS对象的属性修改/删除;attribute是对html标签属性的修改/删除。

 

新增节点

var div1 = document.getElementById('div1')
// 添加新节点
var p1 = document.createElement('p')
p1.innerHTML = 'this is new p'
div1.appendChild(p1)    // 添加新创建的元素
// 移动已有节点(原节点被删除)
var p2 = document.getElementById('p2')
div1.appendChild(p2)

获取父元素/子节点:(.parentElement 和 .childNodes)

var div2 = document.getElementById('div2')
var parent = div2.parentElement
var child = div2.childNodes     // 集合(nodeList)
/// 加入子元素中含有空格/回车之类的字符,得到的集合会多若干个text的子元素
// 解决办法:使用nodeType判断子节点的类型
console.log(child[0].nodeType)  // text或其他元素类型

删除节点

div2.removeChild(child[0])      // 删除第一个子元素

问题:DOM操作的常用API有哪些

答案:  1、获取DOM节点/属性:.getElementById(还有class和tag)、.querySelectorAll、.getAttribute

              2、获取父元素/子节点:.parentElements、.childNodes

              3、新增/删除节点:.appendChild()、.removeChild()

 

2.BOM

// navigator (用于判断浏览器)
var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome')
// screen
console.log(screen.width)
console.log(screen.height)
// location
console.log(location.protocol)  // "https:"
console.log(location.host)  // "www.baidu.com"
console.log(location.pathname)  // "/s"
console.log(location.search)  // "?wd=abc&..."
console.log(location.hash)  // "#mid=100" 用于定位某元素位置
// location.href 可以赋值新地址
console.log(location.href)  // "https://www.baidu.com/s?wd=abc...#mid=100"
// history
history.back()  // 返回
history.forward()   // 前进

 

3.事件

自定义通用事件绑定的函数

var elem = document.getElementById('id')
function bindEvent(elem, type, fn) {
    elem.addEventListener(type, fn)
}

事件冒泡


    

激活

取消

激活

取消

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


问题:简述事件冒泡流程

答案:沿着DOM树往上依次触发该事件。

 

4.Ajax

XMLHtmlRequest:(必须能默写

// 创建XMLHttpRequest对象
var xhr = new XMLHttpRequest()
// 使用xhr的open方法,第三个参数的(默认值)true表示使用 异步
// 只有异步才会调用onreadystatechange指定的函数
xhr.open('GET',  '/api', true)
// 使用xhr的监听事件onreadystatechange
xhr.onreadystatechange = function () {
    // readyState每次改变都会调用onreadystatechange的方法
    // "readyState = 4"表示内容解析完成,可在客户端调用
    if (xhr.readyState = 4) {
        // responseText表示响应内容,2xx:成功;3xx:重定向;4xx:请求错误;5xx:服务器错误
        if (xhr.responseText = 200) {
            alert(xhr.responseText)
        }
    }
}
// xhr发送的内容,没有可写null
xhr.send(null)

 

跨域

浏览器有同源策略,不允许Ajax访问其他域接口。

跨域条件:协议、域名、端口、有一个不同算跨域。

可跨域的三个标签:<img src=””>、<link href=””>、<script src=”” >。

 

JSONP

定义:利用

 

5.存储

cookie

本身用于客户端和服务端通信。但它有本地存储的功能,于是就被“借用”。

使用document.cookie = ...... 获取和修改。

缺点:

1、存储量太少,只有4KB;

2、所有http请求都带着,会影响获取资源的效率;

3、API简单,需要封装太能用document.cookie = ...... 。

 

localStorage和sessionStorage

优点:

       1、HTML5专门为存储而涉及,最大容量5M;

       2、API简单易用:localStorage.getItem(key, value);localStorage.setItem(key)

sessionStorage与localStorage使用方法一样,唯一区别在于重启浏览器后会删除sessionStorage

注意事项

iOS safari的隐藏模式下使用localStorage.getItem会报错,建议统一使用try-catch封装。

 

问题:请描述以下cookie、sesionStorage、localStorage的区别

答案:  1、容量:cookie只有4k;其余两者有5M。

              2、是否携带到Ajax中:cookie每次都会;其余两者不会。

              3、API易用性:cookie需要自己封装数据成字符串;其余两者有.getItem和.setItem。

 

 

三、关于开发环境

1.IDE

webstorm(收费)
sublime
VScode
atom(git hub的开源软件)
基本都依赖插件。

 

2.Git

常用git命令

git status                      // 获取当前git的状态(可看到处于什么分支,哪些文件被修改)
git add .                       // 把所有修改的文件上传(“.”改为文件名可单独上传,多个文件用空格隔开)
git checkout xxx           // 以线上文件还原“xxx”文件
git commit -m “xxx”     // 把需要上传的文件提交到本地缓存,-m “xxx”表示上传时的预留信息
git push origin master  // 把缓存在本地的文件推到git服务器(忽略origin master可以操作当前分支)
git pull origin master    // 把git服务器的文件拉到本地(忽略origin master可以操作当前分支)
git branch                    // 获取所有分支名
git checkout -b xxx      // 切换到“xxx”分支(“-b”可以省略),不存在则自动创建
git merge xxx               // 把”xxx”分支内容合并到本分支
git clone                      // 用于无项目代码的电脑下载代码

 

3.模块化

① AMD(规范)

定义:全称是Asynchronous Module Definition,即异步模块加载机制。

工具:require.js

仅有2个API:define()require()。第一个参数为依赖文件(无则不填);第二个参数模块的实现函数。

// 【index.html】 require.js为插件,data-main为引入模块主入口文件


// 【unit.js】
define(function () {
    // 定义模块,格式:model = { function(){} }
    var getFormaDate = {
        function (date, type) {
            if (type === 1) {
                return '2018-10-03' // 实际需要处理date
            } else {
                return '2018年10月03日'
            }
        }
    }
    return getFormatDate    // 抛出模块对象
})
// 【a-unit.js】
define(['./unit.js'], function (unit) {
    var aGetFormatdate = {
        function (date) {
            return unit.getFormatDate(date, 1)
        }
    }
    return aGetFormatdate
})
// 【main.js】
var dt = new Date()
require(['./aUnit.js'], function (aUnit) {
    console.log(aUnit.aGetFormatdate(dt))
})

② CommonJS

Nodejs模块化的规范,一般和npm一起使用,但不会异步加载JS。

与AMD类似,只有两个API:exports和require。

// 【unit.js】
module.exprots = {
    getFormatDate: function (date, type) {
        if (type === 1) {
            return '2018-10-03' // 实际需要处理date
        } else {
            return '2018年10月03日'
        }
    }
}
// 【a-unit.js】
var unit = require('./unit.js')
module.exprots = {
    aGetFormatdate: function (date) {
        return unit.getFormatDate(date, 2)
    }
}

③ 回滚流程要点

1、将当前服务器的代码打包并记录版本号,备份
2、将备份的上一个版本号解压,覆盖到线上服务器,并生成新的版本号

 

④ Linux基本命令

ssh name@server         //“name”为登陆账号名,“server”为服务地址,回车后会提示输入密码
mkdir xxx                     // 创建文件夹xxx
ls/ll                              // 打印当前目录下的所有文件,“ll“以列形式显示
cd xxx                          // 定位路径到xxx
rm -rf xxx                     // 删除xxx文件
vi xxx                           // 进入vi编辑器。“i“插入模式;”esc“命令模式;”:“底行模式。 “:wq“保存并退出。
mv xxx newPath/xxx     // 移动xxx文件到newPath下并命名为xxx
cat xxx                         // 查看xxx文件的内容
head -n 2 xxx               // 查看xxx文件的头2行,“-n 2“可省略
tail -n 2 xxx                  // 查看xxx文件的尾2行,“-n 2“可省略
grep ‘a’ xxx                 // 搜索xxx文件内包含“a“的内容

 

四、运行环境

1.页面加载

加载资源的形式

输入url:http://www.baidu.com

加载html中的静态资源:

缓存DOM查询:( 用变量存放常用DOM对象 )

var i
// 未缓存 DOM 查询
for (i=0; i < document.getElementById('id').length; i++) {
    // todo
}
// 缓存了 DOM 查询
var id = document.getElementById('id')
for (i = 0; i < id.length; i++) {
    // todo
}

合并DOM插入:( 利用document.createDocumentFragment()

var listNode = document.getElementById('list')
// 存放 待插入的10个li标签
var fragment = document.createDocumentFragment()
var i, li
for (i = 0; i < 10; i++) {
    li = document.createElement('li')
    li.innerHTML = 'num:' + i
    fragment.appendChild(li)
}
// 一次性插入所有li标签
listNode.appendChild(fragment)

事件节流:( 利用setTimeout() )

var textarea = document.getElementById('text')
var timer   // 存放定时器对象
textarea.addEventListener('keyup', function () {
    if (timer) {
        clearTimeout(timer)
    }
    timer = setTimeout(() => {
        // 触发 change 事件
    }, 100)
})

 

3.安全性

XSS

解决办法:用转义字符替换关键字。

 

XSRF

解决办法:增加验证流程,如指纹、密码、短信码等。

 

你可能感兴趣的:(JavaScirpt)