1、原始类型有哪几种?对象类型和原始类型有什么区别?null是对象吗?
6个原始类型,原始类型存储的是值,没有函数可以调用的,string类型比较特殊,如 :('test').toString(),执行该方法时,'test'会被强制转换成String类型(对象),从而可以调用对应的方法 number string null undefined symbol boolean
对象类型存储的是指针,而原始类型存储的是值,当创建一个对象的时候,内存中会开辟一个空间来存放值,通过指针指向该内存空间。
2、如何准确判断数据类型?typeof、constructor、instanceof、Object.prototype.toString.call()
1、使用typeof进行判断
typeof 除了null,判断原始类型是准确的,其他类型判断都为object(除了function)
typeof 'test' > "string"
typeof 1 > "number"
typeof undefined > "undefined"
typeof true > "boolean"
typeof Symbol() > "symbol"
typeof null > "object"
typeof [] > "object"
typeof {} > "object"
typeof function aaa(){} > "function"
2、使用constructor进行判断
('test').constructor.name > "String"
(2).constructor.name > "Number"
(true).constructor.name > "Boolean"
([]).constructor.name > "Array"
({}).constructor.name > "Object"
(function aa(){}).constructor.name > "Function"
constructor判断类型,看似完美,但也存在缺陷,因为对象的constructor是可以更改的,更改后,判断就不可靠了
3、使用instanceof
(1) instanceof Number > false
('test') instanceof String > false
({}) instanceof Object > true
([]) instanceof Object > true
([]) instanceof Array > true
(function aaa(){}) instanceof Function > true
可以看出instanceof只能准确判断出对象和函数类型,其他就不能精准判断了。
4、Object.prototype.toString.call()
以上三种结合起来可以判断数据类型,这里提供一种终极方案。使用Object对象的原型方法toString
var test = Object.prototype.toString
test.call(2) > "[object Number]"
test.call('test') > "[object String]"
test.call(null) > "[object Null]"
test.call(undefined) > "[object Undefined]"
test.call([]) > "[object Array]"
test.call({}) > "[object Object]"
test.call(function(){}) > "[object Function]"
test.call(Symbol()) > "[object Symbol]"
3、JS原型和原型链
什么是原型?
任何对象都有一个原型对象,原型对象,由对象的内置属性_proto_指向它的构造函数的prototype对象,任何对象都是由一个构造函数创建的,不是每个对象都有prototype属性,只有方法才有。
什么是原型链?
原型链的核心是依赖_proto_的指向,当自身不存在属性时,就一层一层的去查找创建对象的构造函数,直到查找到Object时,就没有_proto_指向了。 以下为简单的原型链分析
function Person(name){
this.name = name;
}
var p = new Person('hanhan');
/*
new的过程分三步
(1) var p = {};--初始化对象p
(2) p.__proto__ = Person.prototype;--将对象p的__proto__设置为Person的prototype
(3)Person.call(p,'hanhan');--调用构造函数Person来初始化p
*/
/*
p.__proto__ === Person.prototype > true
Person.prototype.__proto__ === Object.prototype > true
Object.prototype.__proto__ > null
*/
//由此可见,原型链的顶端是Object,最终__proto__指向null
4、闭包
什么是闭包?
闭包是函数和声明该函数的词法环境的组合。如:函数A内部中存在函数B,函数B可以访问函数A中的变量,这就是闭包
function A{
let age =18;
window.B = function () {
console.log(age);
}
}
B(); //打印:18
/*
有个需求,延迟打印循环的值,代码如下
*/
for(var i = 0;i<10;i++){
setTimeout(function(){
console.log(i);
},0);
}
//打印结果为10个10,显然不符合需求,因为setTimeout为异步函数,执行的时候i已经变成10,可以使用闭包解决这问题
for(var i = 0;i<10;i++){
(function(value){
setTimeout(function(){
console.log(value);
},0);
})(i);
}
闭包有哪些作用?
主要有两点用处:
1、可以读取函数内部的变量
2、让这些变量的值始终保持在内存中
注意点:
1、使用闭包时,由于变量会被保存在内存中,内存消耗大,所以不能滥用。IE浏览器中可能会导致内存泄露,所以在退出函数之前,将不适用的局部变量进行删除。
5、浅拷贝和深拷贝
浅拷贝只会拷贝所有属性值到新的对象,如果属性值包含对象的话,那么拷贝的是其地址,浅拷贝可以使用Object.assgin方法,还可以通过扩展运算符来实现浅拷贝,如:let b = {...a};
在很多情况下,我们需要用到深拷贝,如何实现深拷贝呢?
1、jQuery中$.extend可以实现深拷贝
2、JSON.parse(JSON.stringify(obj))--该方法会忽略undefined、symbol,不能序列化对象
3、手动实现一个简单的深拷贝
/**
* 实现深拷贝
* @param {被深拷贝对象} obj
*/
function deepClone(obj){
//判断是否为对象,除null外
function isObj(o){
return (typeof o === 'object' || typeof o === 'function') && o !==null
}
if(!isObj(obj)){
throw new Error('传入的值非对象');
}
//判断是否是数组
let isArray = Array.isArray(obj);
let newObj = isArray ? [...obj] : {...obj}
Reflect.ownKeys(newObj).forEach(key=>{
//判断属性对应的值是否为对象,如果是对象的话就进行递归clone
newObj[key] = isObj(obj[key]) ? deepClone(obj[key]) : obj[key]
});
return newObj;
}
6、继承
es6中新增了class,用此来实现继承很方便
/**
* 继承
* class只是语法糖,本质还是函数
*/
//父类
class Parent{
// 构造函数,在new的时候执行
constructor(value){
this.val = value;
}
getValue(){
console.log(this.val);
}
}
//子类
class Child extends Parent{
constructor(value,name){
super(value)
this.name = name;
}
sayHello(){
console.log(this.name);
}
}
var aaa = new Child('18','piao');
7、ES6新特性
1:let、const
2、Proxy
new Proxy(aaa,{
get(target, property, receiver){
console.log('获取值');
return Reflect.get(target, property, receiver)
},
})
3、map 生成新数组,遍历原数组,进行变换放入到新的数组
[1,2,3,4].map(v=>v*v) // ->[1,4,9,16]
4、filter 生成新数组,遍历数组,将返回值为true的元素放入新的数组
[1,2,3,4].filter(val=>val>=3) // ->[3,4]
5、reduce 可以将数组中元素,通过回调函数最终转换成一个值,如实现数组元素求和,第二个参数代表初始值
[1,2,3,4].reduce((acc,current)=>acc+current,0) // ->10
8、Event Loop(事件循环)
进程和线程:在应用上来说进程代表的就是一个程序,线程是进程中更小的单位,指执行一段指令所需时间。
执行栈:可以认为是存储函数调用的栈结构,先进后出,对于异步代码,会被挂起并在需要执行的时候加入到Task队列中,一旦执行栈为空,Event Loop就会从Task队列中拿出需要执行的代码,并放入执行栈中执行,所以他的本质还是同步行为。
9、call、apply、bind
call、apply是为了改变某个函数运行时的上下文而存在的,也就是改变函数内部的this指向。
call、apply的功能完全一样,只是参数的方式不一样,如下:
func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])
bind()最简单的用法就是创建一个函数,使这个函数不论怎么调用都有同样的this值
10、事件
事件:就是文档或浏览器窗口发生的一些特点交互的瞬间
事件流:(IE)事件冒泡、(Netscaoe)事件捕获
事件冒泡:事件开始由最具体的元素接收,然后逐级向上传播较为不具体的节点。
事件捕获:由不太具体的节点接收事件,然后再一级一级向下到具体的节点。
事件代理:如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话,应该注册到父节点上。优点: (1)节省内存 (2)不需要给子节点注销事件
11、跨域
出于安全考虑,浏览器有同源策略,如果协议、域名、端口号有一个不同就是跨域。主要用于阻止CSRF攻击。解决跨域问题如下:
1、JSONP
利用script标签没有跨域限制的漏洞,通过标签指向一个需要访问的地址,并提供一个回调函数来接收数据。JSONP只是适用于GET请求
<script src="//xxx.com/api?param=xx&callback=jsonp"></script>
function jsonp(data){
console.log(data);
}
2、CORS 服务端设置Access-Control-Allow-Origin 就可以开启 CORS,该属性标识哪些域名可以访问资源,如果设置通配符,表示所有网站都可以访问资源。
3、Proxy代理---通过服务端代理转发,例如用node作为web服务器,请求过程如下:接口请求->node->服务端接口->node->前端,
12、浏览器渲染原理
JS有引擎,那么执行渲染也有一个渲染引擎,Gecko、WebKit
为什么操作DOM慢?
DOM操作属于渲染引擎中的东西,而JS又属于JS引擎中的东西,当我们通过JS操作DOM时,这个操作会涉及到两个线程之间的通信,那么势必会带来性能上的损耗。
回流:修改宽度、高度等影响页面布局的都会发生回流
重绘:当节点的外观发生变化,不影响布局的,会发生重绘,回流必定重绘
13、性能优化
前端性能优化时一个长期的过程,常见的优化方法有如下几种:
网络层面:
1、减少HTTP请求,具体做法如下:
(1)如把多个js或css进行合并请求
(2)设计层面简化页面,如百度首页
(3)合理设置http缓存(设置Cache-Control: max-age)
(4)资源合并与压缩
(5)css雪碧图、base64
(6)懒加载
2、减少资源体积
(1)gzip压缩
(2)代码混淆、压缩
3、缓存
(1)DNS缓存--访问一个域名后,DNS返回正常IP后,操作系统会将这个地址临时存储起来,这就是DNS缓存
(2)CDN部署与缓存
(3)http缓存
强缓存:直接从缓存中读取(Expires、Cache-Control)
协商缓存:由服务端告知,可以从缓存中读取
4、使用webp格式的图片
渲染和DOM操作方面:
在网页初步加载时,获取HTML文件之后,最初的工作是构建DOM和构建CSSOM两个树,之后他们合并形成渲染树,最后进行打印。
1、css放在header中,js放在body底部,先让页面渲染
2、尽量避免内联样式--减少html体积
3、避免直接进行频繁的DOM操作
4、使用class代替内联样式修改
5、使用事件代理
6、函数节流:在一定时间内,函数只能执行一次,函数防抖:函数防抖的基本思想是设置一个定时器,在指定时间间隔内运行代码时清除上一次的定时器,并设置新的定时器,直到函数请求停止并超过时间间隔才会执行
vue项目的性能优化:
vue本身对虚拟DOM diff后才会更新DOM,性能相对来说已经很高,但是项目中还可能会遇到性能问题,优化主要方法:
1、组件懒加载 2、服务端渲染 3、数据遍历时,设置key值 4、骨架屏
14、webpack
现代JavaScript应用程序的静态模块打包工具。核心概念:
(1)入口(entry)
(2)输出(output)
(3)loader--能够处理那些非JavaScript文件,本质上,loader将所有类型的文件,转换成应用程序的依赖图可以直接引用的模块。
(4)插件(plugins)
15、XSS
XSS,跨站脚本攻击。本质上讲,XSS就是浏览器错误的把用户输入的信息当做js脚本执行了。主要分为反射性XSS、存储型XSS、DOM XSS。
反射型XSS:XSS代码出现在URL中,服务端进行解析,最后返回给浏览器,浏览器执行了XSS代码。
存储型XSS:主要是将XSS代码存储在服务器中,下次请求页面的时候就不用带上XSS代码了。例如留言板。
DOM XSS:该问题一般我们编写JS代码造成的,好比使用eval语句执行用户输入的字符串。
防御:
(1)cookie设置httpOnly
(2)对用户输入的特殊字符进行编码、解码或者过滤
(3)禁止使用eval
(4)使用$().text赋值
16、CSRF
CSRF跨站伪造请求,在用户已登录目标网站之后,诱导用户访问一个攻击页面,已用户的身份在攻击页面发送伪造请求。 防御手段:
(1)使用POST,限制GET
(2)加验证码
(3)使用token
17、常见的兼容性问题
1、默认的内补丁和外补丁不同,magin和padding
解决:设置{magin:0;padding:0;}
2、标签之间有默认间距
解决:给父元素设置font-size:0
3、ios元素绑定事件,点击无效
解决:加样式cursor:pointer