JavaScript学习笔记

一、网页中JavaScript一般可分为3个部分:

1. ECMAScript:JavaScript的语法核心;

2. DOM:文档对象模型,HTML各个节点,JavaScript可以操作DOM;

3. BOM:浏览器对象模型,JavaScript对浏览器进行操作; 

二、原始值和引用值

1. 原始值:存放在栈中的简单数据,如 var a = 3, b = a;  此时,a, b是两个独立的互不影响的变量,修改b的值不会影响到a,我们称 a, b 为原始值,在JavaScript中,原始值有5种:null,undefined,Number,Boolean,String。

2. 引用值:存放在堆内存的对象数据,如var obj = new Object(); 此时的obj本质上是一个指针,指向一个对象的堆内存地址。

var obj1 = new Object();
obj1.name = "zhang";

var obj2 = obj1;
obj2.name = "li";

console.log(obj1.name); // "li"

以上例子中,obj2 与 obj1 两个指针都指向同一个对象,当修改obj2.name时,obj1.name 也会同时改变,这是需要注意的地方。

 

三、undefined与null

在JavaScript中,undefined并不表示未定义的对象,它只是代表该变量未初始化,而 null 则表示该变量为空,如:

var a;
alert(a); // undefined
alert(b); // 报错

当一个函数没有返回值时,返回值是undefined,如:

function test () {
}
alert(test()); // undefined

 

四、Object类

Object类本身很少用,但是如同Java一样,所有对象都继承自Object类,Object类有如下几个重要方法:

1. constructor 获取该对象的类型。

var c = ["red", "blue", "green"];
if (c.constructor == Boolean) {
    console.log("This is Boolean");
} else if (c.constructor == Object) {
    console.log("This is Object");
} else if (c.constructor == Array) {
    console.log("This is Array");
}
// This is Array

2. prototype 该方法作用在类上面,指向一个类的所有属性,可以动态为类添加方法或属性。

// 定义一个Persion类
function Person(name) {
    this.name = name;
}

// 给Person类添加一个setAge方法
Person.prototype.setAge = function (age) {
    this.age = age;
}

// 给Persion类添加一个值
Person.prototype.finger = 10;

var p = new Person("zhangsan");
p.setAge(25);
console.log(p.age); // 25
console.log(p.finger); // 10
console.log(Person.prototype); // age方法,finger属性及Person类本身

prototype是一个非常强大的方法,他可以为任何类新增方法,也可以重写已有方法,为JavaScript提供无限可能!

3. hasOwnProperty 判断一个对象是否具有某个方法或属性。

var obj = new Object();
obj.test = function () {
}
obj.name = "zhang";
console.log(obj.hasOwnProperty('test')); // true
console.log(obj.hasOwnProperty('name')); // true
console.log(obj.hasOwnProperty('age')); // false

经测试,通过prototype添加的属性貌似不能用hasOwnProperty判断。

4. toString 将对象转为字符串。

var colors = ["red", "blue", "yellow"];
console.log(colors.toString()); // red,blue,yellow

 

五、其他常用运算符

1. instance 用来精准识别一个对象的类型。

var c = ["red", "blue", "green"];
console.log(c instanceof Array); // true
console.log(typeof c); // Object

typeof具有局限性,很多情况下typeof返回一个Object,这个时候可以用instance判断,与前面的constructor类似。

2. delete 可以删除对象的方法或属性。

var p = new Object();
p.setName = function (name) {
    this.name = name;
}
p.setName("zhang");
delete p.setName;
p.setName("li"); // 报错

delete只能删除开发者自定义的方法或属性,不能删除JavaScript的自带方法。

3. void 用于对任何返回结果都置为undefined,如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JavaScriptTest</title>
    <script>
        function clickMe() {
            return "abc";
        }
    </script>
</head>
<body >
<a href="javascript:void(clickMe())">啊啊啊</a>
</body>
</html>

如果不加void,则点击之后链接会跳转,加上void,点击链接之后什么也不会发生。

 

六、JavaScript函数

(1)函数定义:JavaScript函数非常神奇,最常见的两种函数定义如下:

// 写法1
function func1 (arg1, arg2) {
    console.log("func1");
}

// 写法2
var func2 = function (arg1, arg2) {
    console.log("func2");
}

以上两种函数定义方法略有区别:func1不管是写在前面还是后面,func1在脚本执行的时候会马上定义;而func2类似一个变量,一定要先定义func2,再执行func2(),否则会报错。

func1();
function func1 (arg1, arg2, arg3) {
    console.log("func1");
    func3();
    return;
    function func3() { // func3可以透过return定义,因为他只要一执行func1,func3马上被定义
        console.log("func3");
    }
}

func2();
var func2 = function (arg1, arg2) {
    console.log("func2");
}

// func1
// func3
// 报错

(2)函数重载:JavaScript函数不能重载(所谓重载,也就是函数名称相同,函数的参数个数或参数类型不同),他会被后面的函数给覆盖。

function clickMe() {
    alert(100);
}
function clickMe(x) {
    alert(x);
}
clickMe(); // undefined

此处clickMe()里面并未传参,但是他执行的不是第一个函数,而是第二个。

(3)函数的arguments对象:

JavaScript函数中,有一个特殊的对象arguments,可以通过arguments获取函数的任何一个参数:

function func1 (x, y, z) {
    console.log("x = " + x + ", y = " + y + ", z = " + z);
    console.log("x = " + arguments[0] + ", y = " + arguments[1] + ", z = " + arguments[2]);
}
func1(3, 4, 5);
// x = 3, y = 4, z = 5
// x = 3, y = 4, z = 5

arguments还可以用来检测函数的参数个数,arguments.length, 虽然func1的定义中是三个参数,但其由于没有重载,实可以传递更多或更少的参数,与其他的编程语言大不相同,JavaScript的自由度如此之大,是不是惊呆了!

(4)Function类

函数的定义方式除了上面介绍的两种,还有一种用类创建实例的方法创建一个函数:

var func1 = new Function("arg1", "arg2", "return arg1*arg2");
console.log(func1(3, 4)); // 12

由于是参数和函数体都是字符串的关系,写起来比较困难,读起来也不爽,所以实际上没见过有人用Function类来创建函数的,不过可以用这种写法来说明函数重载的问题。

var clickMe = new Function("alert(100)");
var clickMe = new Function("x", "alert(x)");
clickMe(); // undefined

现在知道为什么会执行第二个函数了,第一个函数被重写覆盖了。

(5)函数闭包

function add() {
    function doAdd (x, y) {
        return x + y;
    }
    var s = 0;
    for (var i = 0; i < arguments.length; i++) {
        s = doAdd(s, arguments[i]);
    }
    return s;
}
var sum = add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
console.log(sum); // 55

doAdd函数就是add函数的一个闭包,他在add函数里面,不受外界影响。

 

七、JavaScript对象

(1)JavaScript对象可以分为3种:

    1. 本地对象 JavaScript语法本身提供的对象,如Object,Array,String,Boolean,Number,Date,Function等;

    2. 内置对象 JavaScript有一个顶层的全局对象,在JavaScript中,实际上不存在独立的函数,所有的函数都必须是某一个对象的方法,如isNAN(),parseInt(),eval()等,我们在使用它们的时候并未写出其具体是那个对象调用的方法,他是属于全局对象的方法;另外还有Math对象,如Math.floor()等;

    3. 宿主对象 JavaScript运行环境提供的对象,如DOM,BOM等。

(2)JavaScript对象的一些常用方法

Array对象:拼接 concat(),截取 slice(),入栈 push(),出栈 pop(),入队 unshift(),出队 shift(),逆序 reverse(),删除或替换 splice(),位置检索 indexOf();

String对象:拼接 concat(),截取 slice(),分割 split()。

内置对象提供了一个eval() 方法,这是一个解释程序,它可以把一段字符串解释成一段JavaScript代码,如:

var str = "function sayHi(s) {alert(s)}";
eval(str);
sayHi("hello"); // 弹出 "hello"

(3)作用域

与其他的面向对象语言不一样,JavaScript中没有私有对象,任何对象的函数或属性都是公共的;

(4)关键字this

this总是指向调用该方法的对象。

function sepakLanguage() {
    alert('I speak ' + this.language); // 谁调用sepakLanguage,这个this就是谁
}
var obj1 = new Object();
obj1.language = "Chinese";
obj1.sepak = sepakLanguage;
obj1.sepak(); // I speak Chinese

var obj2 = new Object();
obj2.language = "English";
obj2.sepak = sepakLanguage;
obj2.sepak(); // I speak English

以上方法中,sepakLanguage中要引用对象的属性时,必须使用this关键字。

 (5)设计模式

一般情况下创建一个对象最直接的方式如下:

var persion = new Object();
persion.name = "zhangsan";
persion.age = 25;
persion.sex = "man";
persion.showInfo = function () {
    console.log("name:" + this.name + ", age:" + this.age + ", sex:" + this.sex);
}
persion.showInfo(); // name:zhangsan, age:25, sex:man

但是如果有许多个persion对象要创建,那么这样写显得很啰嗦,于是人们就设计出了多种创建对象的模式:

1. 工厂模式:

function createPersion(name, age, sex) {
    var persion = new Object();
    persion.name = name;
    persion.age = age;
    persion.sex = sex;
    persion.showInfo = function () {
        console.log("name:" + this.name + ", age:" + this.age + ", sex:" + this.sex);
    }
    return persion;
}
var p1 = createPersion("zhang", 27, "women");
var p2 = createPersion("wang", 30, "man");
p1.showInfo(); // name:zhang, age:27, sex:women
p2.showInfo(); // name:wang, age:30, sex:man

仔细琢磨后,我们发现每次创建一个persion对象都要新建一个showInfo()函数,很浪费内存,于是可以把showInfo()函数单独拿出来:

function showInfo() {
    console.log("name:" + this.name + ", age:" + this.age + ", sex:" + this.sex);
}
function createPersion(name, age, sex) {
    var persion = new Object();
    persion.name = name;
    persion.age = age;
    persion.sex = sex;
    persion.showInfo = showInfo;
    return persion;
}
var p1 = createPersion("zhang", 27, "women");
var p2 = createPersion("wang", 30, "man");
p1.showInfo(); // name:zhang, age:27, sex:women
p2.showInfo(); // name:wang, age:30, sex:man

2. 构造函数模式

function Persion(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.showInfo = function () {
        console.log("name:" + this.name + ", age:" + this.age + ", sex:" + this.sex);
    };
}
var p1 = new Persion("zhang", 27, "women");
var p2 = new Persion("wang", 30, "man");
p1.showInfo(); // name:zhang, age:27, sex:women
p2.showInfo(); // name:wang, age:30, sex:man

构造函数模式看上去和工厂模式很像,但构造函数模式是先定义一个类,类里面并不创建对象,使用时需要用这个类去new一个对象,当然,以上写法与工厂模式一样,仍然存在showInfo()会被创建多次的问题,仍然可以和工厂模式一样,把showInfo()提取出来,放到外面去。

3. 原型模式

在前面介绍的工厂模式和对象模式中,为了防止showInfo()被创建,我们把showInfo()提取成一个单独的函数放在外面,但是,这样很难表现出showInfo()是属于对象或类中的方法。前面我们介绍过Object类有一个prototype方法,该方法可以动态为类添加方法或属性,我们可以在构造函数的基础之上做一下改进:

function Persion(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
Persion.prototype.showInfo = function () {
    console.log("name:" + this.name + ", age:" + this.age + ", sex:" + this.sex);
}
var p1 = new Persion("zhang", 27, "women");
var p2 = new Persion("wang", 30, "man");
p1.showInfo(); // name:zhang, age:27, sex:women
p2.showInfo(); // name:wang, age:30, sex:man

通过prototype一举解决了showInfo()与类之间的关联问题!

4. 动态原型模式

有人指出,对于一个类来说,属性和方法都应该是在类的内部定义好,定义好了一个Persion类,又要在类的外部动态添加方法,这显得不和谐,但是,把方法定义在类内部,又会出现多次创建的问题,于是又有人提出动态原型模式:

function Persion(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    if (typeof Persion.hadInitlized == "undefined") {
        Persion.prototype.showInfo = function () {
            console.log("name:" + this.name + ", age:" + this.age + ", sex:" + this.sex);
        };
        Persion.hadInitlized = true;
    }
}
var p1 = new Persion("zhang", 27, "women");
var p2 = new Persion("wang", 30, "man");
p1.showInfo(); // name:zhang, age:27, sex:women
p2.showInfo(); // name:wang, age:30, sex:man

把Persion.prototype.showInfo放进Persion类里面,并且通过给Persion一个标记,让Persion.prototype.showInfo只执行一次,就实现了把方法定义在内部的目的。

 

八、JavaScript的继承

继承是面向对象语言都有的一个特性,不过我至今都没有使用过JavaScript继承,连JavaScript如何继承我都不知道,当我决定研究JavaScript继承的时候,我才知道JavaScript其实没有明确的继承机制,而是由开发者自己通过自己的手段实现的,所有的继承细节,都是有开发者自己决定!

先举个例子,一个父类为Persion,现在想定义一个Student类,该Student类想继承自Persion类,分析一下下面的代码是如何做到的:

function Persion(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.showInfo = function () {
        console.log("name:" + this.name + ", age:" + this.age + ", sex:" + this.sex);
    }
}
function Student(name, age, sex, grade) {
    this.Persion = Persion;
    this.Persion(name, age, sex);
    this.grade = grade;
}
var s1 = new Student("zhang", 27, "women", 3);
console.log(s1); // Student {Persion: ƒ, name: "zhang", age: 27, sex: "women", grade: 3}
s1.showInfo(); // name:zhang, age:27, sex:women

在子类中,极为巧妙地先给Student赋予一个Persion方法,然后调用Persion方法,就实现了对Persion的继承,接下来再给Student赋予自己的属性,或重写父类的方法,就这样实现了继承。

通过打印s1对象,我们发现s1中包含了一个Persion f对象,我们甚至可以直接通过 var p1 = new s1.Persion("xiaoming", 8, "man"); 又来创建一个persion对象,这显然不太合适,于是一般我们在继承完了之后,需要删除对Persion的引用。

function Persion(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.showInfo = function () {
        console.log("name:" + this.name + ", age:" + this.age + ", sex:" + this.sex);
    }
}
function Student(name, age, sex, grade) {
    this.Persion = Persion;
    this.Persion(name, age, sex);
    delete this.Persion; // 删除对父类的引用
    
    // 新属性和新方法必须要在解除了对父类的引用后再定义,否则有可能会覆盖了父类的属性或方法,带来不可预知的错误,所以,上面那条delete语句也很有必要写上
    this.grade = grade;
}
var s1 = new Student("zhang", 27, "women", 3);
console.log(s1); // Student {name: "zhang", age: 27, sex: "women", grade: 3}

更加神奇的事情是,使用这种方法,可以轻松地实现多继承,比如有ClassA与ClassB,现在想定义一个类ClassC同时继承ClassA和ClassB,通过以下方法可以轻而易举地实现:

function ClassC() {
    this.ClassA = ClassA;
    this.ClassA();
    delete this.ClassA;

    this.ClassB = ClassB;
    this.ClassB();
    delete this.ClassB;
}

太魔幻了吧!

由于以上继承方式的流行,JavaScript后来特意为Function对象添加了两个方法:call()和apply()。

先看一下这两个方法是干嘛的:

function Persion(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.showInfo = function () {
        console.log("name:" + this.name + ", age:" + this.age + ", sex:" + this.sex);
    }
}
var p = new Object();
Persion.call(p, "xiaohong", 10, "women");
console.log(p); // {name: "xiaohong", age: 10, sex: "women", showInfo: ƒ}
p.showInfo(); // name:xiaohong, age:10, sex:women

call()是Function的方法,该函数的第一个参数是赋给this的对象,后面的参数是该函数本身的参数,大概描述一下call()函数的作用:把某个函数作用到某个对象上。

有了call方法,Student与Persion的继承关系可以写成:

function Persion(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.showInfo = function () {
        console.log("name:" + this.name + ", age:" + this.age + ", sex:" + this.sex);
    }
}
function Student(name, age, sex, grade) {
//    this.Persion = Persion;
//    this.Persion(name, age, sex);
//    delete this.Persion;
    Persion.call(this, name, age, sex); // 前面三句可以使用call代替
    this.grade = grade;
}
var s1 = new Student("zhang", 27, "women", 3);

apply()方法与call()方法差不多,只是把call()方法中的除this之外的参数写成数组的形式:

function Student(name, age, sex, grade) {
    Persion.apply(this, [name, age, sex])
    this.grade = grade;
}

除了通过方式实现继承,JavaScript还有一种有趣的继承方式,原型链:

function Persion() {}
Persion.prototype.setName = function (name) {
    this.name = name;
}
Persion.prototype.setAge = function (age) {
    this.age = age;
}
Persion.prototype.setSex = function (sex) {
    this.sex = sex;
}

function Student() {}
Student.prototype = new Persion();
Student.prototype.setGrade = function (grade) {
    this.grade = grade;
}
var s = new Student();
s.setName("xiaoming");
s.setAge(7);
s.setSex("man");
s.setGrade(2);
console.log(s);

原型链的有趣之处在于,它不需要将父类的属性一个一个赋给子类。但是要注意:Persion类是没有参数的,如果像这样写,是没有用的:

function Persion(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}

function Student() {}
Student.prototype = new Persion();
Student.prototype.setGrade = function (grade) {
    this.grade = grade;
}
var s = new Student("xiaoming", 8, "man");
s.setGrade(2);
console.log(s); // Student {grade: 2}, 只打印了grade,并没有继承到父类的属性

由此可见,JavaScript的继承完全由开发者自定义,当然还有更多的继承方式,在一般的网页开发里面,我是没有用遇到过继承这种需求的,我也只是为了研究一下JavaScript继承到底是怎么回事,只是肤浅地了解了一下。

 

JavaScript的语法就写到这里,其实有些东西自己并不懂。

posted @ 2019-06-01 21:52  wwh99  阅读(278)  评论(0编辑  收藏  举报