第一部分 JS语法
1 类型和强制类型转换
在无法创建对象或者找到对象时,将返回null; 在变量未初始化、对象没有指定属性或者数组没有指定元素时,将返回undefined。NaN表示无法表示的数字,故而有typeof NaN 是 number,正如null表示不存在的对象一样, typeof null 是 object,在最新的规范中,null的类型是null。不过浏览器并未遵守这种规范,事实上很少需要在浏览器中确定null的类型。另外需要注意NaN != NaN,需要使用isNaN判断是否是NaN,由于存在自动的类型转换,所以有isNaN("string")也会返回true。
在==比较运算符中,会考虑两种情况,如果两个值类型相同,就直接进行比较,如果两个值类型不同,则尝试将他们转换成相同的类型在进行比较。有以下3中转换的情况,第一,比较数字和字符串,将字符串转换为数字,注意空字符串转换为0。这样也可以理解前面提到的isNaN("String")为true了。比较布尔值和其他类型,讲吧布尔值转换成数字再进行比较。第三,null==undefined返回True。这是因为他们都表示没有值。另外 !==相较于 !=就好比 ===相较于==。<= 和 >=都有类型转换,没有<==和>==运算符。在比较两个对象时,==和===比较两个对象的引用,如果他们相同就返回True。比如[1,2]==[1,2]会返回false,a=[1,2],b=[1,2],a==b返回false,a=[1,2],b=a,a==b,返回true。
除了比较运算符,算术运算符也涉及类型转换,在字符串和数字相加时,将数字转换成字符串再进行拼接,如果想完成数字加,使用Number()显示类型转换。-*/运算符则是将字符串转换为数字。 在js中,有些值并非true或者false,但用于条件表达式中就被视为true或者false。将这些值称之为真值或者假值。js中有5个假值:undefined、null、0、""、NaN,其他的值都是真值。
事件处理程序是异步操作,可能在短时间内发生很多事件,浏览器无法实时处理,则让这些事件按照发生的顺序存储到事件队列中,让浏览器能够依次调用每个事件的处理程序。
2 函数与委托
函数声明函数表达式的差别:首先无论是使用函数声明还是函数表达式,最终都将得到一个函数。但是,使用函数声明时,函数将在执行代码前创建,而使用函数表达式时是在运行阶段执行代码时创建的。另外使用函数声明相当于创建了一个与函数同名的变量,并让它指向函数。
设计函数时有一个不错的经验规则,那就是只让每个函数做一件事情,并把这件事情做好。把js函数当做是值,可以更好地理解js的函数是一等公民。一等公民可以把函数赋给变量、可以把函数传递给函数、可以从函数返回函数。
把函数传递给函数的实例:
function processPassenger(passengers, testFunction){
for(var i=0; i<passengers.length; i++){
if(testFunction(passenger[i]){
return false;
}
}
return true;
}
function checkNoFlyList(passenger){
return (passenger.name === "Dr. Eval")
}
function checkNotPaid(passenger){
return (!passenger.paid);
}
var allCanFly = processPassengers(passengers, checkNoFlyList);
if(!allCanFly){
console.log("The plane can not take off: we hava a passenger on the no-fly-list");
}
var allPaid = processPessenger(passengers, checkNoPaid){
if(!allPaid){
console.log("The plane can not take off: not everyone has paid.");
}
}
我认为这个例子中,将函数作为参数的一个很重要的目的就是替代了一个算法族。像是基于委托的策略模式。
把函数作为返回值得实例:
function createDrinkOrder(passenger){
var orderFunction;
if(passenger.ticket === "firstclass"){
orderFunction = function(){
alert("Would you like a cocktail or wine?")
}
}else{
orderFunction = function(){
alert("Your choice is cola or water.")
}
}
return orderFunction;
}
function serverCunstomer(passenger){
var getDrinkOrderFunction = createDrinkOrder(passenger);
getDrinkOrderFunction();
//让乘客点餐
getDrinkOrderFunction();
getDrinkOrderFunction();
//播放电影
getDrinkOrderFunction();
//清理垃圾
}
3 闭包和词法作用域
《Head First JacaScript》对于闭包这一章节的介绍很是精彩,其将闭包定义为函数和引用环境。看下面两段代码:
var secret= "007";
function getSecret() {
var secret="008";
function getValue() {
return secret;
}
return getValue();
}
console.log(getSecret());
var secret= "007";
function getSecret() {
var secret="008";
function getValue() {
return secret;
}
return getValue;
}
var getValueFun = getSecret();
console.log(getValueFun());
上面两段代码的返回值都是008。两个函数有一个很大的不同点就是getSecret函数的返回类型。一个是函数getValue的引用,一个是函数getValue的运行结果,即字符串。
代码分析:getValue()函数运行得到secret的值008,这里是因为值为008的secret是getSecret函数作用域的局部变量,覆盖了值为007的全局变量secret。如果去掉008的赋值,那么结果将为007。在getValue函数内部可以访问当前作用域的局部变量以及全局变量,关键是看先访问到谁。这里值得注意的是,返回的其实是一个闭包,如果真的只返回函数getValue,那么根本无法知道secret是什么而报错。这里的getValue内部的secret是一个自由变量,即不是在本地作用域中定义的变量。闭包等于环境+函数,环境中指明了自由变量的值,这就是闭包的全部内容了。
这里还涉及到了JS作用域的内容---词法作用域。词法意味着只需要查看代码的结构就能确定变量的作用域,而不是等到代码执行时才明白。对于顶层的函数声明在整个作用域都是全局变量,而对于函数表达式则是从其定义处才开始生效,这是因为浏览器分两遍处理js代码,一次分析函数声明,一次从上往下执行代码。此外,需要知道的是,js中只有函数会引入新的作用域。在{}代码块中声明的var变量在{}外也可以访问,如果将变量声明为let,则在{}外就访问无效了。
4 this的绑定规则
4.1 默认绑定
function
函数调用应用了this的默认调用,this指向全局对象Window。
4.2 隐式绑定
function foo(){
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
obj.foo(); //2
需要注意的是,无论是直接在obj中定义还是先定义在添加为引用属性,这个函数严格来说都不属于obj对象。然而调用位置会使用obj上下文来引用函数,因此可以说函数被调用时,obj对象“拥有”或者“包含”它。当函数引用有上下文时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
一种常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象。也就是它会应用默认绑定。思考如下代码:
function foo(){
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
var bar = obj.foo;
var a = "oops, global";
bar(); //"oops, global"
虽然bar是obj.foo的一个引用,但是实际上它引用的是foo函数本身,,因此此时bar应用了默认绑定。
更微妙。更常见,更出乎意料的情况发生在传入回调函数时:
function foo(){
console.log(this.a);
}
function doFoo(fn){
fn();
}
var obj = {
a: 2,
foo: foo
}
var a = "oops, global";
doFoo(obj.foo); //"oops, global"
setTimeout(obj.foo,100); //"oops, global"
这里,参数传递其实就是一种隐式赋值!!因此传入函数时也会隐式赋值,和上述例子一样。
4.3 显式绑定
function foo(){
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
var a = "oops, global";
var bar = function(){
foo.call(obj);
};
setTimeout(bar, 100); //2
//硬绑定的bar不可能修改它的this
bar.call(window); //2
硬绑定的典型的应用场景就是创建一个包裹函数。
function foo(something){
console.log(this.a, something);
return this.a + something;
}
var obj = {
a: 2
}
var bar = function(){
return foo.apply(obj, arguments);
};
var b = bar(3); //2 3
console.log(b); //5
4.4 new绑定
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
- 创建或者构造一个全新的对象
- 这个新对象会执行[[原型]]连接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new表达式的函数调用会自动返回这个新对象。
function foo(a){
this.a = a;
}
var bar = new foo(2);
console.log(bar.a); //2
优先级:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
5 原型
这里主要区分prototype、__proto__与constructor的关系。
- __proto__和constructor属性是对象所独有的,但是prototype属性是函数所独有的。但是由于JS中函数也是一种对象,所以函数也拥有__proto__和constructor属性。
- __proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象里找,一直找,直到__proto__属性的终点null,然后返回undefined,通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链。
- prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.__proto__ === Foo.prototype。
- constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。
参考:
https://blog.csdn.net/cc18868876837/article/details/81211729blog.csdn.net 一张图理解prototype、proto和constructor的三角关系 - 小火柴的蓝色理想 - 博客园www.cnblogs.com6 异步
首先,JavaScript语言的一大特点就是单线程,支持同步和异步操作。JavaScript异步编程实现主要归为三类:回调函数、发布订阅、Promise对象。这里介绍钱,两种,Promise放在第二部分ES6介绍。JS异步执行的运行机制如下:
- 所有同步任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还存在一个"任务队列"。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
代码实例:
function
//jQuery事件监听:发布订阅模式
$('#btn').on('myEvent', function(e) {
console.log('There is my Event');
});
$('#btn').trigger('myEvent');
参考:
JavaScript 运行机制详解:再谈Event Loopwww.ruanyifeng.com第二部分 ES6下的javascript
第二部分将介绍常见的ES6特性。包括let、const声明;spread/rest;默认的参数值;解构;对象字面量的扩展;模板字面量;箭头函数;for...of循环。此外在代码组织方面,还引入了模块和类。
let声明:声明局部变量,一般配合for循环使用
{ let a = 2, b, c; //一般将作用域的局部变量声明在开头
//...
}
const声明:创建常量。下例中,a可以理解为一个指向固定地址的引用,该引用不能变,而指针指向的内容可以变,
{
const a = [1,2,3] //数组也是object,所以可以修改object型的数据内容
a.push(4)
console.log(a) //[1,2,3,4]
a = 42 //TypeError
}
```
spread/rest: ES6引入了新的运算符... ,通常称为spread或rest。
```
function foo(x,y,z){
console.log(x,y,z)
}
foo(...[1,2,3]) //1 2 3 ##spread##
function(x,y,...z){
console.log(x, y, z)
}
foo(1,2,3,4,5) //1 2 [3,4,5] ##rest##
函数默认参数值:
function foo(x=10,y=10){
console.log(x+y)
}
foo();
function bar(val){
console.log("bar called!")
return y + val;
}
function foo2(x = y + 3, z = bar(x)){
console.log(x, z)
}
var y = 5;
foo2(); //"bar called"
//8 13
foo2(10); //"bar called"
//10 15
y = 6;
foo2(undefined, 10); //9 10
解构:对象字面量是target <-- source, 而对象解构赋值是 source --> target。
funtion bar(){
return {
x : 4,
y : 5,
z : 6
};
}
var { x : bam, y : baz, z : bap } = bar();
console.log(bam, baz, bap) //4 5 6
console.log(x, y, z) //ReferenceError
对象字面量的扩展:ES6为对象字面量新增了几个重要的扩展。
//##简洁属性
var x = 2, y = 3;
o = {
x: x,
y: y
};
//**ES6简化写法
var x = 2, y = 3;
o = {
x,
y
};
//##简洁方法
var o ={
x: function(){
//...
}
y: function(){
//...
}
}
//**ES6简化写法
var o = {
x(){
//...
},
y(){
//...
}
}
//不算ES6新特性但值得一提的Getting/Setting
var o = {
__id : 10,
get id(){return this.__id++;},
set id(v){this.__id = v;}
}
console.log(o.id);
console.log(o.id);
o.id=20;
console.log(o.id);
console.log(o.id);
//计算属性名:
var prefix = "user_";
var o = {
baz: function(...){...}
[prefix + "foo"]: function(){...},
[prefix + "bar"]: function(){...}
};
模板字面量:在${..}中可以出现任何合法的表达式,包括函数调用。
var name = "Kely";
var greeting = `Hello ${name}!`;
console.log(greeting); //Hello Kely!
function upper(s){
return s.toUpperCase()
}
var again_greeting = `Hello ${upper(name)}!`;
console.log(again_greeting); //Hello Kely!
箭头函数:箭头函数是匿名函数,箭头函数转变带来的可读性提升和被转换函数的长度呈负相关。箭头函数的主要设计目的就是以特定的方式改变this的行为特性,解决this相关编码的一个特殊又常见的痛点。
//this是动态绑定的,通过self依赖于词法作用域。
for...of循环:
var a = ["a", "b", "c", "d","e"]
for(var idx in a){
console.log(idx); // 0 1 2 3 4
}
for (var val of a){
console.log(val); //["a" "b" "c" "d" "e"
}
模块
1.
类
class