JS基础知识汇集

基本概念

Undefined类型只有一个值,即特殊的undefined。在使用var声明一个变量但未对其加以初始化时,这个变量的值是undefined。

var message; //这个变量声明之后就默认取得了undefined值
//下面这个变量没有声明
// var age;
console.log(typeof message); //undefined
console.log(typeof age);     //undefined

结果表明对未初始化和为声明的变量执行typeof操作符都返回了undefined值。

  • Null类型

Null类型也只有一个值,即特殊的null。从逻辑角度来看,null值表示一个空对象指针,而这也是typeof操作符检测null值是返回”object”的原因。

var car = null;
console.log(typeof car); // object

如果定义了一个变量准备在将来用于保存对象,那么最好将该变量初始化为null而不是其他的值。

变量、作用域和内存问题

  • 数据类型

1.原始类型:保存一些简单数据,如true,5等。JavaScript共有5中原始类型:

boolean:布尔,值为true或false
number:数字,值为任何整型会浮点数值
string:字符串,值为由单引号或双引号括出的单个字符或连续字符(JavaScript不区分字符类型)
null:空类型,其仅有一个值:nulll
undefined:未定义,其仅有一个值:undefined

2.引用类型:保存为对象,实质是指向内存位置的引用,所以不在变量中保存对象。除了自定义的对象,JavaScript提供了6中内建类型:

Array:数组类型,以数字为索引的一组值的有序列表
Date:日期和时间类型
Error:运行期错误类型
Function:函数类型
Object:通用对象类型
RegExp:正则表达式类型
  • 引用类型的检测

如果检测的是基础数据类型则可以使用typeof操作符,但在检测引用数据类型的时应该使用instanceof操作符。首先typeof对null的检测是返回object,而不是返回null,其次typeof对所有非函数的引用类型均返回object,所以需要用instanceof来检测引用类型。

所以要检测null时,最好用全等于(===),其还能避免强制类型转换。

其语法如下所示

result = variable instanceof constructor
alert(person instanceof Object)
alert(person instanceof Array)
alert(person instanceof RegExp)
alert(person instanceof Function)
alert(person instanceof Date)
alert(person instanceof Error)
  • 数组类型的检测

instanceof操作符的问题在于,它假定只有一个全局执行环境,但如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array构造函数。为了解决这个问题可以使用ECMAScript5新增的Array.isArray()方法。或者可以使用自定义方法

function isArray(arr) {
  if (typeof Array.isArray === "function") {
    return Array.isArray(arr); // ECMA5自带
  } else {
    // Object.prototype.toString的行为:首先,取得对象的一个内部属性[[Class]]
    return Object.prototype.toString.call(arr) === "[object Array]";
  }
}
  • 作用域

参考文档 鸟哥:Javascript作用域原理

js中的this

this是Javascript语言的一个关键字。它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。

随着函数使用场合的不同,this的值会发生变化。但是有一个总的原则,那就是this指的是,调用函数的那个对象。但在es6的箭头函数中this对象,就是定义时所在的对象,而不是使用时所在的对象。

  • 情况一 纯粹的函数调用

这是函数的最通常用法,属于全局性调用,因此this就代表全局对象Global。

var x = 1;
function f(){
  console.log(this.x);
}
f(); //1
  • 情况二 作为对象方法的调用

函数还可以作为某个对象的方法调用,这时this就指这个上级对象。

function test(){
  console.log(this.x);
}

var o = {};
o.x = 1;
o.m = test;
console.log(o.m()); //1

情况三 作为构造函数调用

所谓构造函数,就是通过这个函数生成一个新对象(object)。这时,this就指这个新对象。

function test(){
  this.x = 1;
}

var o = new test();
console.log(o.x); //1

运行结果为1。为了表明这时this不是全局对象,我对代码做一些改变:

var x = 2;
function test(){
  this.x = 1;
}
var o = new test();
console.log(x); //2

运行结果为2,表明全局变量x的值根本没变。

情况四 apply调用

apply()是函数对象的一个方法,它的作用对象是改变函数的调用对象,它的第一个参数就表示改变后的调用这个函数的对象。

var x = 0;
function test(){
  console.log(this.x);
}

var o = {};
o.x = 1;
o.m = test;

o.m.apply(); //0

apply()的参数为空时,默认调用全局对象。因此,这时的运行结果为0,证明this指的是全局对象。 如果把最后一行代码修改为

o.m.apply(o); //1

运行结果就变成了1,证明了这时this代表的是对象o。

如何创建一个函数

函数相关

函数表达式

1、将函数赋值给变量

//function variable
var add = function(a, b) {
  // body...
};

2、立即执行函数

// IEF(Immediately Executed Function)
(function() {
  // body...
})();

3、函数对象作为返回值

return function() {
      // body...
};

4、命名式函数表达式

//NFE(Named Function Expression)
var add = function foo(a, b) {
  // body...
};

通过下面两种做法把函数声明变成函数表达式

1、用一个()将函数声明括起来
(function(){
    // do sth here
    var a,b; //a,b只是局部可见
})();

2在函数声明前加一个!
!function(){
    // do sth here
    var a,b;
}();

变量 & 函数的声明前置

1.函数的生命周期

函数的生命周期分为两个阶段:声明阶段和执行阶段。赋值操作是在执行阶段完成。

2.JS解释器如何找到我们定义的函数和变量?

变量对象(VO):储存执行上下文中定义的变量、函数声明、函数参数。

js函数中定义的变量和声明的函数都是从变量对象中去查找的。

3.变量初始化阶段,VO按照如下顺序填充:

a.函数参数 (若未传入,初始化该参数值为undefined)

b.函数声明 (若发生命名冲突,会覆盖)

c.变量声明 (初始化变量值为 undefined,若发生命名冲突,会忽略。)

function foo(x, y, z) {
  function x() {};
  console.log(x);
}
foo(100); // funtion x()

function foo2(x, y, z) {
  var x = 50;
  console.log(x);
}
foo2(110); // 50

function foo3() {
  function x() {};
  var x = 100;
  console.log(x);
}
foo3(); // 100

function foo4() {
 function x() {};
 var x;
 console.log(x);
}
foo4(); // funtion x()

对象

Object.getPrototypeOf()

The Object.getPrototypeOf() method returns the prototype (i.e. the value of the internal [[Prototype]] property) of the specified object.

var proto = {};
var obj = Object.create(proto);
Object.getPrototypeOf(obj) === proto; // true

Object.defineProperty()

The Object.defineProperty() method defines a new property directly on an object, or modifies an existing property on an object, and returns the object.

Object.defineProperty(obj, prop, descriptor)

对象继承

什么是原型?

原型是一个对象,每个对象都有一个原型,对象的原型通常都是构造器的prototype指向的对象。其他对象可以通过它实现属性继承。

哪些对象有原型

所有的对象在默认的情况下都有一个原型,因为原型本身也是对象,所以每个原型自身又有一个原型(只有一种例外,默认的对象原型在原型链的顶端。更多关于原型链的将在后面介绍)

prototype属性与原型

原型与prototype不一样。每个对象默认都有一个原型,chrome中通过_proto_取得,es5中通过Object.getPrototypeOf(object)取得。每个函数默认都有一个prototype属性,它指向一个对象,我们通常将它成为原型对象。

原型模式

我们创建的每个函数都会有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

构造函数、原型和实例的关系

每个构造函数都包含一个原型对象,原型对象包含一个指向构造函数的指针,而实例包含一个指向原型对象的内部指针。

原型链

如果让原型对象等于另一个类型的实例的话,(由于每个实例都包含一个指向原型对象的指针)则原型对象中就会包含一个指向另一个原型对象的指针。响应地,另一个原型中也包含着指向另一个构造函数的指针。假设另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。

原型链

实现继承

function Person() {}

function Student() {}

Student.prototype = Person.prototype; //1

Student.prototype = new Person(); //2

Student.prototype = Object.create(Person.prototype); //3

Student.prototype.constructor = Student;

注释中:

1 是错误的。如果改变了 Student 就会改变 Person

2 可以实现继承,但是其调用了构造函数,若父类构造函数中有形参,那么传值就会比较奇怪。

3 是最好的方法。创建了一个空对象,并且对象的原型指向参数 Person.prototype。这样便实现了继承。同时原型链写,不向上查找。但是 Object.create 是ES5 中的方法,所以可以使用下列代码做兼容:

if (!Object.create) {
    Object.create = function(proto) {
        function F() {}
        F.prototype = proto;
        return new F;
    };
}

function Animal(){
   this.species = "动物";
}
function Cat(name,color){
   this.name = name;
   this.color = color;
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

** 如何理解Cat.prototype = new Animal(); **

Cat.prototype指向是一个对象, new 构造器():会使new出来的对象的原型指向构造器的prototype属性。因此就会有Cat.prototype.proto = Animal.prototype.而Animal.prototype.proto = Object.prototypre,从而实现了继承。

JavaScript 技巧

  • 简洁写法 JavaScript里我最喜欢的一种东西就是生成对象和数组的简写方法。在过去,如果你想创建一个对象,你需要这样:

    var car = new Object(); car.colour = ‘red’; car.wheels = 4; car.hubcaps = ‘spinning’; car.age = 4;

下面的写法能够达到同样的效果:

var car = {
  colour:'red',
  wheels:4,
  hubcaps:'spinning',
  age:4
}
  • 用 JSON 形式存储数据 你可以在JavaScript里直接使用JSON,可以把它封装在函数里,甚至作为一个API的返回值形式。我们把这称作 JSON-P ,很多的API都使用这种形式。 你可以调用一个数据提供源,在script代码里直接返回 JSON-P 数据:

JavaScript的自带函数(Math, Array 和 String)让我感到惊奇的一个事情是,当我研究了JavaScript里的math和String函数后,发现它们能极大的简化我的编程劳动。使用它们,你可以省去复杂的循环处理和条件判断。例如,当我需要实现一个功能,找出数字数组里最大的一个数时,我过去是这样写出这个循环的,就像下面:

var numbers = [3,342,23,22,124];
var max = 0;
for(var i=0;i<numbers.length;i++){
  if(numbers[i] > max){
    max = numbers[i];
  }
}
alert(max);

我们不用循环也能实现:

var numbers = [3,342,23,22,124];
numbers.sort(function(a,b){return b - a});
alert(numbers[0]);

需要注意的是,你不能对一个数字字符数组进行 sort() ,因为这种情况下它只会按照字母顺序进行排序。如果你想知道更多的用法,可以阅读 这篇不错的关于 sort() 的文章。

再有一个有意思的函数就是 Math.max()。这个函数返回参数里的数字里最大的一个数字:

Math.max(12,123,3,2,433,4); // returns 433

因为这个函数能够校验数字,并返回其中最大的一个,所以你可以用它来测试浏览器对某个特性的支持情况:

var scrollTop= Math.max(
 doc.documentElement.scrollTop,
 doc.body.scrollTop
);

这个是用来解决IE问题的。你可以获得当前页面的 scrollTop 值,但是根据页面上 DOCTYPE的不同,上面这两个属性中只有一个会存放这个值,而另外一个属性会是 undefined,所以你可以通过使用 Math.max() 得到这个数。阅读这篇文章你会得到更多的关于使用数学函数来简化JavaScript的知识。

另外有一对非常有用的操作字符串的函数是 split() 和 join()。我想最有代表性的例子应该是,写一个功能,用来给页面元素附加CSS样式。

是这样的,当你给页面元素附加一个CSS class时,要么它是这个元素的第一个CSS class,或者是它已经有了一些class, 需要在已有的class后加上一个空格,然后追加上这个class。而当你要去掉这个class时,你也需要去掉这个class前面的空格(这个在过去非常重要,因为有些老的浏览器不认识后面跟着空格的class)。

于是,原始的写法会是这样:

function addclass(elm,newclass){
  var c = elm.className;
  elm.className = (c === '') ? newclass : c+' '+newclass;
}

你可以使用 split() 和 join() 函数自动完成这个任务:

function addclass(elm,newclass){
  var classes = elm.className.split(' ');
  classes.push(newclass);
  elm.className = classes.join(' ');
}

这会确保所有的class都被空格分隔,而且你要追加的class正好放在最后。

  • 使用代理(Delegate)

Event delegation allows you to avoid adding event listeners to specific nodes; instead, the event listener is added to one parent. That event listener analyzes bubbled events to find a match on child elements.

<ul id="parent-list">
  <li id="post-1">Item 1</li>
  <li id="post-2">Item 2</li>
  <li id="post-3">Item 3</li>
  <li id="post-4">Item 4</li>
  <li id="post-5">Item 5</li>
  <li id="post-6">Item 6</li>
</ul>

Let’s also say that something needs to happen when each child element is clicked. You could add a separate event listener to each individual LI element, but what if LI elements are frequently added and removed from the list? Adding and removing event listeners would be a nightmare, especially if addition and removal code is in different places within your app. The better solution is to add an event listener to the parent UL element. But if you add the event listener to the parent, how will you know which element was clicked?

Simple: when the event bubbles up to the UL element, you check the event object’s target property to gain a reference to the actual clicked node. Here’s a very basic JavaScript snippet which illustrates event delegation:

// Get the element, add a click listener… document.getElementById(“parent-list”).addEventListener(“click”, function(e) { // e.target is the clicked element! // If it was a list item if(e.target && e.target.nodeName == “LI”) { // List item found! Output the ID! console.log(“List item “, e.target.id.replace(“post-“), “ was clicked!”); } });

  • 匿名函数和模块化

在JavaScript里最令人懊恼的事情是变量没有使用范围。任何变量,函数,数组,对象,只要不在函数内部,都被认为是全局的,这就是说,这个页面上的其它脚本也可以访问它,而且可以覆盖重写它。

解决办法是,把你的变量放在一个匿名函数内部,定义完之后立即调用它。例如,下面的写法将会产生三个全局变量和两个全局函数:

var name = 'Chris';
var age = '34';
var status = 'single';
function createMember(){
  // [...]
}
function getMemberDetails(){
  // [...]
}

如果这个页面上的其它脚本里也存在一个叫 status 的变量,麻烦就会出现。如果我们把它们封装在一个 myApplication 里,这个问题就迎刃而解了:

var myApplication = function(){
  var name = 'Chris';
  var age = '34';
  var status = 'single';
  function createMember(){
    // [...]
  }
  function getMemberDetails(){
    // [...]
  }
}();

但是,这样一来,在函数外面就没有什么功能了。如果这是你需要的,那就可以了。你还可以省去函数的名称:

(function(){
  var name = 'Chris';
  var age = '34';
  var status = 'single';
  function createMember(){
    // [...]
  }
  function getMemberDetails(){
    // [...]
  }
})();

如果你想在函数外面也能使用里面的东西,那就要做些修改。为了能访问 createMember() 或 getMemberDetails(),你需要把它们变成 myApplication的属性,从而把它们暴露于外部的世界:

var myApplication = function(){
  var name = 'Chris';
  var age = '34';
  var status = 'single';
  return{
    createMember:function(){
      // [...]
    },
    getMemberDetails:function(){
      // [...]
    }
  }
}();
// myApplication.createMember() and
// myApplication.getMemberDetails() now works.

这被称作 module 模式或 singleton。Douglas Crockford 多次谈到过这些,Yahoo User Interface Library YUI 里对此有大量的使用。但这样一来让我感到不便的是,我需要改变句式来使函数和变量能被外界访问。更甚者,调用时我还需要加上myApplication 这个前缀。所以,我不喜欢这样做,我更愿意简单的把需要能被外界访问的元素的指针导出来。这样做后,反倒简化了外界调用的写法:

var myApplication = function(){
  var name = 'Chris';
  var age = '34';
  var status = 'single';
  function createMember(){
    // [...]
  }
  function getMemberDetails(){
    // [...]
  }
  return{
    create:createMember,
    get:getMemberDetails
  }
}();
//myApplication.get() and myApplication.create() now work.

我把这个称作 “revealing module pattern.”

  • 可配置化

字符串(String)相关

  • 字符方法charAt()和charCodeAt()用于访问字符串中特定字符,接受一个基于0的字符位置。

    var stringValue = “hello Javascript” alert(stringValue.charAt(1)); //”e” alert(stringValue.charCodeAt(1)); //”101”

    //ECMAScript5中可以使用方括号加数字索引来访问字符串中的特定字符 alert(stringValue[1]); //”e”

  • 字符串操作方法slice()、 substr()、 subString()

这三个方法都会返回一个新的字符串,而且都可以接受一个或两个参数。第一个指定子字符串的开始位置,第二个(在指定的情况下)表示子字符串到哪里结束。

其中slice()和subString()的第二个参数指定子字符串的结束位置,substr()的第二个参数指定返回的字符个数。如果没有指定第二个参数,则将字符串的长度作为结束位置。

var stringValue = "hello Javascript";
alert(stringValue.slice(3,7))//"lo J"
alert(stringValue.subString(3,7))//"lo J"
alert(stringValue.subStr(3,7))//"lo Java"

当传入的参数是负值的时候,slice()方法会将传进来的参数与字符串长度相加,subStr()会将第一个负值与字符串长度相加,将第二个参数转换成0,subString()方法会将所有负值参数都转换成0.

var stringValue = "hello World";
alert(stringValue.slice(-3))//"rld"
alert(stringValue.subString(-3))//"hello World"
alert(stringValue.subStr(-3))//"rld"

alert(stringValue.slice(3, -4))//"lo W"
alert(stringValue.subString(3, -4))//"hel",等价于subString(3,0)这个方法会将较小的数作为开始位置。
alert(stringValue.subStr(3, -4))//""(空字符),第二个参数被转换成0,等价于返回0个字符串
  • 字符串位置方法indexOf()和lastIndexOf().都从第一个字符开始搜索指定字符,然后返回子字符串的位置,如果没有找到返回-1

  • 字符串匹配方法match()、 search().接受一个正则表达式,match返回一个数组,search返回字符串中第一个匹配项的索引。

闭包

个人理解之所以会需要闭包是因为:函数在被调用是它的执行上下文已经被更改,为了实现函数的效果就需要在函数调用时保存该函数上下文中对应变量的值。

事例:

<!doctype html>
<title>javascript闭包 by 司徒正美</title>
<meta charset="utf-8"/>
<meta name="keywords" content="javascript闭包 by 司徒正美" />
<meta name="description" content="javascript闭包 by 司徒正美" />
<script type="text/javascript">
  window.onload = function(){
     for(var i=1; i < 4; i++){
        var id = document.getElementById("a" + i);
           id.onclick = function(){
                alert(i);//现在都是返回4
           }
     }
  }
</script>
<h1>javascript闭包 by 司徒正美</h1>
<ul>
  <li id="a1">aa</li>
  <li id="a2">aa</li>
  <li id="a3">aa</li>
</ul>

我的解释是,onclick的绑定函数 function(){alert(i)}的作用域为对应li对象,它里面alert的i的作用域为window,每次循环都是在重写window.i的值,因此循环完,i已是4,点击哪一个li元素都是4。

解决办法使用函数闭包

var lists = document.getElementsByTagName("li");
for(var i=0,l=lists.length; i < l; i++){
  lists[i].onclick = (function(i){//保存于外部函函数
    return function(){
      alert(i);
    }
  })(i);
}

或
var lists = document.getElementsByTagName("li");
for(var i=0,l=lists.length; i < l; i++){
  lists[i].onclick = new function(){
    var t = i;
    return function(){
      alert(t+1)
    }
  }
}

闭包的用途

1、在函数外部读取到函数内部定义的变量,如模仿私有属性;缓存变量的值;

2、及让变量的值一直保存在内存中等。

怎么来理解看下面的代码

function f1(){
    var n = 999;
    nAdd = function(){n+=1}
    function f2(){
        alert(n);
    }
    retrun f2;
}

var result = f1();
result(); //999
nAdd();
result(); //1000

在这段代码中result实际上就是闭包函数f2。它一共运行2次,第一次的值是999,第二次的值是1000.这证明函数f1中的局部变量n一直都保存在内存中,并没有在f1调用后被自动清除。