学习笔记:《重新介绍 JavaScript(JS 教程)》

  1. 为什么要重新介绍 JavaScript?
  • 因为 JavaScript 被误解为世界上最不懂编程的语言。
  • 经常有人嘲笑 JavaScript 是不值钱的小玩具,但是在其简单的外表下面是强大的语言特性。
  • JavaScript 现在已被用于无数的高知名度应用程序,充分表明这项技术的深层知识是一项开发者需要具备的重要技能。
  1. 首先了解一下这门语言的历史很有用。
  • 脚本语言(Scripting language)是一种新兴的计算机编程语言,它的原则是用简洁而又快速的方式去完成。
  • 脚本语言又被称为扩建的语言,或者动态语言,是一种编程语言,用来控制软件应用程序,脚本通常以文本(如ASCII)保存,只在被调用时进行解释或编译。
  • 最常用的 JavaScript 解释器是浏览器,但是在其他许多地方都有 JavaScript 解释器,如 Adobe Acrobat, Photoshop, SVG 图像, 雅虎的 Widget 引擎, 服务器端环境 Node.js, 还有 NoSQL 数据库(如开源的 Apache CouchDB), 嵌入式计算机, 桌面环境(如GNOME)等等。

概览

  1. JavaScript是一种多范式的动态语言,包含类型和运算符,标准内置对象和方法。
  • 编程语言越来越多的走向多范式(Multi-paradigm),兼顾面向对象和面向过程功能。
  • 什么是多范式?编程范式是程序语言背后的思想。常见的编程范式有:过程式,面向对象,函数式,泛型编程等。
  • https://blog.csdn.net/hnyzyty/article/details/49887259
  • 这个世界是如此得丰富多彩,使用单一模式为世界建模并不会成功。

数据类型

  1. JavaScript 的数据类型
  • Number 数值:双精度64位,没有整型,所以要注意算术运算:
    • 0.1 + 0.2 == 0.30000000000000004;
    • 内置对象 Math 提供算数运算
    • 可以使用内置的 parseInt() 函数把字符串转化为整数
  • String 字符串
  • Boolean 布尔值
  • Symbol (new in ES2015) 元件
  • Object 对象
    • Function 函数
    • Array 数组
    • Date 日期
    • RegExp 正则表达式
    • Error 错误
  • null 空值
  • undefined 未定义

3. Number 数值

  • 和其他编程语言(如 C 和 Java)不同,JavaScript 不区分整数值和浮点数值,所有数字在 JavaScript 中均用浮点数值表示。
  • 你可以使用内置函数 parseInt() 将字符串转换为整型。该函数的第二个参数表示字符串所表示数字的基(进制)。
parseInt("123", 10); // 123
parseInt("010", 10); //10
  • 如果想把一个二进制数字字符串转换成整数值,只要把第二个参数设置为 2 就可以了
parseInt("11", 2); // 3
  • JavaScript 还有一个类似的内置函数 parseFloat(),用以解析浮点数字符串,与parseInt()不同的地方是,parseFloat()只应用于解析十进制数字。
  • 单元运算符 + 也可以把数字字符串转换成数值。
  • 如果给定的字符串不存在数值形式,函数会返回一个特殊的值 NaN(Not a Number 的缩写)。
  • 要小心NaN:如果把 NaN 作为参数进行任何数学运算,结果也会是 NaN。
parseInt("hello", 10); // NaN
NaN + 5; //NaN
  • JavaScript 还有两个特殊值:Infinity(正无穷)和 -Infinity(负无穷):
  • parseInt() 和 parseFloat() 函数会尝试逐个解析字符串中的字符,直到遇上一个无法被解析成数字的字符,然后返回该字符前所有数字字符组成的数字。使用运算符 “+” 将字符串转换成数字,只要字符串中含有无法被解析成数字的字符,该字符串都将被转换成 NaN。请你用这两种方法分别解析“10.2abc”这一字符串,比较得到的结果,理解这两种方法的区别。

其他数据类型

  1. String 字符串
  • JavaScript 中的字符串是一串UTF-16编码单元的序列,每一个编码单元由一个 16 位二进制数表示。
  • 字符串也有methods(方法)能让你操作字符串和获取字符串的信息。
"hello".charAt(0); // "h"
"hello, world".replace("hello", "goodbye"); // "goodbye, world"
"hello".toUpperCase(); // "HELLO"
  1. null 和 undefined 截然不同
  • null 表示一个空值(non-value),必须使用null关键字才能访问。
  • undefined 是“undefined(未定义)”类型的对象,表示一个未初始化的值,也就是还没有被分配的值。
  • undefined 实际上是一个常数。null 则是空值。
  1. Boolean 布尔值,任何值可以转化为布尔值,根据下面规则:
  • false、0、空字符串("")、NaN、null 和 undefined 被转换为 false
  • 所有其他值被转换为 true
  • JavaScript 会在需要一个布尔变量时隐式完成类型转换(比如在 if 条件语句中)。

7. 变量

  • 声明变量使用 3 个关键字:let, const, var.
  • let 声明的变量是一个块级变量,作用域只在块内。
  • var 是最常用的变量声明关键字。是 JavaScript 原来就有的唯一的声明变量的关键字。let 和 const 都是后来新增的。
  • 声明变量后而不赋值,则其值是 undefined 。

8. 运算符

  • 字符串与任何数值相加都是字符串。
    • ‘3’ + 4 + 5; // “345”
    • 3 + 4 + ‘5’; // “75”
  • 如果数据类型不同,== 运算符会执行强制类型转换,例如:
    • 123 == ‘123’; // true
    • 1 == true; // true
  • 要想避免类型强制转换,应该使用 === 运算符:
    • 123 === ‘123’; // false
    • 1 === true; // false
  • There are also != and !== operators.
  • JavaScript 还有【位运算符】 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators

9. 控制结构

  • 除了顺序、分支、循环,有特殊结构吗?有
  • 特殊循环 for…of :for value of 数组
  • 特殊循环 for…in :for key in object
  • && 和 || 运算符使用短路逻辑(short-circuit logic),也就是说后面的运算是否执行,取决于前面的运算。这对于检查 null 对象很有用。
    • actionscript 就是这样,我们经常使用这个特性。
    • var name = (o && o.getName()); //对象存在,则访问其属性
    • var name = (cachedName || (cachedName = getName()));//值不存在,才获取
  • 三元运算符:条件 ? 真则取值 : 假则取值
    • var allowed = (age > 18) ? ‘yes’ : ‘no’;
  • switch 和 case 中可以使用表达式,它们之间的比较是 === 运算符:
    • switch (1 + 3) {
    • case 2 + 2:

10. Objects 对象

  • 可以用函数创建对象原型
    //creates an object prototype, Person
    //用函数创建对象原型,很神奇 
    function Person(name, age){
      this.name = name;
      this.age = age;
    }
    //creates an instance of that prototype Person
    var you = new Person('ming', 30);
  1. Arrays 数组
  • 注意 array.length 不一定是数组中元素的实际个数,例如:
var a = ['dog', 'cat', 'hen'];
a[100] = 'fox';
a.length; // 101
  • 如果查询一个不存在的索引,则得到 undefined 例如:
a = ['a', 'b', 'c'];
console.log("a=", a,"a[1]=", a[1],"a[11]=", a[11]);
//a= ["a", "b", "c"] a[1]= b a[11]= undefined
//有的语言,索引超界会报错,JavaScript 不报错,而是返回 undefined 
  • 迭代数组的另一种方法是 ECMAScript5 添加的 forEach():
['dog', 'cat', 'hen'].forEach(function(currentValue, index, array) {
  // Do something with currentValue or array[index]
});

12. 函数和对象:是语言的核心组件

  • 调用函数,可以不传递需要的参数。也可以传递多余的参数。这看起来有点傻。
  • 函数可以接受任意数量的参数。使用 arguments 对象, rest 语法 …args,
    • …args(包括省略号)叫作剩余参数(rest arguments)。如名所示,这个东西包含了剩下的参数。
  • 使用函数的 apply() 方法调用函数.
  • 使用展开运算符 spread syntax 传递一个数组给函数。
  • JavaScript 允许你创建匿名函数:
  • JavaScript 允许以递归方式调用函数。递归在处理树形结构(比如浏览器 DOM)时非常有用。
function countChars(elm) {
    if (elm.nodeType == 3) { // 文本节点
        return elm.nodeValue.length;
    }
    var count = 0;
    for (var i = 0, child; child = elm.childNodes[i]; i++) {
        count += countChars(child);
    }
    return count;
}
  • 需要注意的是 JavaScript 函数本身是个对象——你可以给它们添加属性或者更改它们的属性,这与前面的对象部分一样。

13. 自定义对象

  • 在经典的面向对象语言中,对象是指数据和在这些数据上进行的操作的集合。
  • JavaScript 是一种基于原型的编程语言,并没有 class 语句,而是把函数用作类。
  • 关键字 this。当使用在函数中时,this 指代当前的对象,也就是调用了函数的对象。
    • 如果在一个对象上使用点或者方括号来访问属性或方法,这个对象就成了 this。例如:a.fullName(), a[‘fullName’]
    • 如果并没有使用“点”运算符调用某个对象,那么 this 将指向全局对象(global object)。
    • 这是一个经常出错的地方。在 ActionScript3 中经常会遇到。
function makePerson(first, last) {
    return {
        first: first,
        last: last,
        fullName: function() {
            return this.first + ' ' + this.last;
        },
        fullNameReversed: function() {
            return this.last + ', ' + this.first;
        }
    }
}
s = makePerson("Simon", "Willison");
s.fullName(); // Simon Willison
s.fullNameReversed(); // Willison, Simon

var fullName = s.fullName;
// 下面这种调用方式 this 实际上是指向全局对象的
fullName(); // undefined undefined
  • new 关键字将生成的 this 对象返回给调用方,而被 new 调用的函数成为构造函数。
  • prototype 是一个名叫原型链(prototype chain)的查询链的一部分:当你试图访问一个没有定义的属性时,解释器会首先检查这个 prototype 来判断是否存在这样一个属性。
function Person(first, last) {
    this.first = first;
    this.last = last;
}
Person.prototype.fullName = function() {
    return this.first + ' ' + this.last;
}
Person.prototype.fullNameReversed = function() {
    return this.last + ', ' + this.first;
}
    • JavaScript 允许你在程序中的任何时候修改原型(prototype)中的一些东西,也就是说你可以在运行时(runtime)给已存在的对象添加额外的方法。
s = new Person("Simon", "Willison");
s.firstNameCaps();  // TypeError on line 1: s.firstNameCaps is not a function

Person.prototype.firstNameCaps = function() {
    return this.first.toUpperCase()
}
s.firstNameCaps(); // SIMON
    • 有趣的是,你还可以给 JavaScript 的内置函数原型(prototype)添加东西。
var s = "Simon";
s.reversed(); // TypeError on line 1: s.reversed is not a function

String.prototype.reversed = function() {
    var r = "";
    for (var i = this.length - 1; i >= 0; i--) {
        r += this[i];
    }
    return r;
}
s.reversed(); // nomiS
    • 原型(prototype)组成链的一部分。这条链的根节点是 Object.prototype,它包括 toString() 方法,这对于调试我们的自定义对象很有用。
var s = new Person("Simon", "Willison");
s; // [object Object]

Person.prototype.toString = function() {
    return '<Person: ' + this.fullName() + '>';
}
s.toString(); // <Person: Simon Willison>
  • apply() 中的第一个参数应该是一个被当作 this 来看待的对象。
  • apply() 有一个姐妹函数,名叫 call,它也可以允许你设置 this,但它带有一个扩展的参数列表而不是一个数组。
function lastNameCaps() {
    return this.last.toUpperCase();
}
var s = new Person("Simon", "Willison");
lastNameCaps.call(s);
// 和以下方式等价
s.lastNameCaps = lastNameCaps;
s.lastNameCaps();

14. 内部函数

  • 关于 JavaScript 中的嵌套函数,一个很重要的细节是它们可以访问父函数作用域中的变量。
function betterExampleNeeded() {
    var a = 1;
    function oneMoreThanA() {
        return a + 1;
    }
    return oneMoreThanA();
}
  • 如果某个函数依赖于(需要调用)另外一两个函数,而那一两个函数对你其余的代码没有用处(不用),你可以将它们嵌套在这个函数内部,这样可以减少全局作用域下的函数数量,有利于编写易于维护的代码。
  • 这也是一个减少使用全局变量的好方法。
    • 当编写复杂代码时,程序员往往试图使用全局变量,将值共享给多个函数,但这样做会使代码很难维护。
    • 内部函数可以共享父函数的变量,所以你可以使用这个特性把一些函数捆绑在一起,这样可以有效地防止“污染”你的全局命名空间——你可以称它为“局部全局(local global)”。
    • 虽然这种方法应该谨慎使用,但它确实很有用,应该掌握。
  • 嵌套函数有利于封装细节,简化代码。全局函数更少,全局变量更少。

15. 闭包:JavaScript 中必须提到的功能最强大的抽象概念之一

    function makeAdder(a){
      return function (b){
        return a + b;
      }
    }
    a = makeAdder(5);//不是得到数值,而是得到一个函数:function (b){return a + b;}}
    b = makeAdder(20);
    c = a(6); //这个才是执行函数计算,得到 11 
    let d = b(7); //27
  • 每当 JavaScript 执行一个函数时,都会创建一个作用域对象(scope object),用来保存在这个函数中创建的局部变量。这与那些保存了所有全局变量和函数的全局对象(global object)类似,但仍有一些很重要的区别:
    • 第一,每次函数被执行的时候,就会创建一个新的、特定的作用域对象;
    • 第二,不能从 JavaScript 代码中直接访问作用域对象,也没有可以遍历当前的作用域对象里面属性的方法。
  • 通常 JavaScript 的垃圾回收器会在函数返回时回收该函数创建的作用域对象。但是返回的函数却保留一个指向那个作用域对象的引用。结果是这个作用域对象不会被垃圾回收器回收,直到指向该函数返回的那个函数对象的引用计数为零。
  • 作用域对象组成了一个名为作用域链(scope chain)的链。
  • 一个闭包就是一个函数和被创建的函数中的作用域对象的组合。

内存泄露

    • 使用闭包的一个坏处是,在 IE 浏览器中它会很容易导致内存泄露。
    • JavaScript 是一种具有垃圾回收机制的语言——对象在被创建的时候分配内存,然后当指向这个对象的引用计数为零时,浏览器会回收内存。
    • IE 浏览器有自己的一套垃圾回收机制,这套机制与 JavaScript 提供的垃圾回收机制进行交互时,可能会发生内存泄露。
      • 在 IE 中,每当在一个 JavaScript 对象和一个本地对象之间形成循环引用时,就会发生内存泄露。
      • 不过一般也很少发生如此明显的内存泄露现象——通常泄露的数据结构有多层的引用(references),往往掩盖了循环引用的情况。
    • 闭包很容易发生无意识的内存泄露。如下所示:
function addHandler() {
    var el = document.getElementById('el');
    el.onclick = function() {
        el.style.backgroundColor = 'red';
    }
}
      • 这段代码会发生内存泄露。为什么?因为对 el 的引用不小心被放在一个匿名内部函数中。这就在 JavaScript 对象(这个内部函数)和本地对象之间(el)创建了一个循环引用。
      • 这个问题有很多种解决方法,最简单的一种是不要使用 el 变量:
function addHandler(){
    document.getElementById('el').onclick = function(){
        this.style.backgroundColor = 'red';
    };
}
  • 另外一种避免闭包的好方法是在 window.onunload 事件发生期间破坏循环引用。很多事件库都能完成这项工作。
    • 注意这样做将使 Firefox 中的 bfcache 无法工作。所以除非有其他必要的原因,最好不要在 Firefox 中注册一个onunload 的监听器。
  1. 结束
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值