JS面试指南代码测试记录 --- Javascript篇

JS作用域与作用域链

作用域

  • 作用域就是代码的执行环境,全局执行环境就是全局作用域,函数的执行环境就是私有作用域,它们都是栈内存。
  • 执行环境定义了变量或函数有权访问的其他数据。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中 。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
  • Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为 window对象的属性和方法创建的。
  • NODE环境中,全局执行环境是 global对象。
  • 某个执行环境中所有的代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出时,如关闭浏览器或网页,才会被销毁)
  • 每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将被环境弹出,把控制权返回给之前的执行环境。ECMAScript 程序中的执行流正是由这个方便的机制控制着。

作用域链

  • 代码在一个环境中执行时,会创造当前变量对象的一个作用域链(作用域形成的链条)
  • 作用域链的前端是当前代码所在环境的变量对象
  • 下一个对象是当前变量对象的外部环境,一直递推下去
  • 作用域链的最后一个对象是全局作用环境的变量对象
  • 内部环境可以通过作用域链访问所有外部环境,但是外部环境不能访问内部环境的任何变量和函数。
  • 作用域链也称变量查找的机制
  • 访问局部变量比访问全局变量要
  • 原文链接:https://www.jianshu.com/p/cc79f22b99fe

this 的理解

  • this指向函数直接调用者,而不是间接调用者
  • 如果有new,则this指向new出来的对象
  • 事件中,this指向触发事件的对象。特殊:IE中的attachEvent总是指向全局对象window

Ajax原理

Ajax原理简单地说就是在用户和服务器之间加一个中间层(Ajax引擎),通过XmlHttpRequest对象来向服务器发送异步请求,从服务器获取数据,然后用JavaScript对象来操作dom而更新页面。使用户操作和服务器请求异步化。

  • 代码
/** 1. 创建连接 **/
var xhr = null;
xhr = new XMLHttpRequest()
/** 2. 连接服务器 **/
xhr.open('get', url, true)
/** 3. 发送请求 **/
xhr.send(null);
/** 4. 接受请求 **/
xhr.onreadystatechange = function(){
   if(xhr.readyState == 4){
   	if(xhr.status == 200){
   		success(xhr.responseText);
   	} else { 
   		/** false **/
   		fail && fail(xhr.status);
   	}
   }
}

ajax 有那些优缺点?

  • 优点:
    • 通过异步模式,提升了用户体验.
    • 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用.
    • Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载。
    • Ajax可以实现动态不刷新(局部刷新)
  • 缺点:
    • 安全问题 AJAX暴露了与服务器交互的细节。
    • 对搜索引擎的支持比较弱。
    • 不容易调试。

模块化开发怎么做

  • 立即执行函数,不暴露私有成员
var module1 = (function(){
    var _count = 0;
    var m1 = function(){
      //...
    };
    var m2 = function(){
      //...
    };
    return {
      m1 : m1,
      m2 : m2
    };
})();

异步加载JS的方式有哪些

  • 设置<script>属性async="async"(一旦脚本可用,则会异步执行)
  • 动态创建 script DOM:document.createElement('script');
  • XmlHttpRequest脚本注入
  • 异步加载库 LABjs
  • 模块加载器 Sea.js

那些操作会造成内存泄漏

  • 定义:
    内存泄漏是指:对象在不使用时仍存在,导致占用的内存无法使用或者回收的情况
  • 可能操作:
    1. 未使用var声明的变量
    2. 闭包函数
    3. 循环引用,两个对象相互引用
    4. 控制台日志(console.log)
    5. 移除存在绑定事件的dom元素(IE
    6. setTimeOut第一个参数用字符串而非函数,会导致内存泄漏
  • 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收。

XMLJSON的区别

1. XML定义

扩展标记语言,用于标记电子文件使其具有结构性的标记语言,可用来标记数据,定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。XML是标准通用标记语言 (SGML) 的子集,非常适合 Web传输。XML提供统一的方法来描述和交换独立于应用程序或供应商的结构化数据。格式统一,符合标准。

2.JSON定义

一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性。可在不同平台之间进行数据交换。JSON采用兼容性很高的、完全独立于语言文本格式,同时也具备类似于C语言的习惯(包括C,C++, C#,Java,JavaScript,Perl,Python等)体系的行为。这些特性使JSON成为理想的数据交换语言。

3. 对比

  • 数据体积方面
    JSON相对于XML来讲,数据的体积小,传递的速度更快些。
  • 数据交互方面
    SONJavaScript的交互更加方便,更容易解析处理,更好的数据交互
  • 数据描述方面
    JSON对数据的描述性比XML较差
  • 传输速度方面
    JSON的速度要远远快于XML

webpack的看法

  • webpack是模块打包工具,可以使用webpack管理模块依赖并编译输出模块们需要的静态文件。
  • 它能够很好地管理、打包web开发中用到的htmlcssJavaScript以及静态资源文件(图片、字体等),让开发更加高效
  • 对于不同资源的文件,webpack有对应的模块加载器
  • webpack模块会分析模块间的依赖关系,并生成优化且合并后的静态资源

AMDCommonjs的理解

  • CommonJS服务器端模块的规范Node.js采用了这个规范;AMD(异步模块加载)和CMD通常用于浏览器端AMD的实现代表是require.js
  • AMDCommonJS最明显的不同就是异步加载CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。
  • AMD推荐的风格通过返回一个对象做为模块对象,CommonJS的风格通过对module.exportsexports的属性赋值来达到暴露模块对象的目的
  • 对于CommonJS,模块以文件为单位存在,模块实现全部位于该文件内部;AMD模块不一定以文件为单位,小模块集群也是允许的;但基本上在开发过程中,还是应当以文件的形式进行管理

常见web安全及防护原理

sql注入原理:

就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令

  • 防护方式
    • 永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等
    • 永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取
    • 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接
    • 不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息

XSS原理:

Xss(cross-site scripting)攻击指的是攻击者往Web页面里插入恶意html标签或者javascript代码。比如:攻击者在论坛中放一个看似安全的链接,骗取用户点击后,窃取cookie中的用户私密信息;或者攻击者在论坛中加一个恶意表单,当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点

  • XSS防范方法
    • 首先代码里对用户输入的地方和变量都需要仔细检查长度和对”<”,”>”,”;”,”’”等字符做过滤;
    • 其次任何内容写到页面之前都必须加以encode,避免不小心把 htmltag弄出来。这一个层面做好,至少可以堵住超过一半的XSS攻击

XSSCSRF有什么区别吗?

  • XSS是获取信息,不需要提前知道其他用户页面的代码和数据包。CSRF是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。要完成一次CSRF攻击,受害者必须依次完成两个步骤
  1. 登录受信任网站A,并在本地生成Cookie
  2. 在不登出A的情况下,访问危险网站B
  • CSRF的防御
    • 服务端的CSRF方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数
    • 通过验证码的方法

用过哪些设计模式

设计模式定义:

计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。

设计原则

  • 单一职责原则
    1.一个程序只做好一件事
    2.如果功能过于复杂就拆分开,每个部分保持独立
  • 开放/封闭原则
    1.对扩展开放,对修改封闭
    2.增加需求时,扩展新代码,而非修改已有代码
  • 里氏替换原则
    1.子类能覆盖父类
    2.父类能出现的地方子类就能出现
  • 接口隔离原则
    1.保持接口的单一独立
    2.类似单一职责原则,这里更关注接口
  • 依赖倒转原则
    1.面向接口编程,依赖于抽象而不依赖于具
    2.使用方只关注接口而不关注具体类的实现

1.外观模式

  • 定义
    把多个子系统中复杂逻辑进行抽象,从而提供一个更统一、更简洁、更易用的API
  • 比如
    JQuery就把复杂的原生DOM操作进行了抽象和封装,并消除了浏览器之间的兼容问题,从而提供了一个更高级更易用的版本
  • 优点
    减少系统相互依赖。
    提高灵活性。
    提高了安全性
  • 缺点
    不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

2.代理模式

  • 定义
    是为一个对象提供一个代用品或占位符,以便控制对它的访问

  • 比如
    HTML元素的事件代理

  • 优点
    1.代理模式能将代理对象与被调用对象分离,降低了系统的耦合度。代理模式在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用
    2.代理对象可以扩展目标对象的功能;通过修改代理对象就可以了,符合开闭原则

  • 缺点
    处理请求速度可能有差别,非直接访问存在开销

3.工厂模式

  • 定义
    定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。
  • 场景
    1.如果你不想让某个子系统与较大的那个对象之间形成强耦合,而是想运行时从许多子系统中进行挑选的话,那么工厂模式是一个理想的选择
    2.将new操作简单封装,遇到new的时候就应该考虑是否用工厂模式;
    3.需要依赖具体环境创建不同实例,这些实例都有相同的行为,这时候我们可以使用工厂模式
  • 优点
    1.创建对象的过程可能很复杂,但我们只需要关心创建结果。
    2.构造函数和创建者分离, 符合“开闭原则”
    3.一个调用者想创建一个对象,只要知道其名称就可以了。
    4.扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
  • 缺点
    1.添加新产品时,需要编写新的具体产品类,一定程度上增加了系统的复杂度
    2.考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度

4.单例模式

  • 定义
    顾名思义,单例模式中Class的实例个数最多为1。当需要一个对象去贯穿整个系统执行某些任务时,单例模式就派上了用场。而除此之外的场景尽量避免单例模式的使用,因为单例模式会引入全局状态,而一个健康的系统应该避免引入过多的全局状态。
  • 我们一般通过实现以下两点来解决上述问题:
    1.隐藏Class的构造函数,避免多次实例化
    2.通过暴露一个 getInstance() 方法来创建/获取唯一实例
  • 实现的关键点有:
    使用 IIFE创建局部作用域并即时执行;
    getInstance()为一个 闭包 ,使用闭包保存局部作用域中的单例对象并返回。
  • 场景
    1.定义命名空间和实现分支型方法
    2.登录框
    3.vuex 和 redux中的store
  • 优点
    划分命名空间,减少全局变量
    增强模块性,把自己的代码组织在一个全局变量名下,放在单一位置,便于维护
    且只会实例化一次。简化了代码的调试和维护
  • 缺点
    由于单例模式提供的是一种单点访问,所以它有可能导致模块间的强耦合
    从而不利于单元测试。无法单独测试一个调用了来自单例的方法的类,而只能把它与那个单例作为一个单元一起测试。

5.策略模式

  • 定义
    对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。把它们一个个封装起来,并且使它们可以互相替换
  • 场景
  1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的’行为’,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
  2. 一个系统需要动态地在几种算法中选择一种。
  3. 表单验证
  • 优点
  1. 利用组合、委托、多态等技术和思想,可以有效的避免多重条件选择语句提供了对开放-封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,理解,易于扩展
  2. 利用组合和委托来让Context拥有执行算法的能力,这也是继承的一种更轻便的代替方案
  • 缺点
  1. 会在程序中增加许多策略类或者策略对象
  2. 要使用策略模式,必须了解所有的strategy,必须了解各个strategy之间的不同点,这样才能选择一个合适的strategy

6.迭代器模式

  • 定义
    提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象的内部表示。
  • 迭代器模式解决了以下问题:
  1. 提供一致的遍历各种数据结构的方式,而不用了解数据的内部结构
  2. 提供遍历容器(集合)的能力而无需改变容器的接口
  • 一个迭代器通常需要实现以下接口:
  1. hasNext():判断迭代是否结束,返回Boolean
  2. next():查找并返回下一个元素

7.观察者模式

  • 定义
    被观察对象(subject)维护一组观察者(observer),当被观察对象状态改变时,通过调用观察者的某个方法将这些变化通知到观察者。
  • 观察者模式中Subject对象一般需要实现以下API:
  1. subscribe(): 接收一个观察者observer对象,使其订阅自己
  2. unsubscribe(): 接收一个观察者observer对象,使其取消订阅自己
  3. fire(): 触发事件,通知到所有观察者
  • 场景
    DOM事件
    vue 响应式的实现
  • 优点
  1. 支持简单的广播通信,自动通知所有已经订阅过的对象
  2. 目标对象与观察者之间的抽象耦合关系能单独扩展以及重用
  3. 增加了灵活性
  4. 观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化。
  • 缺点
    过度使用会导致对象与对象之间的联系弱化,会导致程序难以跟踪维护和理解

8.中介者模式

  • 定义
    在中介者模式中,中介者(Mediator)包装了一系列对象相互作用的方式,使得这些对象不必直接相互作用,而是由中介者协调它们之间的交互,从而使它们可以松散偶合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用,保证这些作用可以彼此独立的变化。
  • 中介者模式和观察者模式有一定的相似性,都是一对多的关系,也都是集中式通信,不同的是中介者模式是处理同级对象之间的交互,而观察者模式是处理Observer和Subject之间的交互。中介者模式有些像婚恋中介,相亲对象刚开始并不能直接交流,而是要通过中介去筛选匹配再决定谁和谁见面。
  • 场景
    例如购物车需求,存在商品选择表单、颜色选择表单、购买数量表单等等,都会触发change事件,那么可以通过中介者来转发处理这些事件,实现各个事件间的解耦,仅仅维护中介者对象即可。
  • 优点
    使各对象之间耦合松散,而且可以独立地改变它们之间的交互
    中介者和对象一对多的关系取代了对象之间的网状多对多的关系
    如果对象之间的复杂耦合度导致维护很困难,而且耦合度随项目变化增速很快,就需要中介者重构代码
  • 缺点
    系统中会新增一个中介者对象,因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。中介 者对象自身往往就是一个难以维护的对象。

9.访问者模式

  • 定义
    访问者模式 是一种将算法与对象结构分离的设计模式,通俗点讲就是:访问者模式让我们能够在不改变一个对象结构的前提下能够给该对象增加新的逻辑,新增的逻辑保存在一个独立的访问者对象中。访问者模式常用于拓展一些第三方的库和工具。
  • 访问者模式的实现有以下几个要素:
  1. Visitor Object:访问者对象,拥有一个visit()方法
  2. Receiving Object:接收对象,拥有一个accept() 方法
  3. visit(receivingObj):用于Visitor接收一个Receiving Object
  4. accept(visitor):用于Receving Object接收一个Visitor,并通过调用Visitor的 visit() 为其提供获取Receiving Object数据的能力
  • 场景
  1. 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作
  2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
  • 优点
    符合单一职责原则
    优秀的扩展性
    灵活性
  • 缺点
    具体元素对访问者公布细节,违反了迪米特原则
    违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
    具体元素变更比较困难

什么是同源策略

  • 参考链接:https://blog.csdn.net/diwang_718/article/details/104753425
  • 定义
    如果源不一样就是协议、域名、端口有一个不一样的话,就是非同源策略,就跨域了。用于隔离潜在的恶意文件的关键的安全机制。
  • 限制内容:
    限制就是说不是一个源的文档,你没有权力去操作另一个源的文档,主要限制的几个方面如下:
  1. Cookie 、LocalStorage 和 IndexDB无法读取
  2. 无法获取或操作另一个资源的DOM
  3. AJAX请求不能发送

offsetWidth/offsetHeight,clientWidth/clientHeightscrollWidth/scrollHeight的区别

  1. offsetWidth/offsetHeight返回值包含content + padding + border,效果与e.getBoundingClientRect()相同
  2. clientWidth/clientHeight返回值只包含content + padding,如果有滚动条,也不包含滚动条
  3. scrollWidth/scrollHeight返回值包含content + padding + 溢出内容的尺寸

javascript有哪些方法定义对象

  1. 对象字面量: var obj = {};
  2. 构造函数: var obj = new Object();
  3. Object.create(): var obj = Object.create(Object.prototype);

常见兼容性问题

  • png24位的图片在IE6浏览器上出现背景,解决方案是做成PNG8
  • 浏览器默认的margin和padding不同。解决方案是加一个全局的*{margin:0;padding:0;}来统一,,但是全局效率很低,一般是如下这样解决:
body,ul,li,ol,dl,dt,dd,form,input,h1,h2,h3,h4,h5,h6,p{
	margin:0;
	padding:0;
}
  • IE下,event对象有x,y属性,但是没有pageX,pageY属性
  • Firefox下,event对象有pageX,pageY属性,但是没有x,y属性.

promise的了解?

依照 Promise/A+的定义,Promise有四种状态:

  1. pending:初始状态, 非 fulfilledrejected.

  2. fulfilled:成功的操作.

  3. rejected:失败的操作.

  4. settled: Promise已被fulfilledrejected,且不是pending

另外, fulfilledrejected一起合称settled

Promise对象用来进行延迟(deferred) 和异步(asynchronous) 计算

Promise的构造函数

  • 构造一个 Promise,最基本的用法如下:
var promise = new Promise(function(resolve, reject) {

        if (...) {  // succeed

            resolve(result);

        } else {   // fails

            reject(Error(errMessage));

        }
    });
  • Promise实例拥有then方法(具有 then方法的对象,通常被称为thenable)。它的使用方法如下:
promise.then(onFulfilled, onRejected)
  • 接收两个函数作为参数,一个在 fulfilled的时候被调用,一个在rejected的时候被调用,接收参数就是futureonFulfilled对应resolve, onRejected对应 reject

jQuery源码有哪些写得好的地方?

  1. jquery源码封装在一个匿名函数的自执行环境中,有助于防止变量的全局污染。
  2. 通过传入window对象参数,可以使window对象作为局部变量使用,好处是当jquery中访问window对象的时候,就不用将作用域链退回到顶层作用域了,从而可以更快的访问window对象。
  3. 传入undefined参数,可以缩短查找undefined时的作用域链
  4. jquery将一些原型属性和方法封装在了jquery.prototype中,为了缩短名称,又赋值给了jquery.fn,这是很形象的写法
  5. 有一些数组或对象的方法经常能使用到,jQuery将其保存为局部变量以提高访问速度
  6. jquery实现的链式调用可以节约代码,所返回的都是同一个对象,可以提高代码效率

vue、react、angular

  • Vue.js 一个用于创建 web 交互界面的库,是一个精简的 MVVM。它通过双向数据绑定把 View 层和 Model 层连接了起来。实际的 DOM 封装和输出格式都被抽象为了Directives 和 Filters
  • AngularJS 是一个比较完善的前端MVVM框架,包含模板,数据双向绑定,路由,模块化,服务,依赖注入等所有功能,模板功能强大丰富,自带了丰富的 Angular指令
  • react React 仅仅是 VIEW 层是facebook公司。推出的一个用于构建UI的一个库,能够实现服务器端的渲染。用了virtual dom,所以性能很好。

Node的应用场景

特点

  1. 他是一个JavaScript运行环境
  2. 依赖于chrome V8进行代码解析
  3. 事件驱动
  4. 非阻塞I/O
  5. 单进程、单线程

优点

高并发(重要优点)

缺点

  1. 只支持单核CPU,不能充分利用CPU
  2. 可靠性低,一旦代码某个环节崩溃,整个系统都崩溃

web开发中会话跟踪的方法有哪些

  • cookie
  • session
  • url重写
  • 隐藏input
  • ip地址

深入理解HTTP SessionCookie

Session

Session指的是HttpSession类的对象

Session的生命周期
  • Session保存在服务器端。为了获得更高的存取速度,服务器一般把Session放在内存里。每个用户都会有一个独立的Session。如果Session内容过于复杂,当大量客户访问服务器时可能会导致内存溢出。因此,Session里的信息应该尽量精简。
  1. Session在用户第一次访问服务器的时候自动创建。需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session。如果尚未生成Sessioni,也可以使用request.getSession(true)强制生成Session
  2. Session生成后,只要用户继续访问,服务器就会更新Session最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session“活跃(active)”了一次。
  3. 由于会有越来越多的用户访问服务器,因此Session也会越来越多。为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session自动失效了。
  • Sessioni的超时时间maxInactiveInterval属性,可以通过对应的getMaxInactiveInterval()获取,通过setMaxInactiveInterval(longinterval)修改。
Session对浏览器的要求
  • 虽然Session保存在服务器,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持。这是因为**Session需要使用Cookie作为识别标志**。HTTP协议是无状态的Session不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一 个名为JSESSIONIDCookie,它的值为该Sessionid(也就是HttpSession.getId()的返回值)。Session依据该Cookie来识别是否为同一用户。
  • Cookie为服务器自动生成的,它的maxAge属性一般为–1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效。
  • 因此同一机器的两个浏览器窗口访问服务器时,会生成两个不同的Session。但是由浏览器窗口内的链接、脚本等打开的新窗口(也就是说不是双击桌面浏览器图标等打开的窗口)除外。这类子窗口会共享父窗口的Cookie,因此会共享一个Session
  • 注意:新开的浏览器窗口会生成新的Session,但子窗口除外。子窗口会共用父窗口的Session。例如,在链接上右击,在弹出的快捷菜单中选择“在新窗口中打开”时,子窗口便可以访问父窗口的Session
  • 如果客户端浏览器将Cookie功能禁用,或者不支持Cookie,Java Web提供了另一种解决方案:URL地址重写

Cookie

什么是Cookie
  • Cookie通过在客户端记录信息确定用户身份,Sessioni通过在服务器端记录信息确定用户身份
  • Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。 Cookie可以弥补HTTP协议无状态的不足。在Sessioni出现之前,基本上所有的网站都采用Cookie来跟踪会话。
  • Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。 客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以 此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
Cookie 对象的内容

Cookie对象使用key-value属性对的形式保存用户状态,一个Cookie对象保存一个属性对,一个request或者response同时使用多个Cookie。因为Cookie类位于包javax.servlet.http.*下面,所以JSP中不需要import该类。

Cookie的不可跨域名性
  • 浏览器判断一个网站是否能操作另一个网站Cookie的依据是域名
  • 提示:Cookie中保存中文只能编码。一般使用UTF-8编码即可。不推荐使用GBK等中文编码,因为浏览器不一定支持,而且JavaScript也不支持GBK编码.
Cookie的有效期
  • CookiemaxAge决定着Cookie的有效期,单位为秒(Second)。Cookie中通过getMaxAge()方法与setMaxAge(int maxAge)方法来读写maxAge属性。
  1. 如果maxAge属性为正数,则表示该Cookie会在maxAge秒之后自动失效。浏览器会将maxAge为正数的Cookie持久化,即写到对应的Cookie文件中。无论客户关闭了浏览器还是电脑,只要还在maxAge秒之前,登录网站时该Cookie仍然有效
  2. 如果maxAge负数,则表示该Cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效,关闭窗口后该Cookie即失效maxAge为负数的 Cookie,为临时性Cookie,不会被持久化,不会被写到Cookie文件中。Cookie信息保存在浏览器内存中,因此关闭浏览器该Cookie就消失了。Cookie默认的maxAge值为–1
  3. 如果maxAge0,则表示删除该CookieCookie机制没有提供删除Cookie的方法,因此通过设置该Cookie即时失效实现删除Cookie的效果。失效的Cookie会被浏览器从Cookie文件或者内存中删除,
Cookie的修改、删除
  • Cookie不提供修改、删除操作

  • 如果要修改某个Cookie,只需要新建一个同名的Cookie,添加到response覆盖原来的Cookie

  • 如果要删除某个Cookie,只需要新建一个同名的Cookie,并将maxAge设置为0,并添加到response中覆盖原来的Cookie

  • 注意:修改、删除Cookie时,新建的Cookievalue、maxAge之外的所有属性,例如name、path、domain等,都要与原Cookie完全一样。否则,浏览器将视为两个不同的Cookie不予覆盖,导致修改、删除失败。

Cookie的域名
  • 同一个一级域名下的两个二级域名如www.helloweenvsfei.comimages.helloweenvsfei.com也不能交互使用Cookie,因为二者的域名并不严格相同。如果想所有helloweenvsfei.com名下的二级域名都可以使用该Cookie,需要设置Cookiedomain参数
  • 注意:domain参数必须以点(“.”)开始。另外,name相同但domain不同的两个Cookie是两个不同的Cookie。如果想要两个域名完全不同的网站共有Cookie,可以生成两个Cookiedomain属性分别为两个域名,输出到客户端。
Cookie的安全属性
  • HTTP协议不仅是无状态的,而且是不安全的。使用HTTP协议的数据不经过任何加密就直接在网络上传播,有被截获的可 能。使用HTTP协议传输很机密的内容是一种隐患。如果不希望CookieHTTP等非安全协议中传输,可以设置Cookiesecure属性为 true。浏览器只会在HTTPSSSL等安全协议中传输此类Cookie
JavaScript操作Cookie
  • Cookie是保存在浏览器端的,因此浏览器具有操作Cookie的先决条件。浏览器可以使用脚本程序如 JavaScript等操作Cookie
  • W3C标准的浏览器会 阻止JavaScript读写任何不属于自己网站的Cookie。换句话说,A网站的JavaScript程序读写B网站的Cookie不会有任何结果。
永久登录
  • 方法一:
    最直接的是把用户名与密码都保持到Cookie中,下次访问时检查Cookie中的用户名与密码,与数据库比较。这是一种比较危险的选择,一般不把密码等重要信息保存到Cookie中。

  • 方法二:
    是把密码加密后保存到Cookie中,下次访问时解密并与数据库比较。这种方案略微安全一些。

  • 方法三:
    登录的时间戳保存到Cookie与数据库中,到时只验证用户名与登录时间戳就可以了。

  • 这几种方案验证账号时都要查询数据库。

  • 我们实现一个永久登录的Demo:
    方法:只在登录时查询一次数据库,以后访问验证登录信息时不再查询数据库。实现方式是把账号按照一定的规则加密后,连同账号一块保存到Cookie中。下次访问时只需要判断账号的加密规则是否正确即可。本例把账号保存到名为accountCookie中,把账号连同密钥用MD1算法加密后保存到名为ssidCookie中。验证时验证Cookie中的账号与密钥加密后是否与Cookie中的ssid相等。

URL地址重写

  • URL地址重写是对客户端不支持Cookie的解决方案
  • URL地址重写的原理是将该用户Sessionid信息重写 到URL地址中。服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。
  • HttpServletResponse类提供了encodeURL(Stringurl)实现URL地址重写,

JS的基本数据类型和引用数据类型

  • 基本数据类型:undefinednullbooleannumberstringsymbol
  • 引用数据类型:objectarrayfunction

介绍js有哪些内置对象

  • ObjectJavaScript 中所有对象的父对象
  • 数据封装类对象:ObjectArrayBooleanNumberString
  • 其他对象:FunctionArgumentsMathDateRegExpError

说几条写JavaScript的基本规范

  • 不要在同一行声明多个变量
  • 请使用===/!==来比较true/false或者数值
  • 使用对象字面量替代new Array这种形式
  • 不要使用全局函数
  • Switch语句必须带有default分支
  • If语句必须使用大括号
  • for-in循环中的变量 应该使用var关键字明确限定作用域,从而避免作用域污

JavaScript有几种类型的值

  1. :原始数据类型(undefinednullbooleannumberstring
  2. :引用数据类型(对象、数组和函数)
  • 两种类型的区别是:存储位置不同;
  • 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
  • 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其
  • 在栈中的地址,取得地址后从堆中获得实体

eval是做什么的

  • 它的功能是把对应的字符串解析成JS代码并运行
  • 应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)
  • JSON字符串转换为JSON对象的时候可以用evalvar obj =eval('('+ str +')')

["1", "2", "3"].map(parseInt) 答案是多少

  • [1, NaN, NaN]因为parseInt需要两个参数 (val, radix),其中radix 表示解析时用的基数。
  • map传了 3(element, index, array),对应的 radix 不合法导致解析失败。

JSON 的了解

  • JSON(JavaScript Object Notation)是一种轻量级的数据交换格式
  • 它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小
  • JSON字符串转换为JSON对象:
var obj =eval('('+ str +')');
var obj = str.parseJSON();
var obj = JSON.parse(str);
  • JSON对象转换为JSON字符串:
var last=obj.toJSONString();
var last=JSON.stringify(obj);

js延迟加载的方式有哪些

  • 设置<script>属性 defer="defer"(脚本将在页面完成解析时执行)
  • 动态创建 script DOM:document.createElement('script');
  • XmlHttpRequest 脚本注入
  • 延迟加载工具LazyLoad

同步和异步的区别

  • 同步:浏览器访问服务器请求,用户看到页面刷新。重发请求,等请求完,页面刷新,新内容出现,用户看到新内容,才可以进行下一步操作。
  • 异步:浏览器访问服务器请求,用户正常操作,浏览器后台进行请求。请求完,页面不刷新,新内容出现,用户看到新内容。

渐进增强和优雅降级

  • 渐进增强:针对低版本的浏览器进行构建页面,保证最基本功能,然后再针对高版本浏览器进行效果、互交等改进和追加功能达到更好地用户体验。
  • 优雅降级:一开始就构建完整页面,再针对低版本浏览器进行兼容。

deferasync

  • defer并行加载js文件,会按照页面上的<script>表情顺序执行
  • async并行加载js文件,加载完成立即执行,不会按照页面上的<script>表情顺序执行

说说严格模式的限制

  • 变量必须声明后使用
  • 函数参数必须有同名属性,否则报错
  • 不能用with语句
  • 不能对只读属性赋值
  • 不能使用前缀0表示八进制
  • 不能删除不可删除的属性
  • 不能删除delete prop,会报错,只能删除 delete global[prop]
  • eval不会在它的外层作用于引入变量
  • evalarguments不能被重新赋值
  • arguments不会自动反应函数参数的变化
  • 不能使用arguments.callee,该属性是一个指针,指向拥有这个 arguments对象的函数
  • 不能使用arguments.caller,caller是函数对象的一个属性,该属性保存着调用当前函数的函数。如果没有父函数,则为null
  • 禁止this指向全局对象
  • 不能使用fn.callerfn.arguments获取函数调用的堆栈
  • 增加了保留字(比如protectedstaticinterface

attribute和property的区别是什么

  • attributedom元素在文档中作为html标签拥有的属性;
  • property就是dom元素在js中作为对象拥有的属性。
  • 对于html的标准属性来说,attributeproperty是同步的,是会自动更新的
  • 但是对于自定义的属性来说,他们是不同步的

联系

  • property能够从attribute中得到同步;
  • attribute不会同步property上的值;
  • attributeproperty之间的数据绑定是单向的,attribute->property
  • 更改propertyattribute上的任意值,都会将更新反映到HTML页面中;

谈谈你对ES6的理解

  • 新增模板字符串(为JavaScript提供了简单的字符串插值功能)
  • 箭头函数
  • for-of(用来遍历数据—例如数组中的值。)
  • arguments对象可被不定参数和默认参数完美代替。
  1. 不定参数:containsAll(haystack, ...needles),·needles·前的省略号表明它是一个不定参数,函数被调用时,不定参数前的所有参数都正常填充,任何“额外的”参数都被放进一个数组中并赋值给不定参数。
  2. 默认参数:对于每个参数而言,定义默认值时=后的部分是一个表达式,如果调用者没有传递相应参数,将使用该表达式的值作为参数默认值。animalSentence(animals2="tigers", animals3="bears")
  • ES6promise对象纳入规范,提供了原生的Promise对象。
  • 增加了letconst命令,用来声明变量。
  • 增加了块级作用域。
  • let命令实际上就增加了块级作用域。
  • 还有就是引入module模块的概念

ES6怎么写class

什么是面向对象编程及面向过程编程,它们的异同和优缺点

  • 面向过程就是分析出解决问题的步骤,然后通过函数一步步实现步骤,使用的时候一个一个依次调用。
  • 面向对象是把构成问题的事务分解成各个对象,构建对象是为了描述某个事务在解决问题的行为而不是为了完成一个步骤。
  • 面向对象是以功能来划分的,而不是步骤

面向对象的编程思想

  • 基本思想:使用对象、类、继承、封装等基本概念来进行程序设计
  • 优点:
    1. 易维护
      采用面向对象的思想设计地结构,可读性高,具有继承性,即使改变需求,需要维护的也只是局部模块,所以维护起来比较方便、成本低。
    2. 易拓展
    3. 重用性、继承性高,减少重复代码
    4. 缩短开发周期

对web标准、可用性、可访问性、可维护性的理解

  • 可用性:从用户的角度来看产品问题,能否完成任务,效率如何、是否易于上手等。可用性好意味着产品质量好,是企业的核心竞争力。
  • 可访问性:是web内容对于残障人士的可阅读性和可理解能力
  • 可维护性:一是,系统出错能够快熟定位并解决问题的成本。二是,代码容易理解和增加功能。

通过JS判断是否是一个数组

  1. instanceof方法
    用来判断一个对象是否在其原型链原型构造函数的属性中
var arr = [];
arr instanceof Array; // true
  1. constructor方法
    返回对象相对性的构造函数
var arr = [];
arr.constructor == Array; //true
  1. JQ中正在使用的方法
Object.prototype.toString.call(value) == '[object Array]'
// 利用这个方法,可以写一个返回数据类型的方法
var isType = function (obj) {
     return Object.prototype.toString.call(obj).slice(8,-1);
}
  1. isArray
var a = new Array(123);
var b = new Date();
console.log(Array.isArray(a)); //true
console.log(Array.isArray(b)); //false

map与forEach的区别

  • forEach方法,是最基本的方法,就是遍历与循环,默认有3个传参:分别是遍历的数组内容item、数组索引index、和当前遍历数组Array
  • map方法,基本用法与forEach一致,但是不同的,它会返回一个新的数组,所以在callback需要有return值,如果没有,会返回undefined

谈一谈你理解的函数式编程

  • 简单说,“函数式编程"是一种"编程范式”(programming paradigm),也就是如何编写程序的方法论
  • 它具有以下特性:闭包和高阶函数、惰性计算、递归、函数是"第一等公民"、只用"表达式"

谈一谈箭头函数与普通函数的区别?

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
  • 不可以当作构造函数,也就是使用new命令会报错
  • 不可以使用arguments对象,因为函数体不存在。可以用Rest参数来代替
  • 不可以使用yield命令,因此箭头函数不能做Generator函数(构造器)

谈一谈函数中this的指向

this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象
javascript语言精髓》中大概概括了4种调用方式

  • 方法调用模式
    函数调用模式this指向window的全局对象
  • 函数调用模式
    事实上谁调用这个函数,this就是谁。
  • 构造器调用模式
    new一个函数时,实际上会创建一个连接到prototype成员的新对象,同时this会被绑定到那个新实例对象上。
function Person(name,age){
// 这里的this都指向实例
    this.name = name;
    this.age = age;
    this.sayAge = function(){
        console.log(this.age);
    }
}
 
var dot = new Person('Dot',2);
dot.sayAge();//2

总结:构造函数调用模式this指向被构造的实例对象

  • apply/call调用模式
    语法:
    1、函数.apply(对象, [参数列表])
    2、函数.call(对象, arg1,arg2,arg3…argn)
var fun = function(str) {
    this.status = str;
};
fun.prototype.getStatus = function() {
    alert(this.status);
};
var obj = {
    status: 1
};
fun.prototype.getStatus.apply(obj);
// obj对象冒充fun
//相当于 fun.prototype.getStatus.apply(obj, null); 

总结:this指向第一个参数

异步编程的实现方式

  • 回调函数
    优点:简单,易于理解
    缺点:耦合性高,不方便维护
  • 事件监听(采用时间驱动模式,取决于时间是否发生)
    优点:容易理解、可以绑定多个事件、每个事件可以指定多个回调函数
    缺点:事件驱动型、流程不够清晰
  • 发布/订阅(观察者模式)
    类似于事件监听,但是可以通过‘消息中心’知道有多少个发布者,多少订阅者
  • promise对象
    优点:可以通过then方法,进行链式写法。可以书写错误时的回调函数
    缺点:编写和理解相对比较困难
  • Generator函数
    优点:函数体外的数据交换、错误处理机制
    缺点:流程管理不方便
  • async函数
    优点:内置执行器,更好的语义,更广的适用性,返回的时Promise,结构清晰
    缺点:错误处理机制

对原生Javascript了解程度

数据类型、运算、对象、Function、继承、闭包、作用域、原型链、事件、RegExp、JSON、Ajax、DOM、BOM、内存泄漏、跨域、异步装载、模板引擎、前端MVC、路由、模块化、Canvas、ECMAScript

Js动画与CSS动画区别及相应实现

  • SS3的动画的优点
    在性能上会稍微好点,浏览器对css动画做了些优化
    代码相对简单
  • 缺点
    在动画控制上不够灵活
    兼容性不好
  • js动画弥补了这两个缺点,控制力很强,可以单帧、变换,完全可以兼容IE6,并且功能强大。对于复杂的动画,使用JavaScript比较好,在实现一些小的交互动效的时候,就多考虑css

JS 数组和对象的遍历方式,以及几种方式的比较

通常我们会用循环的方式来遍历数组。但是循环是 导致js 性能问题的原因之一。一般我们会采用下几种方式来进行数组的遍历

  • for-in循环
 var arr = [1,2,3,4,5];

  var obj = { a : 1, b : 2, c : 3 };

  for( var item in arr|obj ){

    fn(item){
     // do sth with arr[item];

     //do sth wtih obj[item];
    };

  }

缺点:for-in 需要分析出array 的每个属性,这个操作性能开销很大。用在 key 已知的数组上是非常不划算的。所以尽量不要用 for-in,除非你不清楚要处理哪些属性,例如 JSON 对象这样的情况。

  • for循环
for (var i=0; i<arr.length; i++){

    //do sth with arr[i];

  }

缺点:循环每进行一次,就要检查一下数组长度。读取属性(数组长度)要比读局部变量慢,
改进:

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

var length =arr.length;

for(var i=0; i<length; i++){

  fn(arr[i]);

}
  • forEach
var arr = [1,2,3,4,5];

arr.forEach(

  fn(value,index){

    //Do sth with value ;

  }

)

这里的 forEach回调中两个参数分别为value,index
forEach无法遍历对象
IE不支持该方法;Firefox 和 chrome支持
forEach无法使用 break,continue跳出循环,且使用return是跳过本次循环

gulp

gulp是前端开发过程中一种基于流的代码构建工具,是自动化项目的构建利器;它不仅能对网站资源进行优化,而且在开发过程中很多重复的任务能够使用正确的工具自动完成

  • Gulp的核心概念:
    流,简单来说就是建立在面向对象基础上的一种抽象的处理数据的工具。在流中,定义了一些处理数据的基本操作,如读取数据,写入数据等,程序员是对流进行所有操作的,而不用关心流的另一头数据的真正流向
    gulp正是通过流和代码优于配置的策略来尽量简化任务编写的工作
  • Gulp的特点:
    易于使用:通过代码优于配置的策略,gulp 让简单的任务简单,复杂的任务可管理
    构建快速:利用 Node.js 流的威力,你可以快速构建项目并减少频繁的 IO操作
    易于学习: 通过最少的 API,掌握 gulp 毫不费力,构建工作尽在掌握:如同一系列流管道

说一下Vue的双向绑定数据的原理

vue.js则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的settergetter在数据变动时发布消息给订阅者,触发相应的监听回调

事件的各个阶段

  • 1:捕获阶段 —> 2:目标阶段 —> 3:冒泡阶段
  • document—> target目标 ----> document

addEventListener()方法

  • 作用:于向指定元素添加事件句柄。

  • 语法:element.addEventListener(event, function, useCapture)
    event:指定事件名,字符串
    function:指定要事件触发时执行的函数。
    useCapture:布尔值,指定事件是否在捕获或冒泡阶段执行

      true表示该元素在事件的“捕获阶段”(由外往内传递时)响应事件
      false表示该元素在事件的“冒泡阶段”(由内向外传递时)响应事件
    

let var const

let

  • 允许你声明一个作用域被限制在块级中的变量、语句或者表达式
  • let绑定不受变量提升的约束,这意味着let声明不会被提升到当前
  • 该变量处于从块开始到初始化处理的“暂存死区”

var

  • 声明变量的作用域限制在其声明位置的上下文中,而非声明变量总是全局的
  • 由于变量声明(以及其他声明)总是在任意代码执行之前处理的,所以在代码中的任意位置声明变量总是等效于在代码开头声明

const

  • 声明创建一个值的只读引用 (即指针)
  • 基本数据当值发生改变时,那么其对应的指针也将发生改变,故造成 const申明基本数据类型时
    再将其值改变时,将会造成报错, 例如 const a = 3 ; a = 5时 将会报错
  • 但是如果是复合类型时,如果只改变复合类型的其中某个Value项时, 将还是正常使用

快速的让一个数组乱序

var arr = [1,2,3,4,5,6,7,8,9,10];
arr.sort(function(){
    return Math.random() - 0.5;
})
console.log(arr);

如何渲染几万条数据并不卡住界面

这道题考察了如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条都渲染出来,而应该一次渲染部分 DOM,那么就可以通过requestAnimationFrame来每 16 ms刷新一次。
window.requestAnimationFrame()告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <ul>控件</ul>
  <script>
    setTimeout(() => {
      // 插入十万条数据
      const total = 100000
      // 一次插入 20 条,如果觉得性能不好就减少
      const once = 20
      // 渲染数据总共需要几次
      const loopCount = total / once
      let countOfRender = 0
      let ul = document.querySelector("ul");
      function add() {
        // 优化性能,插入不会造成回流
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < once; i++) {
          const li = document.createElement("li");
          li.innerText = Math.floor(Math.random() * total);
          fragment.appendChild(li);
        }
        ul.appendChild(fragment);
        countOfRender += 1;
        loop();
      }
      function loop() {
        if (countOfRender < loopCount) {
          window.requestAnimationFrame(add);
        }
      }
      loop();
    }, 0);
  </script>
</body>
</html>

希望获取到页面中所有的checkbox怎么做?

 var domList = document.getElementsByTagName(‘input’)
 var checkBoxList = [];
 var len = domList.length;  //缓存到局部变量
 while (len--) {  //使用while的效率会比for循环更高
   if (domList[len].type == ‘checkbox’) {
       checkBoxList.push(domList[len]);
   }
 }

怎样添加、移除、移动、复制、创建和查找节点

创建新节点

createDocumentFragment()    //创建一个DOM片段
createElement()   //创建一个具体的元素
createTextNode()   //创建一个文本节点

添加、移除、替换、插入

appendChild()      //添加
removeChild()      //移除
replaceChild()      //替换
insertBefore()      //插入

查找

getElementsByTagName()    //通过标签名称
getElementsByName()     //通过元素的Name属性的值
getElementById()        //通过元素Id,唯一性

正则表达式

正则表达式构造函数var reg=new RegExp(“xxx”)与正则表达字面量var reg=//有什么不同?匹配邮箱的正则表达式?

当使用RegExp()构造函数的时候,不仅需要转义引号(即\”表示),并且还需要双反斜杠(即\\表示一个\)。使用正则表达字面量的效率更高

邮箱的正则匹配:

var regMail = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/;

Javascript中calleecaller的作用?

  • arguments是函数内部属性,用来保存函数参数

  • callee是一个指针,指向拥有arguments的函数

    作用:解除函数执行时与函数名的联系=>调用自身
  • caller是返回当前函数的函数引用

    可以通过arguments.callee.caller来解除函数对当前函数名的耦合

那么问题来了?如果一对兔子每月生一对兔子;一对新生兔,从第二个月起就开始生兔子;假定每对兔子都是一雌一雄,试问一对兔子,第n个月能繁殖成多少对兔子?(使用callee完成)

var result=[];
  function fn(n){  //典型的斐波那契数列
     if(n==1){
          return 1;
     }else if(n==2){
             return 1;
     }else{
          if(result[n]){
                  return result[n];
         }else{
                 //argument.callee()表示fn()
                 result[n]=arguments.callee(n-1)+arguments.callee(n-2);
                 return result[n];
         }
    }
 }

请简单实现双向数据绑定

  • Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
  • get
    属性的 getter 函数,如果没有getter,则为undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
    默认为undefined
  • set
    属性的setter函数,如果没有 setter,则为undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的this对象。
    默认为undefined
<input id="input"/>
const data = {};
const input = document.getElementById('input');
Object.defineProperty(data, 'text', {
  set(value) {
    input.value = value;
    this.value = value;
  }
});
input.onChange = function(e) {
  data.text = e.target.value;
}

Event loop

正确的一次 Event loop 顺序

  1. 执行同步代码,
  2. 执行栈为空,查询是否有异步任务任务需要执行
  3. 先同步再异步,在此基础上先宏任务再微任务
  • 异步任务:setTimeoutsetIntervalajax、事件绑定等
  • 同步任务:除了异步任务外的所有任务
  • 微任务:process.nextTickPromise后的then语句和catch语句等
  • 宏任务:除了微任务以外的所有任务

事件流

事件流分为两种,捕获事件流和冒泡事件流

  • 捕获事件流从根节点开始执行,一直往子节点查找执行,直到查找执行到目标节点
  • 冒泡事件流从目标节点开始执行,一直往父节点冒泡查找执行,直到查到到根节点
事件流分为三个阶段,一个是捕获节点,一个是处于目标节点阶段,一个是冒泡阶段

JavaScript 对象生命周期的理解

  • 当创建一个对象时,JavaScript 会自动为该对象分配适当的内存
  • 垃圾回收器定期扫描对象,并计算引用了该对象的其他对象的数量
  • 如果被引用数量为0,或惟一引用是循环的,那么该对象的内存即可回收

我现在有一个canvas,上面随机布着一些黑块,请实现方法,计算canvas上有多少个黑块

请手写实现一个promise

说说从输入URL到看到页面发生的全过程,越详细越好

首先浏览器主进程接管,开了一个下载线程。
然后进行HTTP请求(DNS查询、IP寻址等等),中间会有三次捂手,等待响应,开始下载响应报文。
将下载完的内容转交给Renderer进程管理。
Renderer进程开始解析css rule treedom tree,这两个过程是并行的,所以一般我会把link标签放在页面顶部。
解析绘制过程中,当浏览器遇到link标签或者scriptimg等标签,浏览器会去下载这些内容,遇到时候缓存的使用缓存,不适用缓存的重新下载资源。
css rule treedom tree生成完了之后,开始合成render tree,这个时候浏览器会进行layout,开始计算每一个节点的位置,然后进行绘制。
绘制结束后,关闭TCP连接,过程有四次挥手

描述一下this

this,函数执行的上下文,可以通过applycallbind改变this的指向。对于匿名函数或者直接调用的函数来说,this指向全局上下文(浏览器为windowNodeJSglobal),剩下的函数调用,那就是谁调用它,this就指向谁。当然还有es6的箭头函数,箭头函数的指向取决于该箭头函数声明的位置,在哪里声明,this就指向哪里

浏览器的缓存机制

浏览器缓存主要有两类:缓存协商和彻底缓存,也有称之为协商缓存和强缓存

  • 强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的network选项中可以看到该请求返回200的状态码;
  • 协商缓存:向服务器发送请求,服务器会根据这个请求的request header的一些参数来判断是否命中协商缓存,如果命中,则返回304状态码并带上新的response header通知浏览器从缓存中读取资源;
  • 两者的共同点是,都是从客户端缓存中读取资源;区别是强缓存不会发请求,协商缓存会发请求

现在要你完成一个Dialog组件,说说你设计的思路?它应该有什么功能?

  • 该组件需要提供hook指定渲染位置,默认渲染在body下面。
  • 然后改组件可以指定外层样式,如宽度等
  • 组件外层还需要一层mask来遮住底层内容,点击mask可以执行传进来的onCancel函数关闭Dialog
  • 另外组件是可控的,需要外层传入visible表示是否可见。
  • 然后Dialog可能需要自定义头head和底部footer,默认有头部和底部,底部有一个确认按钮和取消按钮,确认按钮会执行外部传进来的onOk事件,然后取消按钮会执行外部传进来的onCancel事件。
  • 当组件的visibletrue时候,设置bodyoverflowhidden,隐藏body的滚动条,反之显示滚动条。
    组件高度可能大于页面高度,组件内部需要滚动条。
  • 只有组件的visible有变化且为ture时候,才重渲染组件内的所有内容

ajax、axios、fetch区别

jQuery ajax

$.ajax({
	type:'POST',
	url:url,
	data:data,
	dataType:dataType,
	success:function(){},
	error:function(){}
})
优缺点:
  • 本身是针对MVC的编程,不符合现在前端MVVM的浪潮
  • 基于原生的XHR开发,XHR本身的架构不清晰,已经有了fetch的替代方案
  • JQuery整个项目太大,单纯使用ajax却要引入整个JQuery非常的不合理(采取个性化打包的方案又不能享受CDN服务)

axios

axios({
	method:'post',
	url:'',
	data:{
		fisrstName:'',
		lastName:''
	}
})
.then(function(response){
	console.log(response);
})
.catch(function(error){
	console.log(error);
})
优缺点:

从浏览器中创建 XMLHttpRequest
node.js 发出http请求
支持Promise API
拦截请求和响应
转换请求和响应数据
取消请求
自动转换JSON数据
客户端支持防止CSRF/XSRF

fetch

try {
	let response = await fetch(url);
	let data = response.json();
	console.log(data);
} catch(e) {
	console.log("Oops,error",e);
}
优缺点:
  • fetcht只对网络请求报错,对400500都当做成功的请求,需要封装去处理
  • fetch默认不会带cookie,需要添加配置项
  • fetch不支持abort,不支持超时控制,使用setTimeoutPromise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了量的浪费
  • fetch没有办法原生监测请求的进度,而XHR可以

JavaScript的组成

JavaScript 由以下三部分组成:

  • ECMAScript(核心):JavaScript 语言基础
  • DOM(文档对象模型):规定了访问HTML和XML的接口
  • BOM(浏览器对象模型):提供了浏览器窗口之间进行交互的对象和方法

检测浏览器版本版本有哪些方式?

  • 根据 navigator.userAgent UA.toLowerCase().indexOf('chrome')
  • 根据 window 对象的成员'ActiveXObject' in window

介绍JS有哪些内置对象

  • 数据封装类对象:Object、Array、Boolean、Number、String
  • 其他对象:Function、Arguments、Math、Date、RegExp、Error
  • ES6新增对象:Symbol、Map、Set、Promises、Proxy、Reflect

描述浏览器的渲染过程,DOM树和渲染树的区别

浏览器的渲染过程:

  • 解析HTML构建 DOM(DOM树),并行请求css/image/js
  • CSS文件下载完成,开始构建CSSOM(CSS树)
  • CSSOM构建结束后,和 DOM 一起生成 Render Tree(渲染树)
  • 布局(Layout):计算出每个节点在屏幕中的位置
  • 显示(Painting):通过显卡把页面画到屏幕上

DOM树 和 渲染树 的区别:

  • DOM树与HTML标签一一对应,包括head和隐藏元素
  • 渲染树不包括head和隐藏元素,大段文本的每一个行都是独立节点,每一个节点都有对应的css属性

script 的位置是否会影响首屏显示时间

  • 首屏时间和DomContentLoad事件没有必然的先后关系
  • 所有CSS尽早加载是减少首屏时间的最关键
  • js的下载和执行会阻塞Dom树的构建(严谨地说是中断了Dom树的更新),所以script标签放在首屏范围内的HTML代码段里会截断首屏的内容。
  • script标签放在body底部,做与不做async或者defer处理,都不会影响首屏时间,但影响DomContentLoadload的时间,进而影响依赖他们的代码的执行的开始时间。

结论

  • 不影响,如果这里里的首屏指的是页面从白板变成网页画面——也就是第一次Painting,但有可能截断首屏的内容,使其只显示上面一部分。
  • 为什么说是“有可能”呢?,如果该js下载地比css还快,或者script标签不在第一屏的html里,实际上是不影响的。明白这一影响边界非常重要,这样我们在考察页面性能瓶颈的时候就有的放矢了。

解释JavaScript中的作用域与变量声明提升

JavaScript作用域:

JavaScript中,作用域为function(){}内的区域,称为函数作用域

JavaScript变量声明提升:

  • JavaScript中,函数声明与变量声明经常被JavaScript引擎隐式地提升到当前作用域的顶部。
  • 声明语句中的赋值部分并不会被提升,只有名称被提升
  • 函数声明的优先级高于变量,如果变量名跟函数名相同且未赋值,则函数声明会覆盖变量声明
  • 如果函数有多个同名参数,那么最后一个参数(即使没有定义)会覆盖前面的同名参数

JavaScript有几种类型的值?,你能画一下他们的内存图吗

  • 原始数据类型(Undefined,Null,Boolean,Number、String)-- 栈
  • 引用数据类型(对象、数组和函数)-- 堆
  • 两种类型的区别是:存储位置不同:
    • 原始数据类型是直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据;
    • 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;
  • 引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。
  • 当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

JavaScript如何实现一个类,怎么实例化这个类

1. 构造函数法(this + prototype) – 用 new 关键字 生成实例对象

  • 缺点:用到了 this 和 prototype,编写复杂,可读性差
function Mobile(name, price){
  this.name = name;
  this.price = price;
}
Mobile.prototype.sell = function(){
  alert(this.name + ",售价 $" + this.price);
}
var iPhone7 = new Mobile("iPhone7", 1000);
iPhone7.sell();

2. Object.create 法 – 用 Object.create() 生成实例对象

  • 缺点:不能实现私有属性和私有方法,实例对象之间也不能共享数据
 var Person = {
     firstname: "Mark",
     lastname: "Yun",
     age: 25,
     introduce: function(){
         alert('I am ' + Person.firstname + ' ' + Person.lastname);
     }
 };

 var person = Object.create(Person);
 person.introduce();

 // Object.create 要求 IE9+,低版本浏览器可以自行部署:
 if (!Object.create) {
    Object.create = function (o) {
    function F() {}
    F.prototype = o;
    return new F();
  };
 }

3. 极简主义法(消除 this 和 prototype) – 调用 createNew() 得到实例对象

  • 优点:容易理解,结构清晰优雅,符合传统的"面向对象编程"的构造
var Cat = {
   age: 3, // 共享数据 -- 定义在类对象内,createNew() 外
   createNew: function () {
     var cat = {};
     // var cat = Animal.createNew(); // 继承 Animal 类
     cat.name = "小咪";
     var sound = "喵喵喵"; // 私有属性--定义在 createNew() 内,输出对象外
     cat.makeSound = function () {
       alert(sound);  // 暴露私有属性
     };
     cat.changeAge = function(num){
       Cat.age = num; // 修改共享数据
     };
     return cat; // 输出对象
   }
 };

 var cat = Cat.createNew();
 cat.makeSound();

4. ES6 语法糖 class – 用 new 关键字 生成实例对象

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

var point = new Point(2, 3);

Javascript如何实现继承

介绍 DOM 的发展

  • DOM:文档对象模型(Document Object Model),定义了访问HTMLXML文档的标准,与编程语言及平台无关
  • DOM0:提供了查询和操作Web文档的内容API。未形成标准,实现混乱。如:document.forms['login']
  • DOM1W3C提出标准化的DOM,简化了对文档中任意部分的访问和操作。如:JavaScript中的Document对象
  • DOM2:原来DOM基础上扩充了鼠标事件等细分模块,增加了对CSS的支持。如:getComputedStyle(elem, pseudo)
  • DOM3:增加了XPath模块和加载与保存(Load and Save)模块。如:XPathEvaluator

介绍DOM0,DOM2,DOM3事件处理方式区别

DOM0级事件处理方式:

btn.onclick = func;
btn.onclick = null;

DOM2级事件处理方式:

btn.addEventListener('click', func, false);
btn.removeEventListener('click', func, false);
btn.attachEvent("onclick", func);
btn.detachEvent("onclick", func);

DOM3级事件处理方式:

eventUtil.addListener(input, "textInput", func);
eventUtil是自定义对象,textInputDOM3级事件

W3C事件的 target 与 currentTarget 的区别?

  • target 只会出现在事件流的目标阶段
  • currentTarget可能出现在事件流的任何阶段
  • 当事件流处在目标阶段时,二者的指向相同
  • 当事件流处于捕获或冒泡阶段时:currentTarget指向当前事件活动的对象(一般为父级)

什么是函数节流?介绍一下应用场景和原理?

  • 函数节流(throttle)是指阻止一个函数在很短时间间隔内连续调用
  • 函数节流的原理:使用定时器做时间节流。 当触发一个事件时,先用 setTimout 让这个事件延迟一小段时间再执行。 如果在这个时间间隔内又触发了事件,就 clearTimeout 原来的定时器, 再setTimeout一个新的定时器重复以上流程。
function throttle(method, context) {
     clearTimeout(methor.tId);
     method.tId = setTimeout(function(){
         method.call(context);
     }100); // 两次调用至少间隔 100ms
}
// 调用
window.onresize = function(){
  throttle(myFunc, window);
}

区分什么是“客户区坐标”、“页面坐标”、“屏幕坐标”

  • 客户区坐标:鼠标指针在可视区中的水平坐标(clientX)和垂直坐标(clientY)
  • 页面坐标:鼠标指针在页面布局中的水平坐标(pageX)和垂直坐标(pageY)
  • 屏幕坐标:设备物理屏幕的水平坐标(screenX)和垂直坐标(screenY)

如何获得一个DOM元素的绝对位置?

  • elem.offsetLeft:返回元素相对于其定位父级左侧的距离
  • elem.offsetTop:返回元素相对于其定位父级顶部的距离
  • elem.getBoundingClientRect():返回一个DOMRect对象,包含一组描述边框的只读属性,单位像素

解释一下这段代码的意思

[].forEach.call($$("*"), function(el){
     el.style.outline = "1px solid #" + (~~(Math.random()*(1<<24))).toString(16);
 })

解释:获取页面所有的元素,遍历这些元素,为它们添加1像素随机颜色的轮廓(outline)

$$(sel) // $$函数被许多现代浏览器命令行支持,等价于 document.querySelectorAll(sel)
[].forEach.call(NodeLists) // 使用 call 函数将数组遍历函数 forEach 应到节点元素列表
el.style.outline = "1px solid #333"// 样式 outline 位于盒模型之外,不影响元素布局位置
(1<<24) // parseInt("ffffff", 16) == 16777215 == 2^24 - 1// 1<<24 == 2^24 == 16777216
Math.random()*(1<<24) // 表示一个位于 0 到 16777216 之间的随机浮点数
~~Math.random()*(1<<24) // ~~ 作用相当于 parseInt 取整
(~~(Math.random()*(1<<24))).toString(16) // 转换为一个十六进制-

Javascript垃圾回收方法

标记清除(mark and sweep)

  • 这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”
  • 垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了

引用计数(reference counting)

在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间

如何删除一个

为了删除一个cookie,可以将其过期时间设定为一个过去的时间 。

var date = new Date();
date.setDate(date.getDate() - 1);//真正的删除,setDate()方法用于设置一个月的某一天
document.cookie = 'user='+ encodeURIComponent('name')  + ';expires = ' + new Date(0)

页面编码和被请求的资源编码如果不一致如何处理

  • 后端响应头设置 charset
  • 前端页面<meta>设置 charset

<script>放在</body>之前和之后有什么区别?浏览器会如何解析它们?

  • 按照HTML标准,在</body>结束后出现<script>或任何元素的开始标签,都是解析错误
  • 虽然不符合HTML标准,但浏览器会自动容错,使实际效果与写在</body>之前没有区别
  • 浏览器的容错机制会忽略<script>之前的</body>,视作<script>仍在 body 体内。省略</body></html>闭合标签符合HTML标准,服务器可以利用这一标准尽可能少输出内容

JavaScript 中,调用函数有哪几种方式

  • 方法调用模式 Foo.foo(arg1, arg2);
  • 函数调用模式foo(arg1, arg2);
  • 构造器调用模式(new Foo())(arg1, arg2);
  • call/applay调用模式 Foo.foo.call(that, arg1, arg2);
  • bind调用模式Foo.foo.bind(that)(arg1, arg2)();

bind() 方法的简单实现

参考链接:https://blog.csdn.net/colorfulsekai/article/details/113782970

  • bind()方法创建一个新的函数,在 bind()被调用时,这个新函数的this 被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
  • call() 方法不同的是,call()方法只会临时改变一次this指向,并立即执行,而bind()方法是返回一个this改变后的函数,并且不会立即执行。
(function () {
    Function.prototype.bind = function () {
        let args = arguments;
        return () => {
            return this.apply(args[0], [...args].slice(1));
        };
    };
})();
解释

args获取 bind()函数参数伪数组的引用,由于是伪数组,并不能直接调用slice()方法,所以通过解构赋值转成真数组后调用slice()方法,将bind()方法第二个及之后的参数合并为数组并传给 apply() 函数作为第二个参数

  • 由于 this 要指向调用 bind() 的函数,故只能采用箭头函数的形式,也可以写成如下形式:
(function () {
    Function.prototype.bind = function () {
        let that = this, args = arguments;
        return function() {
            return that.apply(args[0], [...args].slice(1));
        };
    };
})();

列举一下JavaScript数组和对象有哪些原生方法?

数组

arr.concat(arr1,arr2,arrn);
arr.join(",");
arr.sort(func);
arr.pop();
arr.push(e1,e2,en);
arr.shift();
unishift(e1,e2,en);
arr.reverse();
arr.slice(start,end);
arr.splice(index,count,e1,e2,en);
arr.indexOf(e1);
arr.includes(e1);//ES6

对象

object.hasOwnProperty(prop);
object.propertyIsEnumerale(prop);
object.valueOf();
object.toString();
object.toLocaleString();
object.prototype.isPropertyOf(object);

MVVM

MVVM 由以下三个内容组成

  1. View:界面
  2. Model:数据模型
  3. ViewModel:作为桥梁负责沟通 View 和 Model
  • 在 JQuery 时期,如果需要刷新 UI 时,需要先取到对应的 DOM 再更新 UI,这样数据和业务的逻辑就和页面有强耦合
  • 在 MVVM 中,UI 是通过数据驱动的,数据一旦改变就会相应的刷新对应的 UI,UI 如果改变,也会改变对应的数据。这种方式就可以在业务处理中只关心数据的流转,而无需直接和页面打交道。ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下,View 和 Model 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel
  • 在 MVVM 中,最核心的也就是数据双向绑定

WEB应用从服务器主动推送Data到客户端有那些方式

  • AJAX 轮询
  • html5 服务器推送事件 (new EventSource(SERVER_URL)).addEventListener(“message”, func);
  • html5 Websocket
    (newWebSocket(SERVER_URL)).addEventListener(“message”, func);

判断是否是数组

  • Array.isArray(arr)
  • Object.prototype.toString.call(arr) === '[Object Array]'
  • arr instanceof Array
  • array.constructor === Array

加载

1. 异步加载js的方法

  • defer:只支持IE如果您的脚本不会改变文档的内容,可将 defer属性加入到<script>标签中,以便加快处理文档的速度。因为浏览器知道它将能够安全地读取文档的剩余部分而不用执行脚本,它将推迟对脚本的解释,直到文档已经显示给用户为止
  • async:HTML5属性,仅适用于外部脚本;并且如果在IE中,同时存在deferasync,那么defer的优先级比较高;脚本将在页面完成时执行

2. 图片的懒加载和预加载

  • 预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
  • 懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数
  • 两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。

有四个操作会忽略enumerable为false的属性

  • for...in循环:只遍历对象自身的和继承的可枚举的属性。
  • Object.keys():返回对象自身的所有可枚举的属性的键名。
  • JSON.stringify():只串行化对象自身的可枚举的属性。
  • Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

ES6 一共有 5 种方法可以遍历对象的属性。

(1)for…in

for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol属性)。

(2)Object.keys(obj)

Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

(3)Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)的键名。

(4)Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

(5)Reflect.ownKeys(obj)

Reflect.ownKeys返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol或字符串,也不管是否可枚举。

以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。

首先遍历所有数值键,按照数值升序排列。
其次遍历所有字符串键,按照加入时间升序排列。
最后遍历所有 Symbol 键,按照加入时间升序排列。

在输入框中如何判断输入的是一个正确的网址

function isUrl(url) {
       try {
           new URL(url);
           return true;
       }catch(err){
     return false;
}}

为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图片

  • 能够完成整个 HTTP 请求+响应(尽管不需要响应内容)
  • 触发 GET 请求之后不需要获取和处理数据、服务器也不需要发送数据
  • 跨域友好
  • 执行过程无阻塞
  • 相比 XMLHttpRequest 对象发送 GET 请求,性能上更好
  • GIF的最低合法体积最小(最小的BMP文件需要74个字节,PNG需要67个字节,而合法的GIF,只需要43个字节)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值