JavaScript语言

概述

JavaScript是动态类型与语言,而Java是静态类型语言。

学习一门编程语言套路基本差不多,首先是基础的数据类型,对基础数据类型有深刻的理解,那就是学成了一半。然后就是基础的运算符与表达式,最后是一些区别的高级特性如面向对象、并发相关的。

基础数据类型

一共六类:string、number、boolean、null、undefined、object

为什么有null和undefined类型?

类型相同使用 === ,必须类型相同

类型不同,尝试类型转换做比较

console.log(null == undefined);

// true string转number
console.log(1 == "1.0"); 

// true boolean转number
console.log(1 == true);

// object == string | number 尝试转为基本类型
new String("hi") == "hi";

包装类型

JS的string、number、bollean有对应的包装类,在对一个基础类型调用方法的时候会生成一个临时的包装类。

console.log(string.length)

string.t = 1;

console.log(string.t)

如上程序,输出6和undefined,原因是第二行生成的为临时对象,第三行又生成个临时对象但是和第二行的对象是不一样的。

类型检测方法

console.log(typeof 100)
console.log(typeof NaN)
console.log(typeof undefined)
// 历史问题,返回object
console.log(typeof null)
// 无法判断具体类型,为object 
console.log(typeof [1, 2])

console.log([1, 2] instanceof Array)
// false是会比较类型的,与java的instanceOf类似
console.log(new Object instanceof Array)

运算符

和java大多相似,有个,运算符,使用最右侧的计算结果

// tmp计算后为3
var tmp = (1, 2,3)

语句

没有块级的作用域

js的块并没有起到作用域的目的,但是有函数作用域

var语句

// b是个全局变量,未声明,直接赋值未全局变量,在use strict模式下是不允许的,会报错Reference Error var a=b=1;

function foo(){
    var a=b=1;
}
foo();
console.log(a) // 3
console.log(b) // undifined

函数声明vs函数表达式

// 函数声明作用域为全局
console.log(fe()) // true
function fe(){
    return true;
}

// 函数表达式本质为一个变量的声明,作用域与变量类似
//console.log(fd()); // fd not a function
var fd = function (){
    return true;
}
console.log(fd()); // true

with修改作用域

不在建议使用,js不好优化,可读性差。

with({x:1}) {
    console.log(x);
}

严格模式

use restrict,

对象

prototype与属性标签

var descriptor = Object.getOwnPropertyDescriptor(Object, 'prototype')
console.log(descriptor.configurable) // false

标签修改

var obj = {x:1, y:2};
Object.preventExtensions(obj);
// Object {value: 1, writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(obj, 'x'));
Object.seal(obj);
// Object {value: 1, writable: true, enumerable: true, configurable: false} configurable 为false
console.log(Object.getOwnPropertyDescriptor(obj, 'x'));
Object.freeze(obj)
// Object { value: 1, writable: false, enumerable: true, configurable: false } writable 和 configurable都为false
console.log(Object.getOwnPropertyDescriptor(obj, 'x'));

数组

JS中的数组是弱类型的,一个数组内可以有不同类型的元素

方法

join

var array = [1, 2, 3]
// 输出1+2+3
console.log(array.join("+"))
// @@@
console.log(new Array(3).join('@'))

reverse

var array = [1, 2, 3]
array.reverse();
// 会对原数组改变
console.log(array)

sort

var arr = [11, 30, 2]
// 字母顺序排序
arr.sort()
console.log(arr)
// 从小到大排序
arr.sort(function(a, b){
    return a - b;
})
console.log(arr)

concat

var arr = [1, 2, 3]
// [1, 2, 3, 4, 5, 6]
console.log(arr.concat(4, [5, 6]))
// 不会改变原数组
console.log(arr)
// [1, 2, 3, 4, Array(2)], 不会拉平两次
console.log(arr.concat(4, [[5, 6]]))

####slice

var arr = [1, 2, 3, 4, 5]
// 左闭右开 [2, 3],原数组未修改
console.log(arr.slice(1, 3))
// 第二个参数默认-1
console.log(arr.slice(1))
// 倒数第四个到倒数第三个 2
console.log(arr.slice(-4, -3))

splice

var arr = [1, 2, 3, 4, 5]
// 从第三个元素开始切割,切割原来数组,[3, 4, 5]
console.log(arr.splice(2))
// 对原来数组进行了改变 [1, 2]
console.log(arr)

// 从第三个元素开始切割,切割两个
arr = [1, 2, 3, 4, 5]
// [3, 4]
console.log(arr.splice(2, 2))
// [1, 2, 5]
console.log(arr)

arr = [1, 2, 3, 4, 5]
// 切割两个并加入两个 [3, 4]
console.log(arr.splice(2, 2, 'a', 'b'))
// [1, 2, 'a', 'b', 5]
console.log(arr)

foreach

var arr = [1, 2, 3, 4, 5]
// value,index,数组自身
// 1 | 0 | true
// 2 | 1 | true
// 3 | 2 | true
// 4 | 3 | true
// 5 | 4 | true
arr.forEach(function(x, index, a){
    console.log(x + '|' + index + '|' + (a === arr))
})

map

var arr = [1, 2, 3, 4, 5]
// 对每个元素操作,并返回新数组  [11, 12, 13, 14, 15]
console.log(arr.map(function(x){
    return x + 10;
}))
// 不会对原来数组改变 [1, 2, 3, 4, 5]
console.log(arr)

filter

var arr = [1, 2, 3, 4, 5, 6]
// 元素筛选  [2, 4, 5, 6]
console.log(arr.filter(function(x){
    return x % 2 == 0 || x > 4;
}))
// 不会对原来数组改变 [1, 2, 3, 4, 5, 6]
console.log(arr)

every & some

var arr = [1, 2, 3, 4, 5, 6]
// 任何一个都满足条件,false
console.log(arr.every(function(x){
    return  x > 4;
}))
// 有一个满足条件,true
console.log(arr.some(function (x) {
    return x > 4;
}))

reduce

var arr = [1, 2, 3, 4, 5, 6]
// 第二个参数为第一次迭代时候x的值
var sum = arr.reduce(function(x, y){
    return x + y;
}, 100)
console.log(sum)
var max = arr.reduce(function(x, y){
    return x > y ? x : y
})
console.log(max)

indexOf

var arr = [1, 2, 3, 2, 1]
// 1
console.log(arr.indexOf(2))
// -1
console.log(arr.indexOf(99))
// 4
console.log(arr.indexOf(1, 1))
// 4
console.log(arr.indexOf(1, -3))
// 3
console.log(arr.lastIndexOf(2))
// 1
console.log(arr.lastIndexOf(2, -3))

函数

函数声明、函数表达式、函数构造器

// 函数表达式,有名字的函数对像,可以用在递归的场景
var fun = function nfe(){};

// 可以用函数构造器(更像对象了), new 也可以省略
var func = new Function('a', 'b', 'console.log(a + b)')
func(1, 2)

// func构造器不能访问函数对象外层的局部变量,能访问全局变量 undefined string
var globalVal = 'global';
(function(){
    var localVal = 'localVal';
    Function('console.log(typeof localVal, typeof globalVal)')();
})();

this

全局的this(浏览器)

//浏览器下,为window
console.log(this === window)

this.a = 37
// 37
console.log(window.a)

对象方法函数的this

// this 代表对象本身
var o = {
    prop : 33,
    f : function() {
        return this.prop;
    }
};
console.log(o.f())

// 还可以这样去赋值、调用
var o = {prop:33}
function func(){
    return this.prop;
}
o.f = func;
console.log(o.f())

原型链上的对象this

// 与java中继承差不多
var o = {f:function(){ return this.a + this.b}}
var p = Object.create(o)
p.a = 1;
p.b = 2;
console.log(p.f())

构造器中的this

// 函数对象的构造器,也能当类使用,会返回this,如果返回的是个基本类型,也会返回this
function MyClass(){
    this.a = 33;
}

var o = new MyClass();
console.log(o.a)

// 但是如果返回一个对象,this就是返回的对象
function MyClass(){
    this.a = 33;
    return {a:34}
}

var o = new MyClass();
console.log(o.a)

call与apply的this

// call,apply参数为数组
function add(a, b){
    return this.c + this.d + a + b;
}
var o = {c:1, d:2}
console.log(add.call(o, 3, 4))
console.log(add.apply(o, [3, 4]))

bind与this

// bind方法能把函数中的this与对象绑定在一起,变成两外一个已绑定的函数对象
function f(){
    return this.a;
}

var g = f.bind({a:'test'})
console.log(g())

var o = {a:22, f:f, g:g}
console.log(o.f(), o.g())

arguments

bind与currying

// 通过指定特定参数能够生成其他的函数对象
function foo(a, b, c){
    return a + b + c;
}

var fun = foo.bind(undefined, 100)
console.log(fun(1, 2))

var fun2 = fun.bind(undefined, 200)
console.log(fun2(10))

闭包

与c++中的不一样,c++中的是拷贝了个变量的副本,但js中的好像是保存了对原来环境中的变量的引用。如下,两次调用后,原来函数内的v会被修改。

function foo(){
    var v = 'hello';
    return function(){
        v += ' world';
        return v;
    }
}

var func1 = foo();
// hello world
console.log(func1())
// hello world world
console.log(func1())

作用域

JS很奇怪,没有块级别的作用域,也就是说在一半的for循环如for(var i=0; i<10; i++)在外部也能访问到i的,所以一半for循环不建议定义在括号内,而是定义在括号外。

没有块级作用域,那像定义局部变量有什么方式?通过函数作用域:

// 通过函数
(function(){
    var a = 1;
})();

// !代替括号,如果没有会变成函数声明,需要用函数表达式方式,并且带个()立即执行
!function(){
    var a = 1;
}();

执行上下文与变量对象

上下文并不是一个概念,竟然有个对象,如在全局上下文中有个对象GlobalContextVO,在NodeJs中是gloable,在浏览器中是windows。

// 为什么能在js中直接调用Math、isNaN等方法(对象)?JS没有静态方法的概念,一切都是对象,因为js的引擎会在第一行初始化全局的上下文对象
String(10); // [[global]].String(10);
var a = 10
// 10,在浏览器中
console.log(window.a) 

VO(变量对象)初始化

VO按照如下顺序进行填充

function fun(x, y, z){
    function x(){};
    console.log(x)
}
// function x(){ … } 覆盖
fun(100)

function fun1(x, y, z) {
    function x() { };
    var x;
    console.log(x)
}

// function x() { … },变量重复忽略
fun1();
// function x(){ … }  初始化(全局的无函数参数),先找函数声明
console.log(x)
// 没有局部作用域的概念,所以a为undefined
console.log(a)

var x = 10;
// 在赋值阶段对x进行赋值,赋值后为10
console.log(x)
x = 20;

function x(){};
// 20 因为函数声明提前,是赋值20后的值
console.log(x);

if(true){
    var a = 1;
} else {
    var b = true;
}

// 1
console.log(a)
// undefined
console.log(b)

OOP

原型链


// 创建一个类,直接用function可以,但是有些奇怪,如果是函数一为小写开头
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// vscode 建议转化为一个新的类
class PersonNew {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}

Person.prototype.hi = function() {
    console.log("Hi my name is " + this.name + ", I'm " + this.age + "years old.")
}

Person.prototype.walk = function() {
    console.log(this.name + " is walking.")
}

var p = new Person("bobo", 3);
p.hi();
p.walk();

function Student(name, age, className) {
    Person.call(this, name, age);
    this.className = className;
}

// Object.create创建一个对象,并且对象的原型指向参数,兵器赋值给Student.prototype,原来的Student.prototype指向Object.prototype
Student.prototype = Object.create(Person.prototype);
// 可以不写
Student.prototype.construct = Student;

Student.prototype.hi = function() {
    console.log("Hi my name is " + this.name + ", I'm " + this.age + "years old" + ", and from "
    + this.className)
}

var boboStu = new Student("bobo", 3, "Class 3, grade 2")
boboStu.hi();
// bobo is walking.
boboStu.walk();

// 每个对象都有个prototype的对象,这个对象里面有个__proto__属性(属性叫原型),而这个原型指向的为另外一个对象(父类)的prototype对象
// prototype对象上可以有很多的属性和方法
// 调用的toString、valueOf、hasOwnProperty等方法,都是继承自Object的prototype
// Object的prototype为空
// null
console.log(Object.prototype.__proto__)
// function toString() { … }
console.log(Object.prototype.toString)

function foo() {}
// true 如果是一个新的对象,默认的prototype为Object的prototype
console.log(foo.prototype.__proto__ === Object.prototype)


// 一个对象可以没有原型
var obj = Object.create(null)
// undefine
console.log(obj.toString)

Student.prototype.x = 111
// 动态修改原型上的属性,对于已实例化的对象是会影响的
console.log(boboStu.x)

Student.prototype = {y:2}
// undefined,已经实例化后,在修改原型是不可以的
console.log(boboStu.y)

Person.prototype.ppp = 2;
// true
console.log('ppp' in boboStu)
// false 判断是否为自己的属性
console.log(boboStu.hasOwnProperty('ppp'))
// 父类的非prototype的属性也是 true
console.log(boboStu.hasOwnProperty('name'))

instanceof


// instanceof 判断左边对象的原型链上是否有右侧函数出现
// true
console.log([1, 2] instanceof Array)
// false
console.log(new Object instanceof Array)
// true
console.log([1, 2] instanceof Object)

Demo

// 匿名函数限定作用域的作用,this为Window或global
!function(global) {
    function DetectorBase(configs) {
        if(!this instanceof DetectorBase) {
            throw new Error("Do not invoke without new")
        }
        this.configs = configs;
    }

    // 模拟接口
    DetectorBase.prototype.detect = function(){
        throw new Error("not Implemented.")
    }

    // 基类实现
    DetectorBase.prototype.analyze = function(){
        console.log("base analyzing...")
        this.data = "###data###";
    }

    function LinkDetector(links) {
        DetectorBase.apply(this, arguments);
        if(! this instanceof LinkDetector){
            throw new Error("Do not invoke without new")
        }
        this.links = links;
    }

    inherit(LinkDetector, DetectorBase)

    function inherit(subClass, superClass) {
        subClass.prototype = Object.create(superClass.prototype)
        superClass.prototype.constructor = subClass;
    }

    LinkDetector.prototype.detect = function(){
        console.log("loading data: " + this.data)
        console.log("Scaning links " + this.links)
    }

    // 防止修改
    Object.freeze(DetectorBase)
    Object.freeze(DetectorBase.prototype)
    Object.freeze(LinkDetector)
    Object.freeze(LinkDetector.prototype)

    // 必须导出才能使用
    Object.defineProperties(global, {
        // 其他属性默认为false
        DetectorBase : {value : DetectorBase},
        LinkDetector: { value: LinkDetector}
    })

}(this)

// 不能直接调用 ReferenceError: DetectorBase is not defined
// DetectorBase.detect();

// vscode 下必须加个this,浏览器正常
var detectorBase = new this.DetectorBase();
detectorBase.analyze();
var linkDetector = new this.LinkDetector("http://www.baidu.com");
linkDetector.detect();

参考

慕课-JavaScript深入浅出

Table of Contents