前情回顾:基本
存储类型
数组
Js的数组与其它语言的数组有很大的区别。跟其他语言中的数组一样,ECMAScript 数组也是一组有序的数据。但是,Js的一个数组中可以存储不同类型的数据。
// 数组字面量表示法
let array = [1, '2', true];
// 构造函数创建数组 new可以省略
let colors = new Array();
let colors1 = new Array("blue", "red", 'green');
// 创建数组传的是一个数值,则会创建一个长度为指定数值的数组
let list = new Array(10);
// 数组求和
let nums = [2, 1, 8, 4];
// 数组的最大和最小值
let arr = [2, 6, 1, 7, 4];
// 数组常用的一些方法
// Array.isArray()方法,检测一个值是不是数组
console.log(Array.isArray(array));
console.log(typeof array);
// push()和pop()方法
// push 是想数组尾部添加元素,pop是删除数组尾部的元素
// push 可以一次追加多个元素也可以追加单个元素并返回新数组的长度
array.push({color: "red"}, [1, 22]);
console.log(array);
// prop 返回的是移除的数组中元素
console.log(array.pop());
// shift()和unshift()
// unshift() 方法将新项添加到数组的开头,并返回新的长度。
// shift() 方法移除数组的第一项,返回值是被移除的元素。
练习
// 晒出数组[1, 20, 14, 12, 9, 0, 8, 28]中大于10的所有元素
数组方法
// splice()
// array.splice(index, howmany, item1, ....., itemX)
// index 必填项,整数。指定在什么位置添加/删除项目,使用负值指定从数组末尾开始的位置。
// howmany 可选。要删除的项目数。如果设置为 0,则不会删除任何项目。
// item1, ..., itemX 可选。要添加到数组中的新项目。
// 返回值是数组,数组里是删除的元素
let fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.splice(0, 1, "Lemon");
// join()
// 方法将数组作为字符串返回。元素将由指定的分隔符分隔。默认分隔符是逗号 (,)。
// 参数可选。要使用的分隔符。如果省略,元素用逗号分隔。
fruits.join(' - ');
fruits.join("");
// concat()
// 方法用于连接两个或多个数组,方法不会更改现有数组,而是返回一个新数组,其中包含已连接数组的值。
let sedan = ["S60", "S90"];
let SUV = ["XC40", "XC60", "XC90"];
let Volvo = sedan.concat(SUV);
console.log(Volvo.concat("1", "2"));
// indexOf()
// 在数组中搜索指定项目,并返回其位置。 可以指定搜索起始位置。
// 如果未找到该项目,则 indexOf() 返回 -1。
// 如果该项目出现多次,则 indexOf() 方法返回第一次出现的位置。
Volvo.indexOf("XC60", 2);
字符串
let carName1 = “Volvo XC60”;
let carName2 = ‘Volvo XC60’;
如果在输入多行字符串的时候一定在每行的结尾处加‘\’
let string = '如果字符串太长需要换行,\
这里才能换行\ 再来一行’;
字符串方法
// 获取字符串的长度。
console.log(string.length);
// 转义字符串与Java中一样
let carName3 = new String('Volvo XC60');
console.log(carName3 === carName1);
// 原始值,比如“Bill Gates”,无法拥有属性和方法(因为它们不是对象)。
// 但是通过 JavaScript,方法和属性也可用于原始值,因为在执行方法和属性时 JavaScript 将原始值视为对象。
// charAt() 方法返回字符串中指定索引(下标)处的字符。
// 默认索引为 0
// 超出范围的索引返回空字符串
// 无效索引转换为 0
console.log(carName1.charAt(0));
console.log("Volvo XC60".charAt(2));
// concat() 用于将一个或多个字符串拼接成一个新字符串。
// 方法不会更改现有字符串。
// 方法返回新字符串。
let text1 = '你好';
let text2 = '中国';
text1.concat(text2);
text1.concat(' ', text2);
// 获取字符串中的子串
// string.slice(start, end)
// 方法不会改变原字符串。
// 第一个参数 起始下标。如果是负数,则该参数指定从字符串尾部开始算起的位置。
// 第二个参数 终止下标。如果没有指定这一参数,那么要抽取的子串包括起始位置到原字符串结尾的字符串。如果该参数是负数,则它指定从字符串尾部开始算起的位置。
// console.log(carName1.slice(1));
console.log(carName1.slice(-3, -1));
// substr()
// substr(start, length)
// 方法从指定位置开始,并返回指定数量的字符。
// length省略就,则提取字符串的其余部分。
// 如果 start 大于长度,则 substr() 返回 ""。
// 如果 length 为 0 或负数,则返回空字符串。
console.log(carName1.substr(1, 3));
// substring() 方法从字符串中提取两个索引(位置)之间的字符,并返回子字符串。
// string.substring(start, end)
// end位置不包括
// 如果 start 大于 end,则交换参数
// 小于 0 的开始或结束值被视为 0。
// 如果参数 start 与 end 相等,那么该方法返回的就是一个空串(即长度为 0 的字符串)。
// 请记住,该子串包括 start 处的字符,不包括 end 处的字符,返回的子串长度始终等于 end-start。
console.log(carName1.substring(1, 3));
// split()
// string.split(separator, limit) separator:可选。用于拆分的字符串或正则表达式。如果省略,则返回包含原始字符串的数组。limit:可选。限制拆分数量的整数。超出限制的项目被排除在外。
// 方法返回新数组,不会更改原始字符串。
// 方法将字符串拆分为子字符串数组。拆分的数组不包含指定拆分的字符。
let text = "How are you doing today?";
console.log(text.split(' ', 3));
// indexOf() 方法返回值在字符串中第一次出现的位置
// 如果未找到该值,则 indexOf() 方法返回 -1。
// indexOf() 方法区分大小写。
text.indexOf("e", 5);
// trim() 方法从字符串的两侧删除空格。
// trim() 方法不会更改原始字符串。
let str = " Hello World! ";
let result = text.trim();
函数
问:什么是函数?
答:可以是代码复用 有参数 返回值
- 函数是 ECMAScript 中最有意思的部分之一,这主要是因为函数实际上是对象。
- 每个函数都是 Function 类型的实例,而 Function 也有属性和方法,跟其他引用类型一样。
- 因为函数是对象,所以函数名就是指向函数对象的指针,而且不一定与函数本身紧密绑定。
例子:
function sum(num1, num2) {
return num1 + num2;
}
let sum = function (num1, num2) {
return num1 + num2;
};
let sum = (num1, num2) => {
return num1 + num2;
};
基础使用:
// 这个构造函数接收任意多个字符串参数,最后一个参数始终会被当成函数体,而之前的参数都是新函数的参数。
let sum = new Function("num1", "num2", "return num1 + num2"); //不推荐
// 箭头函数 ES6新语法
let arrowSum = (sum1, sum2) => {
return sum1 + sum2;
}
// 箭头函数语法简洁
let ints = [1, 2, 3];
console.log(ints.map(function (i) { return i + 1; }));
console.log(ints.map((i) => { return i + 1; }));
// 如果只有一个参数,可以不用括号
let double = x => { return 2 * x;}
// 如果没有参数括号不可以省略
let getRandom = () => { return Math.random(); };
// 如果函数体的内容就一句话那么可以省略大括号
let triple = (x) => 3 * x;
// 可以赋值
let value = {};
let setName = (x) => x.name = "Matt";
setName(value);
console.log(value.name);
// 函数的参数
let result = sum(1, 2);
console.log(sum());
// 参数默认值
function getSum(x = 0, y = 0) {
return x + y;
}
console.log(getSum());
// 主要是因为 ECMAScript 函数的参数在内部表现为一个数组。
// 事实上,在使用 function 关键字定义(非箭头)函数时,可以在函数内部访问 arguments 对象,从中取得传进来的每个参数值。
function howManyArgs() {
console.log(arguments.length);
}
howManyArgs("string", 45); //
howManyArgs(); //
howManyArgs(12); //
// 箭头函数的参数
// 如果函数是使用箭头语法定义的,那么传给函数的参数将不能使用 arguments 关键字访问,而只能通过定义的命名参数访问。
let nav = () => {
console.log(arguments[0]);
};
nav(5);
// 虽然箭头函数中没有 arguments 对象,但可以在包装函数中把它提供给箭头函数:
function foo() {
let bar = () => {
console.log(arguments[0]); // 5
};
bar();
}
foo(5);
// 作用域
// Js有一个作用域叫全局作用域--作用范围在整个script标签内部或者一个独立的js文件
// 局部作用域在函数内部或者快内部
{
var a = 10;
let b = 10;
}
console.log(a);
console.log(b);
var color = "blue";
function changeColor() {
let anotherColor = "red";
function swapColors() {
let tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 这里可以访问color、anotherColor和tempColor
}
// 这里可以访问color和anotherColor,但访问不到tempColor
swapColors();
}
// 这里只能访问color
changeColor();
// 未声明的局部变量
function add(num1, num2) {
sum = num1 + num2
}
add(10, 20)
console.log(sum);
// 函数名
// 函数名是指向函数的指针。一个函数可以有多个名称。
function sum(num1,num2) {
return num1 + num2;
}
console.log(sum(10, 10));
let anotherSum = sum; // 注意:这里是不会执行sum函数
sum = null;
sum(10, 10);
console.log(anotherSum(10, 10)); //
// 匿名函数
// 匿名函数立即执行--避免全局变量之间的污染
(function () {
let num = 10;
})();
(function () {
let num = 20;
})();
// 匿名函数立即执行的两种写法
// 匿名函数立即执行后面必须加分号不可以省略
// 第一种
(function () {})();
// 第二种
(function () {}());
// ECMAScript 6 的所有函数对象都会暴露一个只读的 name 属性,其中包含关于函数的信息。多数情况下,这个属性中保存的就是一个函数标识符,或者说是一个字符串化的变量名。
// 即使函数没有名称,也会如实显示成空字符串。如果它是使用 Function 构造函数创建的,则会标识成"anonymous"
function foo() {}
let bar = function() {}
let baz = () => {};
console.log(foo.name);
console.log(bar.name);
console.log(baz.name);
console.log((() => {}).name);
console.log((new Function()).name);
// 函数声明与函数表达式
// JavaScript 引擎在任何代码执行之前,会先读取函数声明,并在执行上下文中 生成函数定义。而函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。
console.log(sum(10, 10));
function sum(num1, num2) {
return num1 + num2;
}
console.log(sum(10, 10));
let sum = function(num1, num2) {
return num1 + num2;
};
// 函数内部
// 函数内部有两个特殊的对象:arguments和this。
// arguments 对象有一个 callee 属性,是一个指向 arguments 对象所在函数的指针。
function factorial(num) {
if (num < 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
function factorial(num) {
if (num < 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
let trueFactorial = factorial;
factorial = function () {
return 0;
};
console.log(trueFactorial(5));
console.log(factorial(5));
// this
// 它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。
// 函数的不同使用场景,this的值就不同。
// 纯粹的函数调用,this代表全局对象(在网页中this指向windows)。
var x = 1;
function test() {
console.log(this.x);
}
test();
// 作为对象方法的调用, this就指向上级对象
let color = "red";
let o = {
color: "blue"
}
function sayColor() {
console.log(this.color);
}
sayColor();
o.sayColor = sayColor;
o.sayColor();
// 箭头函数中使用,this引用的是定义箭头函数的上下文。
// 箭头函数里面的 this 是上下文( context ), 外部作用域的 this 就是箭头函数内的 this。
// 技巧:它的外层没有函数,this 是 window;外层有函数,看外层函数的 this 是谁,它的 this 就是谁。
function King() {
this.royaltyName = 'Henry';
// this 引用 King 的实例
setTimeout(() => console.log(this.royaltyName), 1000);
}
function Queen() {
this.royaltyName = 'Elizabeth';
setTimeout(function() { console.log(this.royaltyName); }, 1000);
}
// 作为构造函数调用,this就指向这个新对象。
var x = 2;
function test() {
this.x = 1;
}
var obj = new test();
// 粗略的总结,谁调用,this就是谁。 箭头函数中,箭头函数在哪儿定义,this就是定义函数的对象。
具体实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function add(num1, num2) {
return num1 + num2;
}
window.add();
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
person1.sayName();
let person2 = new Person();
person2.sayName();
console.log(person1.sayName === person2.sayName);
</script>
</body>
</html>
对象
// 对象
let preson = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function() {
console.log(this.name);
}
};
属性
// 属性的类型
// ECMA-262 使用一些内部特性来描述属性的特征。这些特性是由为 JavaScript 实现引擎的规范定义的。因此,开发者不能在 JavaScript 中直接访问这些特性。
// 为了将某个特性标识为内部特性,规范会用 两个中括号把特性的名称括起来,比如[[Enumerable]]。
// 属性分两种:数据属性和访问器属性。
// 数据属性
// 数据属性包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置。数据属性有 4 个特性描述它们的行为。
// [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是 true
// [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是 true
// [[Writable]]:表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的这个特性都是 true
// [[Value]]:包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性的默认值为 undefined。
// 注意:一个属性被定义为不可配置之后,就不能再变回可配置的了。
// 要修改属性的默认特性,就必须使用 Object.defineProperty()方法。
let person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
console.log(person.name); // "Nicholas"
person.name = "Greg";
console.log(person.name); // "Nicholas"
访问器属性
// 访问器属性
// 访问器属性不包含数据值。相反,它们包含一个获取(getter)函数和一个设置(setter)函数,不过这两个函数不是必需的。访问器属性有4个特性描述它们的行为。
// [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是 true
// [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是 true
// [[Get]]:获取函数,在读取属性时调用。默认值为 undefined。
// [[Set]]:设置函数,在写入属性时调用。默认值为 undefined。
let book = {
year_: 2017,
edition: 1
}
Object.defineProperty(book, "year", {
get() {
return this.year_;
},
set(newValue) {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
});
book.year = 2018;
console.log(book.edition);
// 读取属性的特性
let book = {};
Object.defineProperties(book, {
year_: {
value: 2017
},
edition: {
value: 1
},
year: {
get: function() {
return this.year_;
},
set: function(newValue) {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
}
});
let descriptor = Object.getOwnPropertyDescriptor(book, "year_");
console.log(descriptor.value);
console.log(descriptor.configurable);
console.log(typeof descriptor.get);
let descriptorNew = Object.getOwnPropertyDescriptor(book, "year");
console.log(descriptorNew.value);
console.log(descriptorNew.enumerable);
console.log(typeof descriptorNew.get);
console.log(Object.getOwnPropertyDescriptors(book));
模式
// 工厂模式
// 解决了创建对象重复代码问题,没解决对象标识问题。(创建的对象是什么类型)
function createPerson(name, age, job) {
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
console.log(this.name);
};
return o;
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");
// 构造函数模式
// 构造函数就是普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写
// 构造函数和普通函数的区别就是调用方式不同--普通函数是直接调用的,而构造函数需要使用new关键字来调用
// 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
};
}
let person3 = new Person("Nicholas", 29, "Software Engineer");
let person4 = new Person("Greg", 27, "Doctor");
person3.sayName();
person4.sayName();
// 构造函数不一定要写成函数声明的形式。赋值给变量的函数表达式也可以表示构造函数
let Person = function(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
};
}
console.log(person3 instanceof Object);
console.log(person3 instanceof Person);
console.log(person4 instanceof Object);
console.log(person4 instanceof Person);
// 做为函数调用
Person("Greg", 27, "Doctor");
globalThis.sayName(); //浏览器中是window
// 构造函数的问题
// 构造函数的主要问题在于,其定义的方法会在每个实例上都创建一遍。
console.log(person3.sayName === person4.sayName);
// 解决重复创建的问题
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
console.log(this.name);
}
// 原型模式
// Js中每个函数都会创建一个prototype属性,这个属性是一个对象,
// 它包含应该由特定引用类型的实例共享的属性和方法。这个对象就是通过调用构造函数创建的对象的原型。
// 使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。
// 原来在构造函数中直接赋给对象实例的值,可以直接赋值给它们的原型。
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
person1.sayName();
let person2 = new Person();
person2.sayName();
console.log(person1.sayName === person2.sayName);
// 使用函数表达式也可以
let Person = function () {};
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
person1.sayName();
let person2 = new Person();
person2.sayName();
继承与类
// 继承
// 原型链
// ECMA-262 把原型链定义为 ECMAScript 的主要继承方式。
function SuperType() {
this.flag = true;
}
SuperType.prototype.getSuperValue = function() {
return this.flag;
};
function SubType() {
this.subFlag = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subFlag;
};
let instance = new SubType();
console.log(instance.getSuperValue());
// 类
// ES6新引入关键字‘class’
// 与函数类型相似,定义类也有两种主要方式:类声明和类表达式。
// 类声明
class Dog{}
// 类表达式
const Anima = class {};
// 类的构成
// 类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类方法,但这些都不是必需的。
// 空的类定义照样有效。默认情况下,类定义中的代码都在严格模式下执行。
// 类构造函数
// constructor 关键字用于在类定义块内部创建类的构造函数。方法名 constructor 会告诉解释器 在使用 new 操作符创建类的新实例时,应该调用这个函数。
class Cat {
constructor() {}
}
// 实例化
// 使用 new 操作符实例化的操作等于使用 new 调用其构造函数。唯一可感知的不同之处就是,JavaScript 解释器知道使用 new 和类意味着应该使用 constructor 函数进行实例化。
class Vegetable {
// 类构造函数
constructor() {
this.color = 'orange';
}
}
let v = new Vegetable();
console.log(v.color);
// 类实例化时传入的参数会用作构造函数的参数。如果不需要参数,则类名后面的括号也是可选的
class Person {
constructor(name) {
console.log(arguments.length);
this.name = name || null;
}
}
let p1 = new Person;
console.log(p1.name);
let p2 = new Person();
console.log(p2.name);
let p3 = new Person('Jake');
console.log(p3.name);
// ECMAScript 中没有正式的类这个类型。从各方面来看,ECMAScript 类就是一种特殊函数。
console.log(typeof Person);
// ES6 类支持单继承。
// extends关键字。
class Vehicle {
identifyPrototype(id) {
console.log(id, this);
}
static identifyClass(id) {
console.log(id, this);
}
}
class Bus extends Vehicle {}
let v = new Vehicle();
let b = new Bus();
b.identifyPrototype('bus');
v.identifyPrototype('vehicle');