前端学习之使用JavaScript(2)

5 篇文章 0 订阅
3 篇文章 0 订阅

前情回顾:基本

存储类型

数组

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');



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值