前端知识杂烩(Javascript篇)

Posted 前端矿场常年招矿工啦啦啦 - muyuxingguang@

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端知识杂烩(Javascript篇)相关的知识,希望对你有一定的参考价值。

1. javascript是一门什么样的语言,它有什么特点?

JavaScript 是一种脚本语言,官方名称为 ECMAScript(因定义语言的标准为 ECMA-262)。JS 的主要特点:1. 语法类似于常见的高级语言,如 C 和 Java;2. 脚本语言,不需要编译就可以由解释器直接运行;3. 变量松散定义,属于弱类型语言;4. 面向对象的。 JS 最初是为网页设计而开发的,现在也是 Web 开发的重要语言。它支持对浏览器(浏览器对象模型,BOM)和 html 文档(文档对象模型,DOM)进行操作,而使网页呈现动态的交互特性。 严格的说,JS 只是 ECMAScript 的一种实现,是 ECMAScript 和 BOM、DOM 组成的一种 Web 开发技术。

2.JavaScript的数据类型都有什么?

  基本数据类型:String,Boolean,Number,Undefined, Null
  引用数据类型:Object(Array,Date,RegExp,Function)
  那么问题来了,如何判断某变量是否为数组数据类型?

  • 方法一.判断其是否具有“数组性质”,如slice()方法。可自己给该变量定义slice方法,故有时会失效
  • 方法二.obj instanceof Array 在某些IE版本中不正确
  • 方法三.方法一二皆有漏洞,在ECMA Script5中定义了新方法Array.isArray(), 保证其兼容性,最好的方法如下:
  1. function isArray(value){
  2. return Object.prototype.toString.call(value) == "[object Array]";
  3. }

3.请描述一下 cookies,sessionStorage 和 localStorage 的区别?

cookie是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。cookie数据始终在同源的http请求中携带(即使不需要),记会在浏览器和服务器间来回传递。sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。

  • 存储大小:
    (1) cookie数据大小不能超过4k。
    (2)sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
  • 有期时间:
    (1) localStorage存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;
    (2)sessionStorage 数据在当前浏览器窗口关闭后自动删除。
    (3) cookie设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭

4.webSocket如何兼容低浏览器?(阿里)

  • Adobe Flash Socket 、
  • ActiveX HTMLFile (IE) 、
  • 基于 multipart 编码发送 XHR 、
  • 基于长轮询的 XHR

5.this和它声明环境无关,而完全取决于他的执行环境

  1. var name = ‘罗恩’;
  2. var aaa = {
  3. name: ‘哈利’,
  4. say: function () {
  5. console.log(this.name);
  6. }
  7. }
  8. var bbb = {
  9. name: ‘赫敏’,
  10. say: aaa.say
  11. }
  12. var ccc = aaa.say;
  13. aaa.say(); //哈利
  14. bbb.say(); //赫敏
  15. ccc(); //罗恩

6.JavaScript异步编程常用的四种方法

  • 1.回调函数
    f1(f2);
    回调函数是异步编程的基本方法。其优点是易编写、易理解和易部署;缺点是不利于代码的阅读和维护,各个部分之间高度耦合 (Coupling),流程比较混乱,而且每个任务只能指定一个回调函数。
  • 2.事件监听
    f1.on(\'done\',f2);
    事件监听即采用事件驱动模式,任务的执行不取决于代码的顺序,而取决于某个事件是否发生。其优点是易理解,可以绑定多个事件,每个事件可以指定多个回调函数,可以去耦合, 有利于实现模块化;缺点是整个程序都要变成事件驱动型,运行流程会变得不清晰。
  • 3.发布/订阅
    f1: jQuery.publish("done");
    f2: jQuery.subscribe("done", f2);
    假定存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行,这就叫做 "发布/订阅模式" (publish-subscribe pattern),又称 "观察者模式" (observer pattern)。该 方法的性质与"事件监听"类似,但其优势在于可以 通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
  • 4.promise对象
    f1().then(f2);
    Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供 统一接口 ;思想是, 每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。其优点是回调函数是链式写法,程序的流程非常清晰,而且有一整套的配套方法, 可以实现许多强大的功能,如指定多个回调函数、指定发生错误时的回调函数, 如果一个任务已经完成,再添加回调函数,该回调函数会立即执行,所以不用担心是否错过了某个事件或信号;缺点就是编写和理解相对比较难。

7、在严格模式(\'use strict\')下进行 JavaScript 开发有神马好处?

  • 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
  • 消除代码运行的一些不安全之处,保证代码运行的安全;
  • 提高编译器效率,增加运行速度;
  • 为未来新版本的Javascript做好铺垫。

8、神马是 NaN,它的类型是神马?怎么测试一个值是否等于 NaN?

NaN 是 Not a Number 的缩写,JavaScript 的一种特殊数值,其类型是 Number,可以通过 isNaN(param) 来判断一个值是否是 NaN

  1. console.log(isNaN(NaN)); //true
  2. console.log(isNaN(23)); //false
  3. console.log(isNaN(\'ds\')); //true
  4. console.log(isNaN(\'32131sdasd\')); //true
  5. console.log(NaN === NaN); //false
  6. console.log(NaN === undefined); //false
  7. console.log(typeof NaN); //number
  8. console.log(Object.prototype.toString.call(NaN)); //[object Number]

ES6 中,isNaN() 成为了 Number 的静态方法:Number.isNaN()


9、解释一下下面代码的输出

  1. console.log(0.1 + 0.2); //0.30000000000000004
  2. console.log(0.1 + 0.2 == 0.3); //false

JavaScript 中的 number 类型就是浮点型,JavaScript 中的浮点数采用IEEE-754 格式的规定,这是一种二进制表示法,可以精确地表示分数,比如1/2,1/8,1/1024,每个浮点数占64位。但是,二进制浮点数表示法并不能精确的表示类似0.1这样 的简单的数字,会有舍入误差。
由于采用二进制,JavaScript 也不能有限表示 1/10、1/2 等这样的分数。在二进制中,1/10(0.1)被表示为0.00110011001100110011…… 注意 0011 是无限重复的,这是舍入误差造成的,所以对于 0.1 + 0.2 这样的运算,操作数会先被转成二进制,然后再计算:

  1. 0.1 => 0.0001 1001 1001 1001…(无限循环)
  2. 0.2 => 0.0011 0011 0011 0011…(无限循环)

双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串 0.0100110011001100110011001100110011001100…因浮点数小数位的限制而截断的二进制数字,这时候,再把它转换为十进制,就成了 0.30000000000000004。
对于保证浮点数计算的正确性,有两种常见方式。

  • 一是先升幂再降幂:
  1. function add(num1, num2){
  2. let r1, r2, m;
  3. r1 = (\'\'+num1).split(\'.\')[1].length;
  4. r2 = (\'\'+num2).split(\'.\')[1].length;
  5. m = Math.pow(10,Math.max(r1,r2));
  6. return (num1 * m + num2 * m) / m;
  7. }
  8. console.log(add(0.1,0.2)); //0.3
  9. console.log(add(0.15,0.2256)); //0.3756
  • 二是是使用内置的 toPrecision()toFixed() 方法,**注意,方法的返回值字符串。
  1. function add(x, y) {
  2. return x.toPrecision() + y.toPrecision()
  3. }
  4. console.log(add(0.1,0.2)); //"0.10.2"

10、实现函数 isInteger(x) 来判断 x 是否是整数

可以将 x 转换成10进制,判断和本身是不是相等即可:

  1. function isInteger(x) {
  2. return parseInt(x, 10) === x;
  3. }

ES6 对数值进行了扩展,提供了静态方法 isInteger() 来判断参数是否是整数:

  1. Number.isInteger(25) // true
  2. Number.isInteger(25.0) // true
  3. Number.isInteger(25.1) // false
  4. Number.isInteger("15") // false
  5. Number.isInteger(true) // false

JavaScript能够准确表示的整数范围在 -2^532^53 之间(不含两个端点),超过这个范围,无法精确表示这个值。ES6 引入了Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限,并提供了 Number.isSafeInteger() 来判断整数是否是安全型整数。


11.前端模块化-AMD(异步模块定义)规范与CMD(通用模块定义)规范(期待ES6模块一统天下)

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。主要区别是

  • 1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.

    AMD和CMD最大的区别是对依赖模块的执行时机处理不同,注意不是加载的时机或者方式不同
    很多人说requireJS是异步加载模块,SeaJS是同步加载模块,这么理解实际上是不准确的,其实加载模块都是异步的,只不过AMD依赖前置,js可以方便知道依赖模块是谁,立即加载,而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略
    为什么我们说两个的区别是依赖模块执行时机不同,为什么很多人认为AMD是异步的,CMD是同步的(除了名字的原因。。。)
    同样都是异步加载模块,AMD在加载模块完成后就会执行改模块,所有模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行
    CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的
    这也是很多人说AMD用户体验好,因为没有延迟,依赖模块提前执行了,CMD性能好,因为只有用户需要的时候才执行的原因

  • 2. CMD 推崇依赖就近,只有在用到某个模块的时候再去require;AMD 推崇依赖前置在定义模块的时候就要声明其依赖的模块。看代码:
  • 3.AMD推荐的风格通过返回一个对象做为模块对象,CommonJS的风格通过对module.exportsexports的属性赋值来达到暴露模块对象的目的。

    顺便提一下:CommonJS是适用于服务器端的规范,NodeJS即是它的一种实现,CommonJS定义的模块分为:{模块引用(require)} {模块定义(exports)} {模块标识(module)}。 require()用来引入外部模块;exports对象用于导出当前模块的方法或变量,唯一的导出口;module对象就代表模块本身。

  1. //CommonJS规范写法
  2. //sum.js
  3. exports.sum = function(){//做加操作};
  4. //calculate.js
  5. var math = require(\'sum\');
  6. exports.add = function(n){
  7. return math.sum(val,n);
  8. };
  1. // CMD
  2. define(function(require, exports, module) {
  3. var a = require(\'./a\')
  4. a.doSomething()
  5. // 此处略去 100 行
  6. var b = require(\'./b\') // 依赖可以就近书写
  7. b.doSomething();
  8. //本模块的导出接口
  9. exports.each = function (arr) {
  10. // 实现代码
  11. };
  12. exports.log = function (str) {
  13. // 实现代码
  14. };
  15. })
  1. // AMD就只有一个接口:define(id?,dependencies?,factory);
  2. define([\'./a\', \'./b\'], function(a, b) { // 依赖必须一开始就写好
  3. a.doSomething()
  4. // 此处略去 100 行
  5. b.doSomething();
  6. //返回本模块的导出接口
  7. var myModule = {
  8. doStuff:function(){
  9. console.log(\'Yay! Stuff\');
  10. }
  11. }
  12. return myModule;
  13. ...
  14. })
  15. //AMD还有一个require方法主要用来在顶层 JavaScript 文件中或须要动态读取依赖时加载代码
  16. require([\'foo\', \'bar\'], function ( foo, bar ) {
  17. // 这里写其余的代码
  18. foo.doSomething();
  19. //返回本模块的导出接口
  20. var myModule = {
  21. doStuff:function(){
  22. console.log(\'Yay! Stuff\');
  23. }
  24. }
  25. return myModule;
  26. });

12.JS跨域汇总

1.通过jsonp跨域

  • 只能使用 GET 方法发起请求,这是由于 script 标签自身的限制决定的。
  • 不能很好的发现错误,并进行处理。与 Ajax 对比,由于不是通过 XmlHttpRequest 进行传输,所以不能注册 success、 error 等事件监听函数。

2.通过修改document.domain来跨子域(iframe)
3.隐藏的iframe+window.name跨域
4.iframe+跨文档消息传递(XDM)
5.跨域资源共享 CORS

  • CORS 除了 GET 方法外,也支持其它的 HTTP 请求方法如 POST、 PUT 等。
  • CORS 可以使用 XmlHttpRequest 进行传输,所以它的错误处理方式比 JSONP 好。
  • JSONP 可以在不支持 CORS 的老旧浏览器上运作。

6.Web Sockets

跨域请求并非是浏览器限制了发起跨站请求,而是请求可以正常发起,到达服务器端,但是服务器返回的结果会被浏览器拦截。


13.两张图让你看懂“==”与if()


14.JS中创建对象的几种方式(此处只列举,详情见红宝书《JS高级程序设计》)

1.对象字面量
2.Object构造函数

mygirl=new Object();
ES5新方法构建对象,再添加属性

3.工厂模式

工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

4.构造函数模式

使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。

5.原型模式

function Girl(){
}
在Girl.prototype上添加属性和方法
var mygirl=new Girl();
特点:原型中所有属性是被所有实例共享的,这种共享对于函数非常合适,但是对于基本属性就显得不是很合适,尤其是对于组合使用原型模式和构造函数创建对象包含引用类型值的属性来说,问题就比较突出了。

6.组合使用原型模式和构造函数创建对象(推荐)

创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。

7.动态原型模式

相对于组合模式,就是把原型上添加方法的步骤放在构造函数中,然后根据构造函数中是否已经存在该方法来决定添不添加

8.寄生构造函数模式

相对于工厂模式就是把函数当做构造函数调用

9.稳妥构造函数模式


15.JS中实现继承的几种方式(此处只列举,详情见红宝书《JS高级程序设计》)

1.借助原型链

  1. function SuperType(){
  2. this.colors = ["red", "blue", "green"];
  3. }
  4. function SubType(){
  5. }
  6. //继承了 SuperType
  7. SubType.prototype = new SuperType();

使用原型链会出现两个问题,一是如果原型中包含引用类型值,子类所有实例会共享这个引用类型;二是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

2.借助构造函数

  1. function SuperType(name){
  2. this.name = name;
  3. }
  4. function SubType(){
  5. //继承了 SuperType,同时还传递了参数
  6. SuperType.call(this, "Nicholas");
  7. //实例属性
  8. this.age = 29;
  9. }

能够解决第一种方法原型中包含引用类型值所带来问题,也能向超类型构造函数传递参数。问题是如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。

3.组合继承(推荐)
组合继承( combination inheritance),有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。下面来看一个例子。

  1. function SuperType(name){
  2. this.name = name;
  3. this.colors = ["red", "blue", "green"];
  4. }
  5. SuperType.prototype.sayName = function(){
  6. alert(this.name);
  7. };
  8. function SubType(name, age){
  9. //继承属性
  10. SuperType.call(this, name);
  11. this.age = age;
  12. }
  13. //继承方法
  14. SubType.prototype = new SuperType();
  15. SubType.prototype.constructor = SubType;
  16. SubType.prototype.sayAge = function(){
  17. alert(this.age);
  18. };
  19. var instance1 = new SubType("Nicholas", 29);
  20. instance1.colors.push("black");
  21. alert(instance1.colors); //"red,blue,green,black"
  22. instance1.sayName(); //"Nicholas";
  23. instance1.sayAge(); //29
  24. var instance2 = new SubType("Greg", 27);
  25. alert(instance2.colors); //"red,blue,green"
  26. instance2.sayName(); //"Greg";
  27. instance2.sayAge(); //27

4.原型式继承

  1. function object(o){
  2. function F(){}
  3. F.prototype = o;
  4. return new F();
  5. }

在 object() 函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。ECMAScript 5 通过新增 Object.create() 方法规范化了原型式继承。

  1. var person = {
  2. name: "Nicholas",
  3. friends: ["Shelby", "Court", "Van"]
  4. };
  5. var anotherPerson = Object.create(person);
  6. anotherPerson.name = "Greg";
  7. anotherPerson.friends.push("Rob");
  8. alert(person.friends); //"Shelby,Court,Van,Rob"

在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。不过别忘了,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。

5.寄生式继承

  1. function createAnother(original){
  2. var clone = object(original); //通过调用函数创建一个新对象,也可以使用其他类似的方法
  3. clone.sayHi = function(){ //以某种方式来增强这个对象
  4. alert("hi");
  5. };
  6. return clone; //返回这个对象
  7. }

6.寄生组合式继承

  1. function inheritPrototype(subType, superType){
  2. var prototype = object(superType.prototype); //创建对象
  3. prototype.constructor = subType; //增强对象
  4. subType.prototype = prototype; //指定对象
  5. }

16.JS中函数的几种创建形式。

1、声明函数
最普通最标准的声明函数方法,包括函数名及函数体。

  1. function fn1(){}

2、创建匿名函数表达式
创建一个变量,这个变量的内容为一个函数

  1. var fn1=function (){}

注意采用这种方法创建的函数为匿名函数,即没有函数name
3、创建具名函数表达式
创建一个变量,内容为一个带有名称的函数
var fn1=function xxcanghai(){};
注意:具名函数表达式的函数名只能在创建函数内部使用
4、Function构造函数
可以给 Function 构造函数传一个函数字符串,返回包含这个字符串命令的函数,此种方法创建的是匿名函数。

5、自执行函数
(function(){alert(1);})();
(function fn1(){alert(1);})();
自执行函数属于上述的“函数表达式”,规则相同
参考:http://www.cnblogs.com/xxcanghai/p/4991870.html


17.call与apply的异同?

call方法与apply方法的作用是一样的,都是为了改变函数内部的this指向。区别仅在于传入的参数形式的不同。
apply函数接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个可以下标访问的集合,这个集合可以使数组,也可以是类数组,apply方法把这个集合中的元素作为参数传递给被调用的函数。
call方法传入的参数数量是不固定的,跟apply相同的是,第一个参数也是代表函数体内this对象的指向,从第二个参数开始往后,是一组参数序列,每个参数被依次传入函数。
Notes

  • call方法不是说不能接受数组做参数,而是将数组参数当做一个整体,作为参数序列的一部分,而apply方法是将数组中的元素当做参数
  • call和apply第一个参数为null时,函数体内的this指向宿主对象,浏览器中则为window,在严格模式下,仍为null.

18.JavaScript中常见的内存泄漏及解决方案

现代的浏览器大多采用标记清除的方法来进行垃圾回收,其基本步骤如下:

  1. 垃圾回收器创建了一个“roots”列表。Roots 通常是代码中全局变量的引用。JavaScript 中,“window” 对象是一个全局变量,被当作 root 。window 对象总是存在,因此垃圾回收器可以检查它和它的所有子对象是否存在(即不是垃圾);
  2. 所有的 roots 被检查和标记为激活(即不是垃圾)。所有的子对象也被递归地检查。从 root 开始的所有对象如果是可达的,它就不被当作垃圾。
  3. 所有未被标记的内存会被当做垃圾,收集器现在可以释放内存,归还给操作系统了。
    内存泄漏主因是不需要的引用未被及时清除。下列列举几种常见的内存泄漏及其解决方案