前端常见概念讲解

260 阅读4分钟

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