玩转javascript---知识点汇总(2)

308 阅读15分钟

变量提升和函数提升

在javaScript 中,函数及变量的声明都将被提升到函数的最顶部。变量可以在使用后声明,也就是变量可以先使用再声明。

x = 5; // 变量 x 设置为 5

elem = document.getElementById("demo"); // 查找元素 
elem.innerHTML = x;                     // 在元素中显示 x

var x; // 声明 x

两个实例效果一样

var x; // 声明 x
x = 5; // 变量 x 设置为 5

elem = document.getElementById("demo"); // 查找元素 
elem.innerHTML = x;                     // 在元素中显示 x

要注意javascript只有未初始化的变量才会提升,初始化的不会提升。如下y输出undefined。

var x = 5; // 初始化 x

elem = document.getElementById("demo"); // 查找元素 
elem.innerHTML = x + " " + y;           // 显示 x 和 y

var y = 7; // 初始化 y

注意:JavaScript 严格模式(strict mode)不允许使用未声明的变量。

js中创建函数有多种,其中只有函数声明式才存在函数提升!如下面f2为函数字面量式,不会发生函数提升:

console.log(f1); // function f1() {}   console.log(f2); // undefined  function f1() {}var f2 = function() {}

只所以会有以上的打印结果,是由于js中的函数提升导致代码实际上是按照以下来执行的:

function f1() {} // 函数提升,整个代码块提升到文件的最开始
console.log(f1);   console.log(f2);   var f2 = function() {}

  • attribute和property的区别

  • property是DOM中的属性,是JavaScript里的对象;
  • attribute是HTML标签上的特性,它的值只能够是字符串;

如下:

<input id="in_1" value="1" sth="whatever">//这个标签中添加了一个DOM中不存在的属性“sth”
<script>
var in1 = document.getElementById('in_1');
console.log(in1);  
</script> 

从console的打印结果,可以看到in1含有一个名为“attributes”的属性,它的类型是NamedNodeMap,同时还有“id”和“value”两个基本的属性,但没有“sth”这个自定义的属性。

attributes: NamedNodeMap
value: "1"
id: "in_1"

可以发现,标签中的三个属性,只有“id”和“value”会在in1上创建,而“sth”不会被创建。这是由于,每一个DOM对象都会有它默认的基本属性,而这些属性就是所谓的“property”而在创建的时候,它只会创建这些基本属性,我们在TAG标签中自定义的属性是不会直接放到DOM中的。

现在试着执行以下语句:

console.log(in1.attibutes.sth);     // 'sth="whatever"'

由此可以看到HTML标签中定义的属性和值会保存该DOM对象的attributes属性里面;

这些attribute属性的JavaScript中的类型是Attr,而不仅仅是保存属性名和值这么简单;

那么,如果我们更改property和attribute的值会出现什么效果呢?执行如下语句:

in1.value = 'new value of prop';
console.log(in1.value);             // 'new value of prop'
console.log(in1.attributes.value);  // 'value="1"'

此时,页面中的输入栏的值变成了“new value of prop”,而propety中的value也变成了新的值,但attributes却仍然是“1”。从这里可以推断,property和attribute的同名属性的值并不是双向绑定的。

如果反过来,设置attitudes中的值,效果会怎样呢?

in1.attributes.value.value = 'new value of attr';
console.log(in1.value);             // 'new value of attr'
console.log(in1.attributes.value);  // 'new value of attr'

此时,页面中的输入栏得到更新,property中的value也发生了变化。此外,执行下面语句也会得到一样的结果

in1.attributes.value.nodeValue = 'new value of attr';

由此,可得出结论:

  • property能够从attribute中得到同步;
  • attribute不会同步property上的值;
  • attribute和property之间的数据绑定是单向的,attribute->property;
  • 更改property和attribute上的任意值,都会将更新反映到HTML页面中;
  • document load和document DOMContentLoaded两个事件的区别


(1)在chrome浏览器的开发过程中,我们会看到network面板中有这两个数值,分别对应网 络请求上的标志线,这两个时间数值分别代表什么?

(2)我们一再强调将css放在头部,将js文件放在尾部,这样有利于优化页面的性能,为什么这种方式能够优化性能?

(3)在用jquery的时候,我们一般都会将函数调用写在ready方法内,这是什么原理?

DOMContentLoaded顾名思义,就是dom内容加载完毕。那什么是dom内容加载完毕呢?我们从打开一个网页说起。当输入一个URL,页面的展示首先是空白的,然后过一会,页面会展示出内容,但是页面的有些资源比如说图片资源还无法看到,此时页面是可以正常的交互,过一段时间后,图片才完成显示在页面。从页面空白到展示出页面内容,会触发DOMContentLoaded事件。而这段时间就是HTML文档被加载和解析完成。即览器解析完文档便能触发 DOMContentLoaded 事件,不需要等待图片等其他资源加载完成。

而对于load事件来说,页面上所有的资源(图片,音频,视频等)被加载以后才会触发load事件,简单来说,页面的load事件会在DOMContentLoaded被触发之后才触发。

我们在 jQuery 中经常使用的 $(document).ready(function() { // ...代码... }); 其实监听的就是 DOMContentLoaded 事件,而$(document).load(function() { // ...代码... }); 监听的是 load 事件。在用jquery的时候,我们一般都会将函数调用写在ready方法内,就是页面被解析后,我们就可以访问整个页面的所有dom元素,可以缩短页面的可交互时间,提高整个页面的体验。

 onload事件所有的浏览器都支持,所以在使用时我们不需要什么兼容,而DOMContentLoaded不同的浏览器对其支持不同,所以在实现的时候我们需要做不同浏览器的兼容。IE6、IE7不支持DOMContentLoaded,但它支持onreadystatechange事件,该事件的目的是提供与文档或元素的加载状态有关的信息。可用该事件代替事件。

  • === 和 ==

在javascript中“==”与“===”是不相同的,一个是判断值是否相等,一个是判断值及类型是否完全相等。

下面的规则用于判定===运算符比较的两个值是否相等的判断条件

•如果两个值的类型不同,它们就不相同。

•如果两个值是数字,而且值相同,那么除非其中一个或两个都是NaN(这种情况它们不是等同的),否则它们是等同的。值NaN永远不会与其他任何值等同,包括它自身(奇怪的家伙),要检测一个值是否是NaN,可以使用全局函数isNaN()。

•如果两个值都是字符串,而且在串中同一位置上的字符完全相同,那么它们就完全等同。如果字符串的长度或内容不同,它们就不是等同的。

•如果两个值引用的是同一个对象、数组或函数,那么它们完全等同。如果它们引用的是不同的对象(数组或函数),它们就不完全等同,即使这两个对象具有完全相同的属性,或两个数组具有完全相同的元素。

•如果两个值都是布尔型true,或者两个值都是布尔型false,那么它们等同。

•如果两个值都是null或都是undefined,它们完全相同。

下面的规则用于判定==运算符比较的两个值是否相等的判断条件•

如果两个值具有相同的类型,那么就检测它们的等同性。如果这两个值完全相同,它们就相等。如果它们不完全相同,则它们不相等。

•如果两个值的类型不同,它们仍然可能相等。用下面的规则和类型转换来检测它们的相等性 ◦如果一个值是null,另一个值是undefined,它们相等。

◦如果一个值是数字,另一个值是字符串,把字符串转换为数字,再用转换后的值进行比较。

◦如果一个值为true,将它转化为1,再进行比较。如果一个值为false,把它转化为0,再进行比较。

◦如果一个值是对象,另一个值是数字或字符串,将对象转换成原始类型的值,再埋比较。可以使用对象的toString()方法或valueOf()方法把对象转化成原始类型的值。JavaScript核心语言的内部类通常先尝试valueOf()方法转换,再尝试toString()方法转换,但是对于Date类,则先执行toString()方法再执行valueOf()方法转换。不属于JavaScript核心语言的对象则可以采用JavaScript实现定义的方式把自身转换成原始数值。

◦其他的数值组合是不相等的。

如果上面说的都懂了,那么下面的代码就自然理解了

console.log([]===[])
console.log(undefined===undefined)
console.log([]==[])undefined == undefined

  • 关于typeof

typeof的返回值共有五种:number, boolean, string, undefined, object, function.

  1. number

typeof(10);
typeof(NaN);
//NaN在JavaScript中代表的是特殊非数字值,它本身是一个数字类型。
typeof(Infinity);//代表无穷大

2.boolean

typeof(true);
typeof(false);

3.string

typeof("abc");

4.undefined

typeof(undefined);
typeof(a);//不存在的变量

5.object

对象,数组,null返回object
typeof(null);
typeof(window);

6.function

typeof(Array);
typeof(Date);

  • use strict 严格模式

设立"严格模式"的目的,主要有以下几个:

1. 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;

2. 消除代码运行的一些不安全之处,保证代码运行的安全;

3. 提高编译器效率,增加运行速度;

4. 为未来新版本的Javascript做好铺垫。

注:IE6,7,8,9 均不支持严格模式。

缺点:

现在网站的 JS 都会进行压缩,一些文件用了严格模式,而另一些没有。这时这些本来是严格模式的文件,被 merge 后,这个串就到了文件的中间,不仅没有指示严格模式,反而在压缩后浪费了字节。

  • javascript函数作用域

在javascript中有两种变量:

1.全局变量:声明在函数外部的变量(所有没有var直接赋值的变量都属于全局变量)

2.局部变量:声明在函数内部的变量(所有没有var直接赋值的变量都属于全局变量),当局部变量与全局变量重名时,局部变量会覆盖全局变量

var num = 1;            //声明一个全局变量
   function func() {
      var num = 2;        //声明一个局部变量
       return num;
   }
   console.log(func());    //输出:2 

在JavaScript中变量的作用域,并非和C、Java等编程语言似得,在变量声明的代码段之外是不可见的,我们通常称为块级作用域,然而在JavaScript中使用的是函数作用域(变量在声明它们的函数体以及这个函数体嵌套的任意函数体都是有定义的)。(如下面的例子)

function func() {
            console.log(num);           //输出:undefined,而非报错,因为变量num在整个函数体内都是有定义的
            var num = 1;                //声明num 在整个函数体func内都有定义
            console.log(num);           //输出:1
        }
        func();

当声明一个全局变量的时候,实际上是定义了全局对象window的一个属性。

var num = 1;            //声明全变量num
alert(window.num)       //输出:1 声明的全局变量实际上就是声明了一个window对象的属性

  • 在javascript中实现函数重载和多态

1同名函数的调用问题

在js中如果存在多个名称相同的函数,则调用实际每次都只使用最后一个,js其实是没有重载的,也就是说,如果定义了多个同名的函数,单参数不一样,在调用时,js不管参数个数,只管前后顺序

 function test1(arg1)          {            alert("参数1:"+arg1);                 }          function test1(arg1,arg2,arg3)          {             alert("参数1:"+arg1+"参数2:"+arg2+"参数3:"+arg3);                    }  //测试代码           function test(){                 test1("1")           }    

虽然我们调用的是test1("1"),传递了一个参数,但实际调用的却是test1(arg1,arg2,arg3),并没有因为我们传递了一个参数,而调用只有一个参数的方法。

2函数中特殊的参数arguments

如下代码:

function test1(arg1,arg2,arg3)      {         alert("参数1:"+arg1+"参数2:"+arg2+"参数3:"+arg3);            }      function test1(arg1)      {        alert("参数1:"+arg1);             }  //测试代码       function test(){             test1("1""2")       }    

上面代码调用的始终是test1(arg1),也就是只有一个参数的函数,那么如何获取传递的其他参数呢?这就要用到函数中特殊的参数arguments,arguments包含了所有传递给函数的参数

function test1()          {            var text="";            for(var i=0;i<arguments.length;i++){            text+="参数"+i+":"+arguments[i];             }            alert(text);                }  //测试代码           function test(){                 test1("1");               test1("1""2");                test1("1""2","3");           }  

经过测试发现,arguments是一个数组,包含了传递给函数的所有参数,并且arguments.length根据实际传递参数的个数的不同而不同,arguments.length代表了实际传递给函数参数的个数。那么如何实现函数的重载也就很简单了

function test1()          {            var text="";             if(arguments.length==1)             {               //调用一个参数的方法             }            else if(arguments.length==2)             {               //调用两个参数的方法                }                       else   {   //其他的方法                        }                   }  

从某种意义上来说,多态是面向对象中重要的一部分,也是实施继承的主要目的。个实例可以拥有多个类型,它既可以是这种类型,也可以是那种类型,这种多种状态被称为类的多态。

下面代码使用js的原型来设计类的多态特征。

function A(){    this.get = function(){      console.log('A');    }  }  function B(){    this.get = function(){      console.log('B');    }  }  B.prototype = new A(); // 使用原型继承,让B类继承A类  function C(){    this.get = function(){      console.log('C');    }  }  C.prototype = new A(); // 使用原型继承,让B类继承A类    function F(x){    this.x = x;  }  F.prototype.get = function(){    // 判断是否为A类的实例    if(this.x instanceof A){      // 如果是,调用实例的方法      this.x.get();    }  }    // 下面开始  var b = new B();  var c = new C();  var f1 = new F(b); // 此时F中的this.x 就是b了, 而b是A的一个实例  var f2 = new F(c); // 原理同上  f1.get(); // B  f2.get(); // C  

上面的类F就包含了一个多态方法get() ,它能够根据不同实例,来执行不同方法。

  • 常用数组api

1、将数组转化为字符串:2种:
1、var str=String(str);
将数组转化为字符串并分隔每个元素
2,、var str=arr.join("自定义分隔符");
将数组转化为字符串,可定义分隔符
强调:如果join省略"",就等效于String


2、链接和获取子数组:
1、连接: var newArr=arr1.concat(值1,值2,arr2,...);
将concat后的内容,和arr1拼接,组成新数组返回
强调:concat的参数中包含数组,则打散数组,以单个元素拼接
2、获取子数组:var subArr=arr.slice(starti,endi+1);
获得arr中starti位置开始,到endi位置的所有元素组成的新数组
强调:含头不含尾
省略第二个参数:表示从starti一直取到结尾
可支持负数参数:-n表示倒数第n个元素,相当于length-n


3、删除,插入,替换:(直接修改原数组)
1、删除:var deletes=arr.splice(starti,n);
删除arr中starti位置开始的n个元素
返回被删除的元素组成的临时新数组
2、插入:arr.splice(starti,0,值1,值2,...);
在arr中starti位置,插入新值。旧值被向后顺移
强调:要插入的值,只能以单独的参数传入,不支持打散数组参数
3、替换:var deletes=arr.splice(starti,n,值1,值2,...);
删除arr中starti位置开始的n个元素,再在starti位置插入新元素
删除的元素个数和插入的新元素个数不必相等


4、反转数组元素:arr.reverse();


5、升序排列:arr.sort(); (直接修改原数组)
特点:将arr中的元素,以字符串方式升序


6、结尾出入栈:
1、入栈:arr.push(值)
将值压入数组结尾
2、出栈:var last=arr.pop();
弹出数组最后一个元素
优点:每次出入栈,不影响其余元素的位置


7、开头出入栈
1、入栈:arr.unshift(值);
将值插入数组开头
2、出栈:var first=arr.shift();
取出数组第一个元素
缺点:每次出入栈,其余元素的位置都要顺移1

  • 字符串api

1.三个字符方法

两个用于访问字符串中特定字符的方法是:charAt()和charCodeAt()。这两个方法都接收一个参数,即基于0的字符位置。
两个方法的区别:charAt()返回给定位置的那个字符,charCodeAt()返回给定位置的字符编码。
第三个访问字符的方法,方括号加数字索引。但是IE7及更早版本不支持。

var stringValue = "hello world";
console.log(stringValue.charAt(1));//e
console.log(stringValue.charCodeAt(1));//101
console.log(stringValue[1]);//e

2 .4个字符串操作方法

1)concat():专门用来拼接字符串的方法,实践中使用更多的是加号操作符(+)。该方法可以接受任意多个参数,返回拼接得到新字符串,不改变原值。

var stringValue = "hello";
var result = stringValue.concat(" world","!");
console.log(result);//hello world!

(2)基于子字符串创建新字符串的方法:slice()、substr()和substring()
接受一个或两个参数:第一个参数指定子字符串的开始位置,第二个参数表示字符串到哪里结束。
对原来字符串没有影响。
使用区别一:slice()和substring()的第二个参数指定的是子字符串最后一个字符后面的位置(注意是后面一个位置)。而
substr()的第二个参数指定的是返回的字符的个数
区别二:在传递给这些方法的参数是负值时,它们的行为不尽相同。
slice()方法将传入的负值与字符串的长度相加;substr()方法将第一个参数加上字符串的长度,而将负的第二个参数转换为0。而substring()将所有负值参数都转换为0

3.字符串位置方法

indexOf()和lastIndexOf()与数组的相似。
注意:1.前者从前往后搜索,后者从后往前搜索。2**.第二个参数指定从字符串的哪个位置开始搜索。

4.trim()方法

该方法删除前置和后缀的所有空格,然后返回结果。不影响原字符串。

5.4个字符串大小写转换方法

toLowerCase()、toLocaleLowerCase()、toUpperCase()、toLocaleUpperCase()

toLowerCase()和toUpperCase()是两个经典的方法,toLocaleLowerCase()和toLocaleUpperCase()是针对特定地区的。

还有一些正则表达式的方法和不常用的方法,这里不一一举例。