JavaScript是一种属于网络的脚本语言,常用来为网页添加各式各样的动态功能,是一种动态类型、弱类型、基于原型的语言。
它包括三个部分:ECMAScript、BOM和DOM。ECMAScript描述了该语言的语法和基本对象。BOM,浏览器对象模型,描述了与浏览器进行交互的方法和接口。DOM,文档对象模型,描述了处理网页内容的方法和接口。
js的使用:js代码写在script标签中,其引入方式和css样式的style标签一致。
一、变量、数据类型与运算符
1.变量
声明变量:
- 通过var关键字声明一个或多个变量。
- 声明变量的同时可以给其赋值(直接量,字面量),也可以不赋值,不赋值时默认值为undefined。
- 变量命名遵循一般的命名规范:区分大小写,不能包含特殊字符,大小驼峰命名或下划线命名,不能以数字开头,命名含义明确等。
- 变量在内存中会自动存储于释放。
2.数据类型
js中的数据类型包括:字符串(String)、数字(Number)、布尔(Boolean)、数组(Array)、对象(Object)、空(Null)、未定义(Undefined)。
隐式转换
// 转换为布尔类型
var a; // undefined.
var test = function(vec){
if(vec){
console.log("true");
}else{
console.log("false");
}
};
a = null;
a = 0; // 0值
a = NaN; // NaN值
a = ''; // 空字符串
test(a);
// 转换成数值型数据
var a;// undefined -> NaN
var b = 1;
a = null; // null -> 0
a = false; // false -> 0
a = "123"; // "12" -> 12
a = "123abc"; // "123abc" -> NaN
console.log(typeof a);
console.log(b + a);
强制转换
// parseInt(string):将字符串类型转换成数值;
// parseFloat(string):将字符串类型转换成浮点型数值;
var a = "123";
console.log(typeof parseInt(a));
console.log(typeof parseFloat(a));
// String()和toString():将其它类型转换成字符串
var a = 123;
console.log(typeof String(a));
console.log(typeof a.toString());
// Boolean:将其它数据类型转换成布尔值。;
var a = undefined;
console.log(Boolean(a), typeof Boolean(a));
3.运算符
1.算术运算符
+、-、*、/、%、++、--
++、--分为前缀形式和后缀形式。前缀先自加(减)再执行运算,后缀先执行运算再自加(减)。
2.赋值运算符和比较运算符
=、+=、-=、*=、/=、%=、.=
>、>=、<、<=、==、!=、===、!==
3.逻辑运算符
&&、||、!
4.三元运算符
// exp1?exp2:exp3
var a = 1231; // var a;
typeof a==="number"?console.log("true"):console.log("false");
5.运算符优先级
运算符 | 描述 |
. [] () | 字段访问、数组下标、函数调用以及表达式分组 |
++ -- - ~ ! delete new typeof void | 一元运算符、返回数据类型、对象创建、未定义值 |
* / % | 乘法、除法、取模 |
+ - + | 加法、减法、字符串连接 |
<< >> >>> | 移位 |
< <= > >= instanceof | 小于、小于等于、大于、大于等于、instanceof |
== != === !== | 等于、不等于、严格相等、非严格相等 |
& | 按位与 |
^ | 按位异或 |
| | 按位或 |
&& | 逻辑与 |
|| | 逻辑或 |
?: | 条件 |
= oP= | 赋值、运算赋值 |
, | 多重求值 |
二、流程控制
1.条件语句
// if(exp){执行代码段};
var a = 123; // var a;
if(typeof a === "number"){
console.log("true");
}
// if(exp){exp为true的代码段}else{exp为false的代码段};
var a = 123; // var a;
if(typeof a === "number"){
console.log("true");
}else {
console.log("false");
}
// if ... else if ...
var readline = require("readline");
var rl = readline.createInterface({
input: process.stdin,
out: process.stdout,
});
rl.question("请输入成绩: ", (number) => {
if(number > 100){
console.log("输入的成绩不能大于100!");
}else if(number > 80){
console.log("录入成功,A档");
}else if(number > 60){
console.log("录入成功,B档");
}else if(number > 0){
console.log("录入成功,C档");
}else{
console.log("输入的成绩不能小于0!")
}
rl.close();
});
rl.on("close", function () {
console.log("退出程序!");
process.exit();
});
var readline = require("readline"); var rl = readline.createInterface({ input: process.stdin, out: process.stdout, }); rl.question("请输入星期几: ", (day) => { switch(day){ case "1": console.log("今天吃炒面"); break; case "2": console.log("今天吃鱼香肉丝"); break; case "3": console.log("今天吃麻辣香锅盖饭"); break; case "4": console.log("今天吃豆丁胡萝卜"); break; case "5": console.log("今天公司聚餐"); break; case "6": console.log("今天吃泡面"); break; case "7": console.log("今天撸串"); break; default: console.log("input error.") } rl.close(); }); rl.on("close", function () { process.exit(); }
// switch ... case ...
var readline = require("readline");
var rl = readline.createInterface({
input: process.stdin,
out: process.stdout,
});
rl.question("请输入星期几: ", (day) => {
switch(day){
case "1":
console.log("今天吃炒面");
break;
case "2":
console.log("今天吃鱼香肉丝");
break;
case "3":
console.log("今天吃麻辣香锅盖饭");
break;
case "4":
console.log("今天吃豆丁胡萝卜");
break;
case "5":
console.log("今天公司聚餐");
break;
case "6":
console.log("今天吃泡面");
break;
case "7":
console.log("今天撸串");
break;
default:
console.log("input error.")
}
rl.close();
});
rl.on("close", function () {
process.exit();
});
2.循环语句
for 循环
和java中的for循环一致:for(exp1; exp2; exp3){代码块;}
- exp1: 无条件的执行第一个表达式
- exp2: 是判断是否能执行循环体的条件
- exp3: 增量操作
// 九九乘法表
var str = "";
for(var i=1;i<=9;i++){
for(var j=1;j<=i;j++){
str += i + "*" + j + "=" + i*j + " ";
if(i === j){
str += "
";
}
}
}
console.log(str);
while 循环
var arr = function (number) { var arr_list = new Array(); // var arr_list = []; var i = 0; while (i < number){ arr_list[i] = Math.random(); i++; } return arr_list; }; console.log(arr(5))
var arr = function (number) {
var arr_list = new Array(); // var arr_list = [];
var i = 0;
while (i < number){
arr_list[i] = Math.random();
i++;
}
return arr_list;
};
console.log(arr(5));
do ... while循环
// 和java中的do ... while 循环一致,先执行依次do内的代码块,再执行while 判断。不管while条件判断是否成功,do都会至少执行一次。
var arr = function (number) {
var arr_list = new Array(); // var arr_list = [];
var i = 0;
do {
arr_list[i] = Math.random();
i++;
}while (i > number);
return arr_list;
};
console.log(arr(5));
三、函数
1.定义
函数通过function关键字定义。function 函数名称([参数1, ...]){代码块; [return 返回值]};也可以通过Function构造器定义函数。
// 通过function 关键字定义函数
function hello() {
console.log("hello world.")
};
hello();
匿名函数,即function关键字定义的无函数名称的函数。
// 通过function 关键字定义匿名函数
var hello = function () {
console.log("hello world.")
};
hello();
将匿名函数作为参数,传递给另一个函数进行执行,此时其被称为回调函数。回调函数在js中异常强大。
function calc(v1, v2, callback) {
v1 = v1 || 1;
v2 = v2 || 2;
return callback(v1, v2);
}
calc(3,4, function (v1, v2) {
return v1 + v2
});
2.参数
函数如果定义了参数,在调用函数的时候没有传值则默认值为undefined。如果传递的参数超过了形参,js会忽略掉多于的参数值。
function calc(v1, v2){
return v1 + v2;
}
console.log(calc(5, 6, 7));
es5不能直接写形参的默认值,但可以通过arguments对象来实现默认值。同时,arguments对象可以实现可变参数。
function calc(v1, v2){
v1 = v1 || 1;
v2 = v2 || 2;
return v1 + v2;
}
console.log(calc());
3.函数调用
function calc(v1, v2, callback) {
v1 = v1 || 1;
v2 = v2 || 2;
return callback(v1, v2);
}
value1 = calc(3,4, function (v1, v2) {
return v1 + v2
});
value2 = calc.call(calc, 3, 4, function (v1, v2) {
return v1 + v2;
});
value3 = calc.apply(calc, [3, 4, function (v1, v2) {
return v1 + v2;
}]);
value4 = (function (v1, v2) {
return v1 + v2;
})(3, 4);
console.group("函数调用的方式");
console.log("- 直接调用: " + value1);
console.log("- 间接call调用: " + value2);
console.log("- 间接apply调用: " + value3);
console.log("- 自调用: " + value4);
四、对象
js中对象分为:内建对象、宿主对象和自定义对象。
1.对象创建
直接通过var关键字定义Object对象。
var obj1 = new Object();
var obj2 = {};
// 使用字面量来创建一个对象: var obj = {} 和new本质上是一模一样的
// 使用字面量创建对象时,可以在创建对象时直接指定对象中的属性
// 字面量里面的属性可以加引号,也可以不加引号,建议不加
// 如果使用特殊字符或名字则必须加引号
var obj = {
name: "孙悟空",
gender: "男猴",
age: 1500,
credit: {
name1: "孙行者",
name2: "齐天大圣"
}
};
console.log(obj);
也可以通过工厂方法创建对象。
function Person(name, age) {
var obj = {};
obj.name = name;
obj.age = age;
obj.sayHello = function () {
console.log(obj.name + ", " + obj.age + "years old.");
};
return obj;
}
sun = Person("孙悟空", 2000);
sun.sayHello();
2.对象属性操作和方法创建
var obj = new Object(); // new 构造对象, 可使用typeof obj 查看对象
// 在对象中保存的值称为属性
// 向对象中添加属性:对象.属性名 = 属性值
obj.name = "孙悟空";
obj.gender = "男猴";
obj.age = 18;
console.log(obj);
// 读取对象中的属性: 对象.属性名
// 如果读取的对象中没有该属性,会返回undefined
console.log(obj.name);
// 修改对象的属性值: 对象.属性名 = 新值
obj.age = 24;
console.log(obj);
// 删除对象属性
delete obj.age;
console.log(obj);
// 自定义
obj.fuc = function add(x, y) {
return x+y;
};
console.log(obj);
3.作用域
在js中一共有两种作用域,全局作用域和函数作用域
全局作用域:
- 直接编写在script标签内的JS代码
- 在页面打开时创建,在页面关闭时销毁
- 在全局作用域中有一个全局对象window,可以直接使用.它代表的是一个浏览器的窗口,它由浏览器创建
- 在全局作用域中创建的变量都会作为window对象的属性保存
- 在全局作用域中创建的函数都会作为window对象的方法保存
- 全局作用中的变量和函数在页面的任意位置都可以被访问/执行
函数作用域:
- 调用函数时创建函数作用域,函数执行完毕之后,函数作用域销毁
- 每调用一次函数就会创建一个新的函数作用域,它们之间是相互独立的
- 全局作用域中访问不到函数作用域,函数作用域中可以访问全局作用域
- 函数作用域会优先查找本作用域中的变量,如果没有就找上一级中的变量
- 函数作用域的a并不影响上一级作用域中的a
- 如果不使用var声明,直接写a = 20,就是在操作全局作用域中的a变量
- 如果使用全局作用域中的a变量[在本作用域中已声明a变量],可以用window.a,这在b1中已强调过
全局作用域
console.log(window); // window是个对象
// 使用var关键字声明的变量,会在所有的代码执行之前执行;如果声明变量时不使用var,则变量不会被提前声明
console.log(a); // 此时的var a已被声明,它的值是undefined,到下一行时它的值才会是123
var a = 123; // 它相当于window.a = 123
console.log(window.a);
console.log(abc()); // 可以提前声明和执行
console.log(abc2()); // var提前声明的是一个undefined的window属性,不是个函数: abc2 is not a function
// 使用function关键字声明的函数,和var的机制一样,是函数声明的提前,它会提前(优先)执行
function abc() { // 它相当于window.abc = function (){console.log("abc")}
console.log("abc");
}
console.log(window.abc());
// 使用函数表达式不会被声明提前
var abc2 = function() {
console.log("abc2");
}
局部作用域
var a = 19;
var b = 30;
function fun() {
a = 20;
var b = 1000;
console.log("a = " + a); // 这里打印的是20
console.log("b = " + b); // 这里打印的是1000
console.log("window.b = " + window.b); // 这里打印的是30
}
fun();
console.log(a); // 这里打印的是20
// 定义形参相当于在函数作用域中声明了变量
五、原型
1.this关键字
解析器在调用函数时,会向函数内部传递进一个隐含的参数这个隐含的参数就是this,this指向的是一个对象,这个对象称为函数执行的上下文对象
根据函数的调用方式不同,this会指向不同的对象
- 1.以函数的形式调用时,this永远都是window
- 2.以方法的形式调用时,this就是调用方法的那个对象
var name = "全局名字";
function func(a, b) {
console.log("a = " + a + ", b = " + b);
console.log(" Object: " + this);
console.log(this);
}
func(123, 456); // this指的是window
function func2() {
console.log(this.name);
console.log(this);
}
var obj = {
name: "孙悟空",
sayName: func, // 把函数赋给属性,this指的是obj
sayName2: func2
};
var obj2 = {
name: "猪八戒",
sayName: func, // this指的是obj2
sayName2: func2
};
obj2.sayName(2332, 4523);
obj.sayName(234, 789);
obj.sayName2(); // this可以支持对象内部的函数去访问对象内部的属性
obj2.sayName2(); // 这一点特别像python类中的self
var obj3 = {
name: "沙和尚",
teacher: "唐僧",
address: "流沙河",
sayMyTeacher: function3,
sayMySelf: function2,
say: function1
};
function function1() {
var say = this.sayMySelf() + this.sayMyTeacher();
console.log(say);
}
function function2() {
var say = "大家好,我是" + this.name + ",我老家是" + this.address;
return say
}
function function3() {
var say = "。我的师父是" + this.teacher + "老师, 他是个得道高僧";
return say
}
obj3.say();
2.构造方法的重写
function Person1(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
// this.sayName = function() {
// console.log("大家好,我系" + this.name); // 这样写每创建一个对象,就会创建一个sayName
// }
this.sayName = sayName; // 共用同一个方法,它就相当于python中的类方法[类方法只有一份,但每个实例对象都共用]
} // 注意这里的写法,一个隐含的this传递给了sayName函数
var per1 = new Person1("孙悟空", 18, "男");
per1.sayName();
// 在全局作用域中写sayName
// 但是它污染了全局作用域的命名空间,也就是全局作用域不能再写sayName函数
// 另外这个函数也很不安全,由此引出了"原型"的概念
function sayName() {
console.log("大家好,我系" + this.name);
}
3.原型
1.原型prototype
我们所创建的每一个函数,解析器都会向函数中添加一个属性:prototype
如果函数作为普通函数调用prototype,没有任何作用
当该函数以构造函数的形式调用[per1]时,它[per1]会有一个隐含的属性__proto__指向其原型对象[Person]
每次调用时都会有各自的__proto__指向原型对象的prototype,也就是原型对象中的属性和方法被调用函数"共享"
function Person() {}
console.log(Person.prototype);
var per1 = new Person();
console.log(per1.__proto__ == Person.prototype); // true
Person.prototype.a = "我是原型对象中的123456";
per1.a = "我是mc中的" +"123456";
var per2 = new Person();
console.log(per1.a); // 找到调用函数的属性和方法,直接执行
console.log(per2.a); // 如果调用函数没有该属性或方法,会从原型对象中寻找
Person.prototype.sayName = function() {
console.log('我是原型对象的sayName');
};
per2.sayName = function() {
console.log('我是per2中的sayName');
};
per1.sayName(); // 和前面的类似
per2.sayName(); // 和前面的类似
// 解决全局作用域污染问题: 把对象的函数写在prototype里而不是全局作用域里
function MyPerson(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
MyPerson.prototype.sayName = function() {
console.log("大家好,我系" + this.name);
};
var mp1 = new MyPerson("孙悟空", 2000, "男猴");
mp1.sayName();
2.原型模拟类和对象
在python中声明一个类和对象:
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def sayHello(self):
print("{}, {} years old.".format(self.name, self.age))
sun = Person("孙悟空", 2000)
sun.sayHello() // 对象可以调用方法,因为方法只有一份且存在类内存中,每个对象只保留了引用
Person.sayHello(sun) // 类可以传入一个对象来调用方法,因为方法存在类内存中
在Java中创建一个类和对象:类的方法存在方法区,对象存在堆内存中,多个对象共用它们父类的方法。它需要设置静态方法来实现对象调用。
public class Person {
private String name;
private int age;
Person(String name, int age){
this.name = name;
this.age = age;
}
public void sayHello(){
System.out.println(this.name + ", " + this.age + "years old.");
}
public static void sayHello(Person obj){
System.out.println(obj.name + ", " + obj.age + "years old.");
}
public static void main(String[] args){
Person sun = new Person("孙悟空", 2000);
Person.sayHello(sun);
sun.sayHello();
}
}
在js中,对象保存在堆内存中,每创建一个新的对象都会开一个堆内存空间,并把其属性和方法都保存在堆内存中。注意,如果不用原型,每个对象都会将其方法复制一份到自己的堆内存空间中。js中实现类调用实例对象:
function MyPerson(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
MyPerson.sayName = MyPerson.prototype.sayName = function (obj) { // 在自己的prototype空间里写函数
obj = obj || this;
console.log("大家好,我系" + obj.name + ", 一只火眼金睛的" + obj.gender); // 让每个实例都能访问
}
}
var mp1 = new MyPerson("孙悟空", 2000, "男猴"); // 注意这里使用的是new MyPerson,构造
mp1.sayName();
MyPerson.sayName(mp1);