JS 有哪些数据类型?
7种数据类型 string
number
bool
undefined
null
object
symbol
object 包括了数组、函数、正则、日期等对象 一旦出现(数组、函数、正则、日期、NaN)直接0分
(必考)什么是立即执行函数?使用立即执行函数的目的是什么?
声明一个函数,不给他名字(也可以给)然后在后面加上括号,立即执行他,由于这个语法不对,他不能立即执行,所以修改一下语法,让他立即执行,比如用括号包起来,前面加一个!,或者~
;(function (){
var name
}())
//为了防止括号和前面的相作用,就添加一个分号,以示分离
;(function (){
var name
})()
!!!!!!!function (){
var name
}()
~function (){
var name
}()
复制代码
作用: 造出一个函数作用域,防止污染全局变量 侃侃而谈 ES 6 有一个新语法,可以直接声明局部变量,花括号{}就代替了立即执行函数。
{
let name
}
复制代码
JS 如何实现继承?
举例子写出来:(要理解下面每句话的意思)
- 原型链
function Animal(){
this.body = '肉体'
}
Animal.prototype.move = function(){
}
复制代码
下面写一个Human继承animal
function Human(name){
Animal.apply(this, arguments)//自身属性的继承
this.name = name
}
// Human.prototype.__proto__ = Animal.prototype // 非法,不被支持
//所以得生成一个空函数,下面这三句话就是上面这句话的意思
var f = function(){}
f.prototype = Animal.prototype
Human.prototype = new f()
//然后再写Human自己的方法。这个时候我们就说Human继承了animal
Human.prototype.useTools = function(){}
复制代码
var frank = new Human()
- extends 关键字
class Animal{
constructor(){
this.body = '肉体'
},
move(){}//这里就不实现了
}
class Human extends Animal{
constructor(name){
super()
this.name = name
},
useTools(){}
}
var frank = new Human()
复制代码
如果只记住下面这种没有问题,能记住原型链的更好,class语法知识语法糖。
移动端的触摸事件了解吗?
- 讲一下这几个事件:
touchstart touchmove touchend touchcancel
- 模拟
swipe
事件:记录两次touchmove
的位置差,如果后一次在前一次的右边,说明向右滑了。
JS
&&和||
&&
,他的返回值就是遇到的第一个falsy值,后面的不看了 1&&2&&3&&0&&2&&3
返回0,后面的不运行。 ||
,他返回的就是第一个遇到的true值,后面的不看 0||0||0||false||1||2||0
返回1,后面的不运行。
七个数据类型
七种数据类型分为六个基本类型和对象。 六个基本类型分别是null
,undefined
,string
,number
,boolean
,symbol
对象又分为object
,function
,array
null和undefined的区别
相同点:if里都是都是false值
不同点:
-
null
转换为数字的时候是0
,undefined
表示未定义转换为数字的时候是NaN
-
调用函数时,某个参数未设置任何值,这时就可以传入
null
,表示该参数为空。而传入undefined
就代表undefined
typeof
yuchengkai.cn/docs/zh/fro… typeof只需要记住两点。
- typeof 对于基本类型,除了
null
显示为object
,其余都可以显示正确的类型 - typeof 对于对象,除了函数为
function
都会显示object
- typeof运算符只能显示数组的类型是Object,而
Array.isArray(arr)
方法可以识别数组。
六个false值
除了下面六个值被转为false
,其他值都视为true
undefined
null
false
0
NaN
""或''(空字符串)
复制代码
NaN
NaN === NaN; // false
typeof NaN//number
复制代码
for in 和forEach
for in 遍历对象
for in循环注意点
for(var key in person){
console.log(person.key)
}
复制代码
注意:person.key
等于 person['key']
要用 这里遍历要用person[key]
for... in可能会随机遍历,不按照声明顺序
forEach遍历数组
var arr=[1,2,3]
arr.forEach(function(e){
console.log(e)
})
//1 2 3
复制代码
其他类型转换为String
null
和undefined
没有tostring()
方法。只能null+''
和undefined+''
- 其他的用
.toString()
方法。或直接+''
。或者window.String()
方法
其他类型转换为Number(主要是String)
五个方法
- 简便方法
'1234'-0
(常用) - .
parseInt()
//默认十进制 - 简便方法二
+'1'
取正
其他类型转换为Boolean(五个false值)
记住5个false值 null undefined NaN 0 ''
记住下面易错点:
[]//ture
{}//ture
' '//ture 里面有空格
复制代码
转换方法两个:
window.Boolean(xxx)
- 前面加上
!!xxx
内存
- 数据区(存的变量的数据)分为 Stack(栈内存) 和 Heap(堆内存)
- 简单类型的数据直接存在 Stack 里
- 复杂类型的数据是把 Heap 地址存在 Stack 里(对象包括:狭义对象(object),函数(function),数组(array))
关于内存的面试题
引用: 一个广义对象(object,array,function) 例如 var a= {xxx:'xxx'} a存的是{xxx:'xxx'}这个广义对象的地址,假如是10000。那么a(或a存的地址10000)就是这个对象({xxx:'xxx'})的引用。
var a = 1 var b = a b = 2 请问 a 显示是几? 2 1 (深拷贝)
var a = {name: 'a'}
var b = a
b = {name: 'b'}
复制代码
请问现在 a.name 是多少? 'a'
var a = {name: 'a'}
var b = a
b.name = 'b'
复制代码
请问现在 a.name 是多少? 'b'
var a = {name: 'a'}
var b = a
b = null
复制代码
请问现在 a 是什么? {name: 'a'}
GC垃圾回收
如果一个对象没有被引用,他就是垃圾,就会被回收(没有引用找不到他,所以要回收)
{name:'a'}
就是垃圾,就要被
回收,
释放内存
document.body.onclick
这个
引用在栈内存中存了fu这个函数的
地址
内存泄漏:在ie6的时候,如果关闭页面,一些垃圾是没有被清除,内存被永久的占用了
深拷贝
- 首先:值的存储方法:两种。基础类型六个存入stack****栈内存,复合类型对象存入heap****堆内存
所以:
- 对于六个基本数据类型来说,直接赋值就是深拷贝,因为他们直接存在栈内存里。
- 但是如果吧旧的对象赋值给新声明的对象,那么实际上只是把栈内存里的地址赋值给了新对象,实际上堆内存里仍然是原来的对象,如果修改新对象里面的属性,那么原来的对象也会改变,因为用的是同一块堆内存。直接给新对象赋值,无法实现拷贝。
如何实现对象的浅与深拷贝?
- 对象浅拷贝
- 浅拷贝 首先可以通过
Object.assign
来解决这个问题。
Object.assign()只拷贝第一层,如果属性存的是一个引用,那么他也只拷贝到引用。所以是浅拷贝let a = { age: 1 } let b = Object.assign({}, a) a.age = 2 console.log(b.age) // 1 复制代码
- 深拷贝 深拷贝 这个问题通常可以通过
JSON.parse(JSON.stringify(object))
来解决。
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
复制代码
但是该方法也是有局限性的:
- 会忽略
undefined
- 不能序列化函数
- 不能解决循环引用的对象 但是可以解决大部分问题。
作用域
JavaScript有函数级作用域,仅仅函数能创建新作用域。
变量的作用域表示这个变量存在的上下文。
setTimeout中的函数所处在于全局作用域中,所以函数中使用this
关键字时,这个this
关键字指向的是全局对象(window
)
作用域链
题目:
var a = 1
function f1(){
var a = 2
console.log(a)
f4()
}
function f4(){
console.log(a)
}
f1()
复制代码
答案,2,1 这个f4里面的a只能是他自己本身的作用域和他的父作用域,跟f1里面的a没有关系 先看面试题 题目1
var a = 1
function fn1(){
function fn2(){
console.log(a)
}
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
var fn = fn1()
fn() //2
复制代码
题目2
var a = 1
function fn1(){
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
function fn2(){
console.log(a)
}
var fn = fn1()
fn() //1
复制代码
题目3
var a = 1
function fn1(){
function fn3(){
function fn2(){
console.log(a)
}
var a
fn2()
a = 4
}
var a = 2
return fn3
}
var fn = fn1()
fn() //undefined
复制代码
解密
- 函数在执行的过程中,先从自己内部找变量
- 如果找不到,再从创建当前函数所在的作用域去找, 以此往上
- 注意找的是变量的当前的状态
变量提升
两个题目
var foo = 1;
function bar() {
if (!foo) {
var foo = 10;
}
alert(foo);
}
bar();
复制代码
答案是10
第二个例子
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
alert(a);
复制代码
alert输出了1
变量提升后的顺序 提升时代码表现上的排序为:
1 var a;
2 function f (){}
3 形参
4 this和arguments
复制代码
总结: var
声明变量,只会提升var a=undefined
(自动赋值为undefined
)。 function
声明函数,function f (){}
,所有的都会提升。
let与const暂时死区
{ console.log(x) // Uncaught ReferenceError: x is not defined let x = 1 } let x = 1之前到花括号{,就是x的暂时死区,不用使用变量x,const一样
instanceof
instanceof
运算符用来验证,一个对象是否为指定的构造函数的实例。obj instanceof Object
返回true
,就表示obj
对象是Object
的实例。
new
-
使用new命令时,它后面的函数依次执行下面的步骤。
- 创建一个空对象,作为将要返回的对象实例。
- 将这个空对象的原型,指向构造函数的prototype属性。
- 将这个空对象赋值给函数内部的this关键字。
- 开始执行构造函数内部的代码。
既:
- 新生成了一个对象(新生成一个临时对象)
- 链接到原型(新生成的对象
新对象.__proto__=构造函数.prototype
) - 绑定 this (
this = 新对象
) - 返回新对象 new应用举例:
第一步写私有属性,第二步写共有属性.
可以看到这个对象的
ES5的Object.create()
以现有的对象作为模板,生成新的实例对象,这时就可以使用Object.create()
方法。
var person1 = {
name: '张三',
age: 38,
greeting: function() {
console.log('Hi! I\'m ' + this.name + '.');
}
};
var person2 = Object.create(person1);
person2.name // 张三
person2.greeting() // Hi! I'm 张三.
复制代码
this,this的实质:
- 引擎会将函数单独保存在内存中,然后再将函数的地址赋值给变量。
- 由于函数是一个单独的值,所以它可以在不同的**环境(上下文)**执行。
- JavaScript 允许在函数体内部,引用当前环境的其他变量。
- 现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,
this
就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。
总结一句话:this就是指函数所在的当前环境(既所在的对象)
this:
- 在构造函数里,
this
代表新生成的实例对象 - 其他情况下,
this
就是属性或方法“当前”所在的对象。它总是返回一个对象。
面试回答:;
fn()
里面的this
就是window
(fn在最顶层)fn()
是strict mode
,this
就是undefined
(fn在最顶层,严格模式下是undefined)a.b.c.fn()
里面的this
就是a.b.c
(属性或方法当前所在的对象。)new F()
里面的this
就是新生成的实例() => console.log(this)
里面this
跟外面的this
的值一模一样(ES6新增语法)(箭头函数里,this
就上一层的this
的值) 面试答上面的几个↑
面试回答call() apply() bind():
1 call()方法,可以指定函数内部this
的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。 (这个所指定的作用域意思就是所指定的对象)
var obj = {};
var f = function () {
return this;
};
f() === window // true
f.call(obj) === obj // true
复制代码
call
后面的参数则是函数调用时所需的参数
2 apply()
与call()
唯一的区别就是,它接收一个数组作为函数执行时的参数。
x.call(obj,1,2,3)
x.apply(obj,[1,2,3])
复制代码
3 bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。 call
和apply
都是绑定新this
后直接执行函数,而bind
没有执行,而是返回新函数,这个在一函数作为参数的一些方法里就用bind
this面试题 带call的面试题遵循以下两个规则
-
如果call传了参数,this就是传的参数。
-
如果call没传参数或者为undefined,那么this是window(飞严格模式),或者undefined(严格模式)
带call的面试题仍然遵循规则 3.
记住了,当call()
的第一个参数是undefined
的时候, this
是 window
.
this面试题 this题目
答案: 调用B处的console.log().结果是options window(console.log()中console是全局window对象里的一个方法)
第二题:
答案:D Object
第三题:
答案:Object
JS 原型是什么?
举例
var a = [1,2,3]
- 只有0、1、2、length 4 个key
- 但是a可以
a.push(4) pop
这些方法 a.__proto__ === Array.prototype
(a是实例数组对象,Array是构造函数)push
函数 就是沿着a.__proto__
找到的,也就是Array.prototype.push
Array.prototype
还有很多方法,如join
、pop
、slice
、splice
、concat
Array.prototype
就是 a 的原型(proto)
聚完例子后用new对象举例,说给面试官听: 比若说
-
我们新创建一个构造函数
function Person() {} 复制代码
-
然后根据构造函数构造一个新对象
var person1 = new Person(); ``` 复制代码
-
每个函数都有一个
prototype
属性,这个构造函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型。 -
当我们给
Person
的prototype
的name
属性赋值为'Kevin'
Person.prototype.name = 'Kevin'; var person1 = new Person(); var person2 = new Person(); console.log(person1.name) // Kevin console.log(person2.name) // Kevin 复制代码
每一个新的实例对象对象都会从原型"继承"属性,实例对象拥有该原型的所有属性。
-
说白了,原型就是 构造函数 用来 构造 新实例 的 模板对象。
-
这就是原型。
什么是原型链?
先回答什么是原型。在上面,然后继续从__proto__
开始往下说。
说:
-
JavaScript对象除了 null 都具有的一个属性,叫
__proto__
,这个属性会指向该对象的原型对象。 -
当读取实例的属性时,如果找不到,就会通过
__proto__
查找原型中的属性,如果还查不到,就去找原型的原型。 -
例如
Person.prototype
这个原型的原型就是Object
这个构造函数的prototype
,既Object.prototype
这个原型对象。然后,Person.prototype.__proto__
就指向Object.prototype
这个原型。然后Object.prototype
原型是null
。 -
这些原型对象通过
__proto__
像链子一样连起来,就叫做原型链。 然后给面试官画:
链子上都画上__proto__
person1----->Person.prototype----->Object.prototype----->null
Array.prototype----->Object.prototype----->null
实例的.__proto__
指向构造函数. prototype
(原型对象) 面试题:
Number.prototype.__proto__ === Object.prototype
//true
String.prototype.__proto__ === Object.prototype
//true
Boolean.prototype.__proto__ === Object.prototype
//true
Array.prototype.__proto__ === Object.prototype
复制代码
//同理
Function.prototype.__proto__=== Object.prototype//true
复制代码
关于 Function只需记住下面: 构造函数的原型:
String.__proto__===Function.prototype//true
Number.__proto__===Function.prototype//true
Boolean.__proto__===Function.prototype//true
Object.__proto__ === Function.prototype//true
复制代码
特殊的:
Function.__proto__===Function.Prototype//true
复制代码
constructor
每一个构造函数的prototype属性都指向原型对象。 每一个原型对象的constructor属性都指向构造函数。 原型对象
console.log(Person === Person.prototype.constructor); // true
复制代码
Array注意点、伪数组
-
大BUG:
var a = Array(3)
一个参数,且参数为数字,那么久声明数组长度为3的空数组var a = Array(3,3)
两个参数以上的时候,里面的参数都是数组内部的值。 所以声明数组不要用new Array()的方法。 -
数组本质上是一个对象。
-
数组可以用for i和for in循环
-
伪数组:
- 伪数组实际是一个对象,他的
__proto__
指向Object.prototype
,没有pop
push
等方法 - 它里面的属性都是数字(内容是函数所传进来的参数)和一个
length
- JS里面只有一个伪数组
arguments
,代表函数里面所传入的所有的参数。 -
- 伪数组实际是一个对象,他的
-
forEach
[1,2,3].forEach(function(value,index){ console.log(value) console.log(index) }) 复制代码
-
sort&join&concat&map&filter&reduce segmentfault.com/a/119000001…
函数题目
函数声明的五种方式
注意其中一种方式:
- 函数的name
1具名函数
function f(x,y){
return x+y
}
f.name // 'f'
2匿名函数
var f
f = function(x,y){
return x+y
}
f.name // 'f'
3具名函数赋值
var f
f = function f2(x,y){ return x+y }
f.name // 'f2'
console.log(f2) // undefined
4window.Function
var f = new Function('x','y','return x+y')
f.name // "anonymous"
5箭头函数
var f = (x,y) => {
return x+y
}
f.name//"f"
复制代码
只要记住3、4两种特殊的情况就好。
- this
- 特例:当call()的第一个参数是undefined的时候, this 是 window.
- 当启用严格模式的时候,call 里的第一个参数是什么,this 就是什么
- 函数的调用栈:执行的时候函数进入栈,return的时候函数弹出栈
- Stack Overflow堆栈溢出,超出call stack 函数调用栈。
立即执行函数
作用:创建一个独立的作用域,避免变量污染
(function(){alert('我是匿名函数')} ()) // 用括号把整个表达式包起来
(function(){alert('我是匿名函数')}) () //用括号把函数包起来
//下面的都是执行这个表达式,而不管返回值是什么
!function(){alert('我是匿名函数')}()
复制代码
另一种创建独立作用域的方法是使用let
异步函数的经典题
闭包经典面试题
经典面试题,循环中使用闭包解决 var 定义函数的问题
for ( var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
首先因为 setTimeout 是个异步函数,所有会先把循环全部执行完毕,这时候 i 就是 6 了,所以会输出一堆 6。
解决办法两种,第一种使用闭包(创建的这个立即执行函数就是一个新块级作用域,并使用了外部的变量i,解析完之后,也不随着i的最终改变而改变)+立即执行函数
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}
第三种就是使用 let 定义 i 了
for ( let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
因为对于 let 来说,他会创建一个块级作用域,相当于刚才的闭包创建的块级作用域。
复制代码
===
用===
而不用==
。只需要注意两点
NaN ===NaN//false
- 所有即使是内容一样的对象
===
也都是false
。因为地址不一样。
轮播
-
高级轮播 // 第一步,循环给按钮添加点击事件 //第二步:添加定时器,定时出发,循环轮播,并且进入时暂停轮播 jsbin.com/funumujoqe/…
-
无缝轮播 说思路就行了,然后说我在工作的时候都不手写录播,主要是为了理解他的原理,然后用别人写好的轮播库swiper,这样工作中的BUG会少一点。
回调函数
callback 是一种特殊的函数,这个函数被作为参数传给另一个函数去调用。这样的函数就是回调函数。 举一个Callback(回调函数)的例子 ,例如:
$button.on('click', function(){})
div.addEventListener('click', function(){})
复制代码
click 后面的 function 是一个回调,因为「我」没有调用过这个函数,是浏览器在用户点击 button 时调用的。
再举一个使用回调函数的例子
function setClock(callBack){
console.log('1定一个闹钟,三秒钟之后响');
setTimeout(()=>{
console.log('2三秒到了,闹钟响了!');
callBack();
},3000)
}
function getUp(){
console.log('3闹钟已经响了,该起床了')
}
setClock(getUp);
复制代码
回调函数一般只和**异步操作(setTimeOut)**在一起才使用!!!
什么是异步
1单线程模式
JavaScript 只在一个线程上运行,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待。
2同步任务(synchronous)和异步任务(asynchronous)
同步任务主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。
异步任务不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果,或者setTimeOut到时间了(事件循环)),该任务(采用回调函数的形式)才会进入主线程执行
3任务队列和事件循环
JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。
首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。
异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列,也就是说,不会重新进入主线程,因为没有用回调函数指定下一步的操作。
JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。
问关于异步、主线程、事件循环(Event Loop)的时候:按照下面回答: 面试官问问题:js是单线程模式的,那么他是怎么实现异步操作的? 答:
- js里面的任务分为同步任务和异步任务
- 同步任务进入主线程一个一个得执行。异步任务进入任务队列,等同步任务执行完了之后,只有触发了某个条件,才把任务队列里面的任务放到主线程执行(比如ajax得到返回的数据,就开始执行回调函数,setTimeOut的时间到了,就执行回调函数)
- 异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。
- 同步任务执行完之后,引擎就一遍一遍得检查。JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。事件循环机制一遍一遍得检查,符合条件,就把异步任务放到主线程里面去执行(比如ajax返回的数据到了,setTimeOut里面的时间到了)
异步操作的方法: 1.回调函数 2.事件监听(触发条件,执行回调函数) 3.ES6:Promise
定时器
它们向任务队列添加定时任务。时间到就添加,然后事件循环就会扫到,扫到了就执行里面的回调函数。
Promise
什么是Promise? 举例: $.ajax().then(成功函数,失败函数)
作用:避免回调嵌套,使回调变的可控
ES6划入标准标准。Promise现在是js的内置对象。Promise 对象是 JavaScript 的异步操作的解决方案。Promise 可以让异步操作写起来,就像在写同步操作的流程,(链式使用,then之后可以继续then)而不必一层层地嵌套回调函数
写一个Promise异步操作解决方案
function dosomething(){
// Promise 接受函数作为参数两个参数,
// resolve: 异步事件成功时调用
// reject: 异步事件失败时调用
return new Promise((resolve, reject) => {
let result = 异步操作()
// 下面给出承诺,面对不同的结果,我会 执行 不同的解决方案
if (result === 'success')
resolve('成功数据')
else
reject('失败数据')
})
}
// 异步操作,模拟概率事件
function 异步操作() {
return Math.random() > 0.5 ? 'success' : 'fail'
}
// 你在dosomething
dosomething()
// 异步操作成功了,那么我们打印成功的数据
.then(res => console.log(res))
// 异步操作失败了,那么我们打印失败的数据
.catch(res => console.log(res))
复制代码
Sync:同步的意思 Async:异步的意思
MVC与模块化
传统模块化方法:
- 使用window
虽然不同变量,但是是同样的地址//module1.js !function(){ var person = window.person = { name:"frank", } //局部变量person和全局变量person用的是同一个地址 }.call() 复制代码
//module2.js !function(){ var person = person;//即var person = window.person; console.log(person); }.call(); 复制代码
- 使用闭包
//module1.js
!function(){
var person = {
name:"mataotao",
age:18,
};
window.mataotaoGrowUp = function(){
person.age+=1;
return person.age;
};
}.call();
复制代码
//module2.js
!function(){
var newAge = window.mataotaoGrowUp();
console.log(newAge);//19
}.call();
复制代码
用闭包的好处:
- 用来 隐藏数据细节 (不告诉你多少岁但是你可以让他涨一岁,隐藏了age 的细节和name)
- 可以用来 做访问控制 (只能访问到age,无法访问到name)
前端面试题(移动适配,闭包,this,HTTP状态吗,排序思路,页面加载,数组去重)
2 移动端是怎么做适配的?
2016年腾讯前端面试题: 移动端是怎么做适配的? 回答要点:
- meta viewport
- 媒体查询
- 动态 rem 方案
(可以参考我写的博客 CSS5:移动端页面(响应式) CSS9:动态 REM-手机专用的自适应方案) 答:
2.1做手机端页面首先要加上一个meta标签
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
复制代码
content="width=device-width
表示宽度等于设备宽度,意思就是不要将页面宽度变成980px,用设备宽度. user-scalable=no
表示用户不以缩放 initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0
初始缩放倍数,最大缩放倍数,最小缩放倍数,都是1.0,即不能缩放
2.2媒体查询
通过媒体查询,根据不同条件,使用不同的css样式。 例如:
<style>
@media (max-width: 800px){/*如果媒体满足0到800 之间,那么会应用这里面的样式*/
body{
background-color: red;
}
}
</style>
复制代码
2.3动态rem
因为手机需要兼容很多不同宽度的手机设备,所以将长度单位依赖于手机设备宽度,使用动态rem方案,那么就可以在不同手机上实现相同比例的页面缩放而不影响布局。 rem:root em,即<html>
的font-size
. 实现动态rem,主要需要下面两步: 1在<head>
标签里加上如下代码,让10rem等于页面宽度
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>动态REM</title>
<script></script>
</head>
复制代码
2使用sass自动将设计稿的px转换为rem 在scss文件里写这样一个函数:
@function px( $px ){
@return $px/$designWidth*10 + rem;
}
$designWidth : 640; // 640 是设计稿的宽度,你要根据设计稿的宽度填写。设计师的设计稿宽度需要统一
复制代码
就可以使用px()函数将像素转化为rem。
3 实现圆角矩形和阴影怎么做?
2017年腾讯前端实习面试题(二面): 用过CSS3吗? 实现圆角矩形和阴影怎么做? (搜索MDN border-radius) 答: 用过。例如阴影,圆角,动画,渐变和过渡 1.圆角: 简写属性border-radius
。例如 border-radius: 30px;
border-radius: 50%;
半径参数可以是长度单位,也可以是百分比单位。
也可以分别设置四个角
border-top-left-radius: 4px 2px;
border-top-right-radius: 3px 4px;
border-bottom-right-radius: 6px 2px;
border-bottom-left-radius: 3px 4px;
复制代码
半径参数可以是一个或两个,一个参数代表圆形圆角,两个参数是椭圆圆角
2.阴影:
语法: box-shadow:inset x-offset y-offset blur-radius spread-radius color
五个参数分别是:投影方式 X轴偏移量 Y轴偏移量 阴影模糊半径 阴影扩展半径 阴影颜色
4 什么是闭包,闭包的用途是什么?
出处同上(一面二面都问了): 什么是闭包,闭包的用途是什么? JavaScript高程P178 闭包的用途
答:
4.1什么是闭包
闭包是指有权访问另一个函数作用域中的变量的函数。 例如
function foo(){
var local = 1
function bar(){
local++
return local
}
return bar
}
var func = foo()
func()
复制代码
bar函数可以访问变量local,bar就是一个闭包。
4.2闭包的用途
- 模仿块级作用域
function A(num) { //核心代码 (funnction(){ for(var i = 0; i<num; i++) { num++; } })() //核心代码结束 console.log(i)//underfined } 复制代码
匿名自执行函数在内部形成了一个闭包,使i变量只有块级作用域。闭包的本质是函数,其实在这里闭包就是那个匿名函数,这个闭包可以得到函数A内部的活动变量,又能保证自己内部的变量在自执行后直接销毁。
- 存储变量 闭包的另一个特点是可以保存外部函数的变量,原理是基于javascript中函数作用域链的特点,内部函数保留了对外部函数的活动变量的引用,所以变量不会被释放
function B(){
var x = 100;
return {
function(){
return x
}
}
}
var m = B()//运行B函数,生成活动变量 x被m引用
复制代码
运行B函数,生成活动变量 x被m引用, 变量x不会被销毁。 运行B函数,返回值就是B内部的匿名函数,此时m引用了变量x,所以B执行后x不会被释放,利用这一点,我们可以把比较重要或者计算耗费很大的值存在x中,只需要第一次计算赋值后,就可以通过m函数引用x的值,不必重复计算,同时也不容易被修改。 3. 封装私有变量
function Person(){
var name = 'default';
this.getName:function(){
return name;
}
this.setName:function(value){
name = value;
}
}
console.log(Person.getName())//default
console.log(Person.setName('mike'))
console.log(Person.getName())//mike
复制代码
设置了两个闭包函数来操作Person函数内部的name变量,除了这两个函数,在外部无法再访问到name变量,name也就相当于是私有成员。
5 call、apply、bind 的用法分别是什么?
阮一峰的javascript教程--this 深入浅出 妙用Javascript中apply、call、bind
答:
如果在函数中包含多层的this,this的指向是不确定的。需要把this固定下来,避免出现意想不到的情况。JavaScript提供了call、apply、bind这三个方法,来切换/固定this的指向。
5.1Function.prototype.call()
函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。
var obj = {};
var f = function () {
return this;
};
f() === window // true
f.call(obj) === obj // true
复制代码
call的第一个参数就是this所要指向的那个对象,后面的参数则是函数调用时所需的参数。
5.2Function.prototype.apply()
apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数。
apply方法的第一个参数也是this所要指向的那个对象,如果设为null或undefined,则等同于指定全局对象。第二个参数则是一个数组,该数组的所有成员依次作为参数,传入原函数。原函数的参数,在call方法中必须一个个添加,但是在apply方法中,必须以数组形式添加。
function f(x, y){
console.log(x + y);
}
f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2
复制代码
5.3Function.prototype.bind()
bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。
bind方法的参数就是所要绑定this的对象。
var counter = {
count: 0,
inc: function () {
this.count++;
}
};
var func = counter.inc.bind(counter);
func();
counter.count // 1
复制代码
上面代码中,counter.inc方法被赋值给变量func。这时必须用bind方法将inc内部的this,绑定到counter,否则就会出错。
6 HTTP 状态码
出处同上: 请说出至少 8 个 HTTP 状态码,并描述各状态码的意义。
例如:
状态码 200 表示响应成功。
答:
状态码 202 表示:服务器已接受请求,但尚未处理。 状态码 204 表示:请求处理成功,但没有资源可返回。 状态码 206 表示:服务器已经成功处理了部分 GET 请求。
状态码 301 表示:请求的资源已被永久的分配了新的 URI。 状态码 302 表示:请求的资源临时的分配了新的 URI。
状态码 400 表示:请求报文中存在语法错误。 状态码 401 表示:发送的请求需要有通过 HTTP 认证的认证信息。 状态码 403 表示:对请求资源的访问被服务器拒绝了。 状态码 404 表示:服务器上无法找到请求的资源。
状态码 500 表示:服务器端在执行请求时发生了错误。 状态码 503 表示:服务器暂时处于超负债或正在进行停机维护,现在无法处理请求。
7 写出一个 HTTP 请求和响应的内容
出处同上: 请写出一个 HTTP post 请求的内容,包括四部分。 其中 第四部分的内容是 username=ff&password=123 第二部分必须含有 Content-Type 字段 请求的路径为 /path
看我的博客HTTP入门(一):在Bash中curl查看请求与响应
答: 请求:
1 POST /path HTTP/1.1
2 Host: www.baidu.com
2 User-Agent: curl/7.20.0 (x86_64-unknown-linux-gnu) libcurl/7.20.0 zlib/1.2.8
2 Accept: */*
2 Content-Length: 24
2 Content-Type: application/x-www-form-urlencoded
3
4 username=ff&password=123
复制代码
响应:
1 HTTP/1.1 200 OK
2Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
2Content-Length: 2443
2Content-Type: text/html(百度返回的时候百度的数据长度和内容的格式)
2Etag: "5886041d-98b"
2Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
3
4<!DOCTYPE html> ...
复制代码
1234567890
8 请说出至少三种排序的思路
这三种排序的时间复杂度分别为
O(n*n) O(n log2 n) O(n + max)
答:
O(n*n) 冒泡排序:遍历整个数组,依次比较相邻两个元素,将小的排在前面,大的排后面,这样一遍循环下来就可以将最大的元素排到最后,除去已经排过的最大的数,然后再次循环以上操作,直到最后一个为止。
O(n log2 n) 快速排序:以第一个元素为基准,比这个元素小的元素排在左边,比这个元素大的排右边,再以该元素左边和右边的第一个元素为基准,在子区间重复以上的操作,直到只有一个数字排序为止。
O(n + max) 基数排序:首先根据个位数的数值,将需要排序的一串数值分配到0-9的桶中。接着将这些桶中的数值重新串起来,形成新的数列。接着根据十位数、百位数直至最高位重复以上操作。
9 页面从输入URL到页面加载显示完成的过程
著名前端面试题:
一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么? 这一题是在挖掘你的知识边界,所以你知道多少就要答多少。
可以先查阅一些资料再查,但是不要把自己不懂的东西放在答案里,面试官会追问的。
知乎上:从输入 URL 到页面加载完成的过程中都发生了什么 答:
- DNS解析 DNS解析的过程就是浏览器查找域名对应的 IP 地址;
- TCP连接 浏览器根据 IP 地址向服务器发起 TCP 连接,与浏览器建立 TCP 三次握手: (1)主机向服务器发送一个建立连接的请求(您好,我想认识您); (2)服务器接到请求后发送同意连接的信号(好的,很高兴认识您); (3)主机接到同意连接的信号后,再次向服务器发送了确认信号(我也很高兴认识您),自此,主机与服务器两者建立了连接。
- 发送HTTP请求 浏览器根据 URL 内容生成 HTTP 请求报文。HTTP请求报文是由三部分组成: 请求行, 请求报头和请求正文,其中包含请求文件的位置、请求文件的方式等等。
- 服务器处理请求并返回HTTP报文
服务器接到请求后,回想客户端发送HTTP响应报文。HTTP响应报文也是由三部分组成: 状态码, 响应报头和响应报文。服务器会根据 HTTP 请求中的内容来决定如何获取相应的 HTML 文件,并将得到的 HTML 文件发送给浏览器。
-
浏览器解析渲染页面 浏览器是一个边解析边渲染的过程。在浏览器还没有完全接收 HTML 文件时便开始渲染、显示网页。在执行 HTML 中代码时,根据需要,浏览器会继续请求图片、CSS、JavsScript等文件,过程同请求 HTML 。
-
关闭TCP连接或继续保持连接
(1)主机向服务器发送一个断开连接的请求(不早了,我该走了);
(2)服务器接到请求后发送确认收到请求的信号(知道了);
(3)服务器向主机发送断开通知(我也该走了);
(4)主机接到断开通知后断开连接并反馈一个确认信号(嗯,好的),服务器收到确认信号后断开连接;
10 如何实现数组去重
著名面试题: 如何实现数组去重? 假设有数组 array = [1,5,2,3,4,2,3,1,3,4] 你要写一个函数 unique,使得 unique(array) 的值为 [1,5,2,3,4] 也就是把重复的值都去掉,只保留不重复的值。
要求:
不要做多重循环,只能遍历一次 请给出两种方案,一种能在 ES 5 环境中运行,一种能在 ES 6 环境中运行(提示 ES 6 环境多了一个 Set 对象) 从 JavaScript 数组去重谈性能优化 也谈JavaScript数组去重 答:
ES5: 思路:核心是构建了一个 hash 对象来替代 indexOf. 注意在 JavaScript 里,对象的键值只能是字符串,因此需要 var key = typeof(item) + item 来区分数值 1 和字符串 '1' 等情况。 只循环一遍
function unique(arr) {
var ret = []
var hash = {}
for (var i = 0; i < arr.length; i++) {
var item = arr[i]
var key = typeof(item) + item
if (hash[key] !== 1) {
ret.push(item)
hash[key] = 1
}
}
return ret
}
复制代码
ES6:ES2015引入了一种叫作Set的数据类型。顾名思义,Set就是集合的意思,它不允许重复元素出现。 如果重复添加同一个元素的话,Set中只会存在一个。包括NaN也是这样
function unique(array) {
return Array.from(new Set(array));
}
复制代码
JS题目总结:原型链/new/json/MVC/Promise
1原型链相关
第一个框: object是实例对象,他的模板对象(原型对象)在Object()构造函数里面. 构造函数.prototype
指向的是原型对象,即模板对象. 由构造函数构造出来的实例对象.__proto__
也指向的是原型对象,即模板对象. 所以true.
第二个框: fn是一个实例函数,是由用来构造出函数的构造函数造出来的. 所以fn.__proto__ === Function.prototype
任何构造函数.prototype
都是一个对象. 因为fn.__proto__ === Function.prototype
所以fn.__proto__.__proto__ === Object.prototype
等价于 Function.prototype.__proto__ === Object.prototype
等价于 一个对象.__proto__ === Object.prototype
所以是true
第三个框同理.
第四个框比较难理解: 一个实例函数是由用来构造出函数的构造函数造出来的.
Object,Function,Array都是一个实例函数,函数也是一种类型,就像String是一种类型,Number是一种类型一样,函数这个类型里的实例函数由函数的构造函数造出来!很难理解 所以实例函数.__proto__===构造函数.prototype
实例函数的构造函数就是Function
有点鸡生蛋蛋生鸡的感觉.
第五个框同理
2面向对象,new,原型链相关
function fn(){
console.log(this)
}
new fn()
复制代码
new fn()
会执行 fn
,并打印出 this
,请问这个 this
有哪些属性?这个 this
的原型有哪些属性? 答: 这个this
就是new
创建的新对象. this
(这个新对象)有__protot__
属性,它指向fn
构造函数的原型即fn.prototype
这个原型(即fn.prototype
)有两个属性:
construct
:它的值是构造函数fn
__proto__
: 它指向Object.prototype
解读:
fn()
是构造函数new fn()
就是一个构造函数new
出来的新对象. 他的自有属性为空,共有属性为空,因为都没有设置 因为他的自有属性为空,所以他只有一个__proto__
指向构造函数.prototype
(即原型)了. 共有属性为空,所以他的原型就是只有constructor
指向构造函数和__proto__
指向Object.prototype
(因为原型本身就是对象类型,所以指向对象的构造函数) 例子:
4 MVC
前端 MVC 是什么?(10分) 请用代码大概说明 MVC 三个对象分别有哪些重要属性和方法。(10分)
答一:
MVC 是什么 MVC 是一种设计模式(或者软件架构),把系统分为三层:Model数据、View视图和Controller控制器。 Model 数据管理,包括数据逻辑、数据请求、数据存储等功能。前端 Model 主要负责 AJAX 请求或者 LocalStorage 存储 View 负责用户界面,前端 View 主要负责 HTML 渲染。 Controller 负责处理 View 的事件,并更新 Model;也负责监听 Model 的变化,并更新 View,Controller 控制其他的所有流程。
答二: MVC就是把代码分为三块
V(view)只负责看得见的东西. M(model)只负责跟数据相关的操作,不会出现DOM,不会出现任何的html/css操作.例如model里只会有初始化数据库,获取数据方法fetch(),保存数据的方法save() C(controller)只负责把这些view和model组合起来,找到view,找到model,使用model完成数据修改业务,并修改view的显示 V:视图 M:数据 C:控制器
MVC是一种代码组织形式,不是任何一种框架,也不是任何一种技术,只是组织代码的思想,要做的就是V和M传给C,C去统筹 在js里,MVC分别由三个对象去担任三个职责
代码一:
window.View = function(xxx){
return document.querySelector(xxx);
}
复制代码
window.Model = function(object){
let resourceName = object.resourceName;
return {
init: function () {
},
fetch: function () {
},
save: function (object) {
}
}
}
复制代码
window.Controller = function(options){
var init = options.init;
let object = {
view:null,
model:null,
init:function(view,model){
this.view = view;
this.model = model;
this.model.init();
init.call(this,view,model);
this.bindEvents();
},
bindevnets:function(){},
};
for (let key in options) {
if(key !=='init'){
object[key] = options[key]
}
};
return object;
}
复制代码
代码二:
var model = {
data: null,
init(){}
fetch(){}
save(){}
update(){}
delete(){}
}
view = {
init() {}
template: '<h1>hi</h1'>
}
controller = {
view: null,
model: null,
init(view, model){
this.view = view
this.model = model
this.bindEvents()
}
render(){
this.view.querySelector('name').innerText = this.model.data.name
},
bindEvents(){}
}
复制代码
5 ES5类,原型链,构造函数,new
如何在 ES5 中如何用函数模拟一个类?(10分)
答一:
使用原型对象,构造函数,new来模拟类.
- 将公共属性放到原型对象里,并且将构造函数的
prototype
属性指向原型对象. - 私有属性(自有属性)放到构造函数里去定义.
- 将实例化的对象的
__proto__
指向原型对象. 这样当构造函数创建一个实例化的对象的时候,就即拥有自己的私有变量和方法,也有公有的变量和方法了,实例化出来的对象的私有方法和变量修改都不会互相有影响,只有在修改公有的变量和方法的时候是对所有实例生效的
答二: ES 5 没有 class 关键字,所以只能使用函数来模拟类。
function Human(name){
this.name = name
}
Human.prototype.run = function(){}
var person = new Human('frank')
复制代码
上面代码就是一个最简单的类,Human
构造函数创建出来的对象自身有 name
属性,其原型上面有一个 run
属性。
数组方法,快排,冒泡,数组去重
数组方法
Array.isArray() Array.isArray(arr) // true
2.实例方法 valueOf(),toString() arr.valueOf() // [1, 2, 3]返回本身 arr.toString() // "1,2,3,4,5,6"
push(),pop() shift()//删除第一个元素
join()//变成一个字符串
concat()//数组合并
reverse()//颠倒数组
slice(起始位置,终止位置)//[起始位置,终止位置)减下来一部分(一小片)数组,一小片,返回一个新数组,原数组不变
splice方法用于删除原数组的一部分成员,并可以在删除的位置添加新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组。(裁剪下来)
var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2) // ["e", "f"]
a // ["a", "b", "c", "d"]
复制代码
从下标4开始,往后删2个
sort()//按字典序排序,可以传入函数作为参数规定排序的方式。直接改变了原数组
遍历: map()//映射,并返回一个新数组,里面函数可以调整数组的值
forEach()//forEach方法不返回新数组,只用来遍历数据
filter()//过滤一个数组 [1, 2, 3, 4, 5].filter(function (elem) { return (elem > 3); }) // [4, 5]
indexOf()//indexOf方法返回给定元素在数组中第一次出现的位置. 如果没有出现则返回-1
String的方法
属性:length
方法: var s = new String('abc'); s.charAt(1) // "b" 这个方法完全可以用数组下标替代。 'abc'.charAt(1) // "b" 'abc'[1] // "b"
concat()
slice()减一小块下来,返回新字符串,[起始位置,结束位置) substring()一样,优先使用slice() substr()一样,优先使用slice()
indexOf() 用于确定一个字符串在另一个字符串中第一次出现的位置,返回结果是匹配开始的位置。如果返回-1,就表示不匹配
trim()去除字符串两端的空格,返回新字符串
toLowerCase() toUpperCase() 变换大小写
split()将字符串分割为数组 'a|b|c'.split('|') // ["a", "b", "c"],与jion相对应
Math
Math.floor(),Math.ceil()地板值,天花板值,相对于数轴的。
Math.round()四舍五入
Math.random() [0,1)的随机数
快排
let a = [1,1,1,2,2.2,12,14,23,443,5,5,435,45656768,4,6,7,5,8,33,67,86,2,86,67,845,4,24,5342,1,2]
function quickSort (arr){
if(arr.length<=1) {return arr}
// let midIndex = Math.floor(arr.length/2)
// let midItem = arr.splice(0,1)[0]
let midItem = arr[0]
let left = []
let right = []
for(i = 0;i<arr.length;i++){
if(i!==0){//跳过中间值
if(arr[i]<midItem){
left.push(arr[i])
}else{
right.push(arr[i])
}
}
}
return quickSort(left).concat([midItem],quickSort(right))
}
console.log(quickSort(a))
复制代码
数组去重
let a=[1,1,1,2,2,2,3,3,3,3,undefined,'xxx','xxx','3','3','undefined']
function f(array){
let hash = {}
let returnArr = []
for(let i=0;i<array.length;i++){
let arrItemStr = (typeof array[i]) + array[i] + ""//防止undefined和'undefined'转换成字符串然后冲突
if(hash[arrItemStr]!==1){//说明未进入hash
hash[arrItemStr]=1
returnArr.push(array[i])
}
}
// console.log(hash)
return returnArr
}
console.log(f(a))
//2:
console.log(Array.from(new Set(a)))
复制代码
ES6
了解let const 解构赋值 箭头函数 class promise