前言
在看书的过程中,发现有一些内容属于那种边边角角容易忘记却又非常重要。
所以,在这里留下一篇笔记,以便查阅。
第1章 js简介
js组成部分:ECMAScript、DOM、BOM
浏览器就是js的解释器。
DOM是文档对象模型,通过它来操作网页对象上的元素,这些元素就是HTML上的各种标签。
BOM是浏览器对象模型,可以操作浏览器,其中XMLHttpRequest这个对象很重要,ajax异步处理技术就会用到它。
第2章 在HTML中使用js
js脚本默认是从上到下依次执行的,如果写在head里,就会堵塞。
script元素用来将js脚本插入HTML文件。
script中的async属性的作用是异步加载,不用等待,不用排队,要注意的是它不保证哪一个脚本先执行完毕,所以在加载期间不要改DOM。
script中的defer属性的作用是延迟加载,相当于放在body元素的底部位置,等页面全部呈现了才会执行。它也不保证谁先执行完毕。
综上,最后把脚本放在body元素内的底部,即主体内容后面,</body>
的前面。
noscript元素内的文本会在浏览器不支持js的情况下显示出来。
第3章 基本概念
标识符就是变量、函数、属性、函数参数的名字。
区分大小写。
字母数字下划线和美元符号。
其中,数字不可以作为开头。
最好用小驼峰,像这样:myName,hisAge。
保留字、关键字、布尔值(true、false)、null不可以做标识符。
严格模式:"use strict"
对那些不确定的行为和不安全的方式加以限制,如果发生会报错,让代码更强壮。
语句最好加分号。
关键字就是像声明时用的var、let、function……
保留字就是暂时还没有规定用途的准关键字。
未初始化的变量(就是没有给值的变量)保存了undefined。
在函数中不加var来声明的变量,就会保存到window上,变成全局变量。
全局变量哪都能用。
typeof操作符可以判断:undefined boolean number string function object
其中null经过typeof也返回给你object,因为它被当成一个空的引用。
数组、函数、对象都是对象,它在内存上保存的仅仅是一个引用地址,通过这个引用才可以找到真正的值。
undefined == null => true
undefined === null => false
NaN === NaN => false
+0 === -0 => true
Object.is()用来判断两者是否相等。
Object.is(NaN, NaN); => true
Object.is(+0, -0); => false
parseInt(开头不是数字的字符串); => NaN
parseFloat(开头不是数字的字符串); => NaN
null和undefined没有toString()方法。
var o = new Object();
var o = new Object; // 可以省略 但最好不要省略
Object相当于Object类,而o就是Object这个构造函数原型派生出来的一个对象。
Object的实例的属性方法:
o.constructor:构造函数,保存着当前实例的函数。比如上面的Object。
o.hasOwnProperty(propertyName):检查当前实例上是否存在给定的属性。(跟原型无关)
Object.prototype.isPrototypeOf(o):用于测试一个对象是否存在于另一个对象的原型链上。
toString():返回字符串表示。
valueOf():返回当前对象的字符串、数组或布尔值表示。
var a=0
++a; // 0
a // 1
var b=0
b++; // 1
b // 1
与&&
或||
非! !true === false
逻辑短路:
true && 1 => 1
true || 1 => true
false && 2 => false
false || 2 => 2
2**3 => 8
23 < 3 => false
"23" < 3 => false 先把"23"转换成数字23,然后比较
"a" < 3 => false "a"转换成数字变成NaN,NaN不等于任何数字
"23" < "3" => true 都是字符串就比较字符编码的大小
如果是对象的比较,先执行valueOf(),如果没有valueOf()方法,那么就调用toString()方法。然后用基本类型值继续比较。
三元运算符:var value = 5 > 10? '5胜出!' : '10胜出';
循环语句可以重复做一件事情,这样就不用每一次代码都写出来了。
有固定次数时,使用for循环,否则用while循环。
循环中有两种特殊用途的语句:break语句和continue语句。
break语句跳出当前循环体,不循环了。
continue语句跳过当前一轮的循环,进行下一轮循环。
函数是一组逻辑的封装,一般一个函数就代表一个功能。通过执行函数来实现对应的功能。
函数放在对象里,就称为对象的方法。执行对象里的函数,我们称之为调用对象的方法。
函数无需指定返回值,因为它没有指定返回值类型这一说,但最好有返回值,格式是return 返回值。
如果没有指定返回值,那么会默认返回一个undefined作为返回值。
return语句后面的语句永远不会执行。
函数的实参可以随便传几个,它们会被保存在arguments对象里,arguments是一个类数组。(一个有length属性,类似数组,保存着实参的列表。)
如果没有传递参数,和var未初始化一样,会被赋值为undefined。
Java中可以定义函数签名(接受参数的类型和数量),只要签名不同,就可以同时存在同名的函数,
但是JS中不可以!它的函数参数是一个类数组,因此JS没有函数重载,重名会被后者覆盖!
第4章 变量作用域和内存问题
变量的值分两种,一种是基本数据类型值,一种是引用类型值。
基本数据类型值是按值访问的,它可以直接操作保存在变量中的实际的值。他们被放在栈内存中。
引用类型值是按引用访问的,只能操作它的引用地址,不能操作对象的内存空间。他们被放在堆内存中。
引用类型会有各种属性和方法。
当我们复制基本类型值时,原来的值和复制得到的值是相互独立、互不干扰的,是两码事。
当我们复制引用类型值时,复制的值只是一个指针一个地址而已,这两个指针指向同一个对象。当操作任何一个变量时,都会互相发生作用。
js中的所有函数参数都是按值传递的,这意味着函数执行后并不会改变作为参数传递进去的那个外部的变量值。
通过instanceof操作符检测对象的类型,看它是哪个构造函数的实例对象。(instance就是例子的意思。)
如果用基本类型就会返回false,因为基本类型不是对象。基本类型使用typeof来检测。
执行环境又叫执行上下文, 环境中的变量和函数可以访问其他数据,而每个环境中都有一个对应的变量对象(VO)。这个变量对象里保存着环境中所有的变量和函数。
按规则在浏览器中,window对象被认为是全局的执行环境,所以所有的全局变量和函数都是挂载在window对象上的。这个window对象就是全局环境中的变量对象。
这就是当你var value = 123;时,你就能访问到window.value是123的原因。
每个函数都有自己的执行环境。当执行函数时,函数会产生一个环境,这个环境被推入到一个环境栈中(此时它掌握控制权),而函数一旦执行完成,函数环境就会从环境栈中弹出。
注意,当环境出现时,就会随之产生一个变量对象,通过变量对象的作用域链,我们可以访问所有的变量和函数。
活动对象:如果这个执行环境是函数产生的,它的变量对象就是活动对象(AO),这里面一开始只有一个arguments对象(全局环境中并没有它),沿着作用域链可以找到上一层的变量对象,最外层就是全局的变量对象(GO)。(全局的变量对象始终是作用域链的最后一个对象。)
原生JS中只有全局作用域和函数作用域。(函数作用域也叫局部作用域)
简而言之就是,每一个环境下(不管是全局环境还是函数的局部环境),都会对应有一个变量对象,变量对象包含着当前作用域中所有的变量和函数。
比如,全局环境下的变量对象就有全局中所有的变量和函数。
特殊一点的是,在执行函数时会产生函数环境,它的变量对象叫做活动对象,与全局环境不同的是,arguments对象是函数变量对象的专属。
在函数环境中,可以访问到父级作用域内变量的原因就是作用域链,沿着它就能访问上一层作用域的变量了。这就是为什么函数内部可以访问全局变量的原因。
另外,外部环境是没有办法反过来访问到内部环境中的变量的。函数一旦执行完毕,函数环境内的变量就会被销毁。
但是如果在执行函数时,函数内部的变量被返回出来,因为它是连着活动对象的作用域链,所以这时外部就能访问内部的值,这就是闭包。
(上面的变量包括基本类型和引用类型,执行环境也叫作作用域。)
原生JS只有全局作用域和局部作用域(函数),但是没有块级作用域,这就意味着类似于for循环这种代码块里的变量可以被全局使用,这样代码块里的变量就很容易被污染。
标记清除,给当前不适用的值加上标记,然后再回收内存。(主流)
引用计数,当声明一个变量并引用值给它时,就会产生1个引用计数,变量每次被引用时次数+1,使用完成后次数-1,当计数为0时意味着它的内存被回收。(几乎不用)
JS的内存分配和回收会自动执行,它有自动垃圾收集机制。
第5章 引用类型
对象是引用类型的值,它是某个特定引用类型的实例。
对象是描述了一类事物的属性和方法。
新对象通过new+构造函数来创建。
var person = new Perosn(); // person就是Person这个构造函数的实例/对象。
对象还可以用对象字面量表示法来创建,var perosn = {};
访问对象的方法:第一种是点表示法,第二种是方括号表示法。
点表示法:obj.propertyName
方括号表示法:obj[propertyName] //注意:方括号里不仅可以放字符串,放一个值为字符串的变量也是可以的!
数组也是对象的一种,new Array()得到数组实例。
new Array(20)表示一个length长度为20的数组。
数组字面量表示法:var arr = [1,2,3,4];
数组的下标从0开始,比如你要访问第一个数组项arr[0],如果是第二个呢?那就是arr[1]。
数组的最后一项下标是length-1
检测数组的方式一:arr instanceof Array
检测数组的方式二:Array.isArray(arr)
因为数组也是对象,因此它继承了Object原型对象中的方法toString()、valueOf()等方法。
toString()方法可以把数组内的数组项按照字符串形式返回,默认是用逗号分隔的。
数组的join()方法可以指定分隔符将数组项以字符串形式返回。
数组属性和方法
函数也是对象。每个函数都是Function类型的实例。
函数的声明方式有两种,一种是函数声明,一种是函数表达式。
函数声明:function f() {};
函数表达式:var f = function(){}
还有一种方式不推荐:var sum = new Function('n1', 'n2', 'return n1+n2'); // 函数是对象,函数名是指针。
JS有个规则是声明提升,意思是说js在解析过程中会进行预先的编译(预编译)找到那些声明,并把它们提升到顶部。
函数声明的优先级大于函数表达式,函数声明在执行代码之前就可以访问,但是函数表达式不行,它必须要等到执行到那一行才可以,否则在这之前都是undefined。
函数内部有两个特殊的对象:this和arguments。
this引用的是函数执行的环境对象,一般来说这个this对象是window,但是如果被其他对象调用,那么this就指向那个调用它的对象。
注意,函数名实际上是一个含有指针的变量,它是指向一个函数的,虽然调用它的可能是不同的人让this对象发生了变化,但是这个函数名背后的函数一直是同一个函数。
函数的属性:length和prototype
length是指它的形参长度。
prototype是指它的原型对象,所有对象都有toString()方法,这个toString()方法实际上就保存在prototype里。
函数的方法:apply()和call()
它们的作用都是在特定作用域中调用函数,这样就可以设置函数体内this对象的值。
这就意味着对象不用在内部定义一堆方法,可以通过call方法调用其他作用域中的函数。
apply和call的第一个参数都是目标对象,第二个参数略有不同。
apply的第二个参数可以传入一个arguments对象,也可以传入一个数组。
call只能一个一个传值。
bind()方法可以将this绑定到它给定的对象上。它会返回这个函数的的实例。
基本包装类有Boolean、Number、String。它们是比较特殊的引用类型。
new Boolean()、new Numeber()、new String()
当给他们这些引用类型去赋值或者调用方法时,都会经历一个过程:创建一个相关实例,实例调用指定方法,销毁这个实例。
引用类型和包装类型的主要区别在于对象的生存周期。
new出来实例会一直保存在内存中,直到当前作用域消失。
包装类型的对象只会存在于一行代码执行的瞬间,然后立即销毁。(这就是为什么给包装类指定了属性再访问它却只是输出undefined的原因,当你去访问时那个实例早就消失了。)
当我们用typeof去检测包装类的类型时,返回object。
转型函数:var str = '25'; var num = Number(str); // num=25
保留2位有效数字字符串:num.toFixed(2);
字符串属性和方法
两个内置对象:Global对象和Math对象。
Global对象通过window对象来访问,全局变量和函数都是Global对象的属性。
第6章 面向对象程序设计
ESMAScript中没有类的概念。
对象是一组无序属性的集合,属性可以包含基本值或引用值。
对象每一个属性或方法都有一个名字,每个名字对应一个值。
只有对象内部才可以访问的属性,用[[]]封闭起来。这些属性有数据属性和访问器属性。
数据属性有[[configurable]]、[[enumerable]]、[[writable]]、[[value]]。
[[configurable]]:能否删除和修改属性。
[[enumerable]]:能否通过for in循环返回属性,也就是是否是可枚举的。
[[writable]]:能否修改属性值。
[[value]]:数据值,读的时候从这里读,写的时候新值保存在该位置。
Object.defineProperty(目标对象,属性名称,描述对象) // 修改对象的内部特性Property
var person = {};
Object.defineProperty(person, 'name', {
writable: true,
value: 'mike'
});
一旦描述对象中的configurable被设置为false,表示不可配置,于是就不能把它变回可配置了。
如果通过这个方法来创建一个新属性时没有给描述属性配置相关的属性,那么configurable、enumerable、writable的值为false。
如果是修改已经存在的属性,那么就没有这种限制。
访问器属性没有[[value]],而有[[get]]、[[set]]。
[[get]]:在读取属性时调用的函数getter。默认值为undefined
[[set]]:在设置属性时调用的函数setter。默认值为undefined
定义多个属性:Object.defineProperties(目标对象,{目标属性1:{描述属性}, 目标属性2:{描述属性}, ...})
读取属性的特性:Object.getOwnPropertyDescriptor(目标对象,属性名称)
创建对象:工厂模式(问题:有对象识别问题)、构造函数模式(问题:实例方法每次都会重建一遍)、原型模式(问题:共享一个原型对象,一个变化所有实例都变)、组合模式(构造函数+原型,认可度最高)。
new实例经历的过程:1.创建一个新对象;2.将构造函数的作用域给新对象(this指向这个新对象);3.指向构造函数中的代码(新对象添加属性);4.返回新对象。
js继承的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
函数有原型对象,对象有原型指针。
每一个实例对象的原型指针都指向了它的构造函数的原型对象。
继承方式:原型式继承、寄生式继承、寄生组合式继承。
未完待续。。。