重学 JavaScript 笔记(二)—— 原型 && 继承

3.1 函数

3.1.1 函数声明:
//语法:
function 函数名(参数1, 参数2,...,参数N) {
    函数体
}
//eg:
function sayHi(name, message) {
    alert("Hello " + name + "," + message);
}
3.1.2 函数调用
//语法
functionName(arg0, arg1,...,argN)//eg:
sayHi("Nicholas", "how are you today?");//弹出 "Hello Nicholas,how are you today?"
3.1.3 变量作用域
  • 函数内声明的变量,只在该函数内部可见。
function sayHi() {
  let name = 'jane';
  let age = 18;
  alert(name + ":" + age);
}
console.log('age:', age);//age is not defined
  • 函数外声明的变量,函数可以访问,也可以修改。
let age = 20;
function sayHi() {
  let name = 'jane';
  age = 18;
  console.log(name + ":" + age);
}
sayHi() //jane:18
3.1.4 函数返回值

任何函数、任何时候都可以通过 return 语句后跟要返回的值来是实现返回值。函数会在执行完 return 语句后停止并立即退出。因此,return 之后的任何代码都永远不会执行。

function sum(num1, num2) {
    return num1 + num2;
}
let result = sum(5, 10);  // result 为15

注:

  • return 可以不带任何返回值。则函数将返回 undefined 值。一般用在需要提前停止函数执行,又不需要返回值的情况下。
  • 推荐:要么让函数始终都有返回值,要么就永远都不要有返回值,否则会给代码调试带来不便。

严格模式下的限制: 发生以下情况,会导致语法错误,代码无法执行。

  • 不能把函数/参数命名为eval 或arguments;
  • 不能出现两个命名参数同名的情况。
3.1.5 参数

参数不是必需的,可以不传,个数及类型,即使个数与定义的个数不同,也不会报错。因为参数在内部使用一个数组来表示的。
函数体内部可以通过 arguments 对象来访问这个参数数组,从而获取传递过来的每一个参数。

function sayHi(name, message) {
    alert("Hello " + name + "," + message);
}
sayHi('jane', 'nice to meet you!')//Hello jane,nice to meet you!
//重写
function sayHi() {
    alert("Hello " + arguments[0] + "," + arguments[1]);
}
sayHi('jane', 'nice to meet you!')//Hello jane,nice to meet you!
  • arguments 对象的长度由传入函数的参数决定;
  • 没有传递值的命名参数自动被赋予 undefined 值,类似于定义了变量但未初始化。

严格模式下:

重写arguments 的值会导致语法错误(代码将不会执行)

3.1.6 ES6 函数新特性

1. 参数默认值

声明函数时直接为参数设置默认值,若调用函数时未赋值,则参数的值为默认值。

function sayHi(name, message='nice to meet you!') {
    console.log("Hello " + name + "," + message);
}
sayHi('jane', 'yeah!')  //Hello jane,yeah!
sayHi('tom')  //Hello tom,nice to meet you!

若默认值为表达式,则执行时才计算其值

let num = 10;
function sayHi(name, age=num+8) {
    console.log(name + ", age:" + age);
    num+=10;
}
sayHi('jane')  //jane, age:18
sayHi('tom')  //tom, age:28
sayHi('lucy', 12)   //lucy, age:12

2. rest 参数
…变量名,用于获取函数的剩余参数,将其放在数组中。
rest 参数必须是最后一个参数,否则会报错。

function sum(...nums){
  let sum = 0;
  nums.forEach(item => {
    sum += item;
  })
  return sum
}
let result1 = sum()   //0
let result2 = sum(1,2,3,4)   //10

3. 扩展运算符
…变量名,用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。

//对象
let obj1 = {name: 'jane', age: 18};
let obj2 = {color: 'yellow', ...obj1} //obj2: {color: "yellow", name: "jane", age: 18}
//等价于
let obj3 = Object.assign({color: 'yellow'}, obj1)   //obj3: {color: "yellow", name: "jane", age: 18}

//数组
let arr1 = [1,2,3]
let arr2 = [4,5,6]
let arr3 = [7,8,9]
let arr4 = [...arr1, ...arr2, ...arr3]  //[1, 2, 3, 4, 5, 6, 7, 8, 9]
//等价于
let arr5 = arr1.concat(arr2, arr3)  //[1, 2, 3, 4, 5, 6, 7, 8, 9]

4. 箭头函数

//箭头函数定义
let sum = (a,b) => {
  return a+b
}
//等同于
let sum = function(a,b) {
  return a+b
}
  • 箭头函数的 this 指向的是定义时的 this 对象,而不是执行时的 this 对象
  • 箭头函数是匿名函数 ,不能作为构造函数
  • 箭头函数不可以使用 arguments,可使用 rest 参数

5. name
ES6 的 name 会返回函数名。

function sum(...nums){
  let sum = 0;
  nums.forEach(item => {
    sum += item;
  })
  return sum
}
console.log(sum.name);    //"sum"

3.2 构造函数

1. 构造函数: 一种特殊的函数。

  • 主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new操作符一起使用在创建对象的语句中。
  • 我们可以使用构造函数来创建多个类似的对象,而免于代码重复。
  • JavaScript 内置了一些构造函数

2. 构造函数特点

  • 首字母大写(通常约定)
  • 只能用 new 操作符来执行
  • 内部使用的 this 对象,会指向即将生成的实例对象

3. 实例化一个对象的过程

new 一个新对象 => this 指向这个新对象 => 执行代码(对 this 赋值)=> 返回 this

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.displayInfo = function() {
        console.log(`name: ${name}, age: ${age}`)
    }
}
let jane = new Person('jane', 12)
jane.displayInfo()  //name: jane, age: 12
let tom = new Person('tom', 18)
tom.displayInfo()   //name: tom, age: 18

JavaScript 内置了很多构造函数,如:
Object、Function、Array、RegExp、Date、Number、String、Boolean。

Object2.2 引用类型 已经详细介绍过了。
在 JavaScript 中,Function、Array、RegExp、Date、Number、String、Boolean 是构造函数,也有自己的构造函数,他们的构造函数就是 Object,即 Object 是其他构造函数的构造函数。

后面还会介绍一个 Math 对象,Math 不是对象的类,没有构造函数,但作为 JavaScript 内置的对象,可直接调用来进行数学方法的处理。

3.2.1 Function

3.2.1.1 函数声明:
//1. 构造函数创建 new Function
let 函数名 = new Function(参数1, 参数2,...,参数N, 函数体)
//eg:
let sayHi = new Function("name", "message", "alert('Hello " + name + "," + message)')

sayHi3('jane','hi')//Hello jane,hi

但实际的代码中不使用这种方法来创建函数,还是会用字面量的方法去创建,关于函数的详细介绍,见 3.1 函数

3.2.2 Array

3.2.2.1 创建数组

创建数组:

//使用new Array()
let arr1 = new Array(); //[]
let arr2 = new Array(5); //[empty × 5]
let arr3 = new Array('beijing','tianjin',12);//["beijing", "tianjin", 12]

//使用方括号 —— 常用
let arrs1 = [];//[]
let arrs2 = ['beijing','tianjin',12];//["beijing", "tianjin", 12]

数组内的元素可以是任何类型:

let arr = ['beijing', {name: 'jane'}, 12, true, function(){console.log('fn')}];
//0: "beijing"
//1: {name: "jane"}
//2: 12
//3: true
//4: ƒ ()

数组编号从 0 开始:

let arr = ['beijing','tianjin',12];
//arr[0]: 'beijing'
//arr[1]: 'tianjin'
//arr[2]: 12

通过 数组名[下标] 来获取或设置值:

let arr = ['beijing','tianjin',12];
console.log(arr[0]);// 'beijing'
arr[3] = true;
console.log(arr); //["beijing", "tianjin", 12, true]
3.2.2.2 常用方法

判断是否为数组:Array.isArray

//Array.isArray —— 是则返回 true,否责返回 false
let a = [1,2,3];
let b = 'str';
let b = [];
console.log(Array.isArray(a), Array.isArray(b), Array.isArray(c));  //  true false true

//unshift —— 在数组最前面添加元素,可添加多个
arr.unshift('a', 'b', 'c')
console.log(arr);//arr: ["a", "b", "c", 1, 2, 3, 4, 5, 6]

添加数组元素:push、unshift

//push —— 在数组最后添加元素,可添加多个
let arr = [1,2,3];
arr.push(4,5,6);
console.log(arr);//arr: [1,2,3,4,5,6]

//unshift —— 在数组最前面添加元素,可添加多个
arr.unshift('a', 'b', 'c')
console.log(arr);//arr: ["a", "b", "c", 1, 2, 3, 4, 5, 6]

删除数组元素:pop、shift

//pop() —— 删除并返回数组的最后一个元素
console.log(arr.pop())//6
console.log(arr);//["a", "b", "c", 1, 2, 3, 4, 5]

//shift() —— 删除并返回数组的第一个元素
console.log(arr.shift()) //a
console.log(arr); //["b", "c", 1, 2, 3, 4, 5]

替换数组中的元素:splice

//splice(start, num, newitem) —— 把 arr[start] 开始的 num 个,替换成后面的元素,不改变原数组
console.log(arr)    //[5, 4, 3, 2, 1, "c", "b"]
console.log(arr.splice(2,3,'jane')) //[3, 2, 1]
console.log(arr)    //[5, 4, "jane", "c", "b"]
console.log(arr.splice(1,1,'aa','bb', 'cc')) //[4]
console.log(arr)    //[5, "aa", "bb", "cc", "jane", "c", "b"]

替换数组中某些元素:fill

//fill(value, start, end)
let arr = [1, 2, 3, 4, 5, 5, 6, "b", "c"]
let result1 = arr.fill('hello'); 
// result1: ["hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"]
let result2 = arr.fill('hello', 2, 5); 
//result2: [1, 2, "hello", "hello", "hello", 5, 6, "b", "c"]
let result3 = arr.fill('hello', 2, );
//result3: [1, 2, "hello", "hello", "hello", "hello", "hello", "hello", "hello"]

取数组中的元素:slice

//slice(start, end) —— 取 [arr[start], ...,  arr[end-1]],不改变原数组
console.log(arr)    //[5, 4, 3, 2, 1, "c", "b"]
console.log(arr.slice(2,5)) //[3, 2, 1]
console.log(arr)    //[5, 4, 3, 2, 1, "c", "b"]

颠倒数组中元素的顺序:reverse

//reverse —— 颠倒数组中元素的顺序
console.log(arr)    //["b", "c", 1, 2, 3, 4, 5]
console.log(arr.reverse())   //[5, 4, 3, 2, 1, "c", "b"]
console.log(arr)    //[5, 4, 3, 2, 1, "c", "b"]

数组排序:sort

//sort —— 将数组中的元素进行排序
let arrN = [1,2,4,6,12,11]
arrN.sort() //[1, 11, 12, 2, 4, 6]
arrN.sort((a, b)=>{return a-b}) //[1, 2, 4, 6, 11, 12] 从小到大排序
arrN.sort((a, b)=>{return b-a}) //[12, 11, 6, 4, 2, 1] 从大到小排序

数组转换为字符串:toString、join

//1. toString —— 将数组转换为字符串
arr.toString()  //"5,4,3,2,1,c,b

//2. join —— 通过指定的分隔符, 把数组的所有元素连接成一个字符串
arr     //[5, 4, 3, 2, 1, "c", "b"]
arr.join()  //"5,4,3,2,1,c,b"
arr.join('-')   //"5-4-3-2-1-c-b"

连接数组:concat

//concat 连接数组,并返回结果,不改变原数组
let arr1 = ['jane', 'tom']
let arr2 = ['green', 'red']
let arr3 = [true, false]
let arr4 = [1, 2, 3]
arr1.concat(arr2)  //["jane", "tom", "green", "red"]
arr1.concat(arr2, arr3, arr4) //["jane", "tom", "green", "red", true, false, 1, 2, 3]

查询数组: indexOf、lastIndexOf、find、findIndex、filter

let arr1 = [5, 4, 3, 2, 1, 5, 6, "c", "b"];
//indexOf(item) —— 从前面开始查询,返回查到的第一个 item 的下标值
arr1.indexOf(5)   //0

//lastIndexOf(item) —— 从后面开始查询,返回查到的第一个 item 的下标值
arr1.lastIndexOf(5)   //5

//includes(item) —— 判断数组中是否存在 item,存在则返回 true,不存在则返回 false
arr1.includes(0)     //false
arr1.includes(1)     //true

//find —— 遍历数组,返回使函数返回 true 的第一个元素
let arr2 = [
    { name: 'jane', age:12 },
    { name: 'tom', age:13 },
    { name: 'lucy', age:14 },
    { name: 'jimmy', age:12 }
]
let result1 = arr2.find((item, index, array) => {
    return item.age === 12
})
console.log('result1:', result1)    //result: {name: "jane", age: 12}

//findIndex —— 遍历数组,返回使函数返回 true 的第一个元素的下标
let result2 = arr2.findIndex((item, index, array) => {
    return item.age === 12
})
console.log('result2:', result2)    //result: 0

//filter —— 遍历数组,返回使函数返回 true 的所有元素组成的数组
let result3 = arr2.filter((item, index, array) => {
    return item.age === 12
})
console.log('result3:', result3)    
//result3: [{ name: 'jane', age:12 },{ name: 'jimmy', age:12 }]

检查元素:some、every

//some —— 数组中至少有一个元素在执行函数时返回的结果为 true
let arr1 = [1, 2, 3, 4, 5, 5, 6, 7]
let arr2 = [1, 2, 3, 4, 5, 5, 6, 7, 8]
let flag1 = arr1.some((item)=>{
    return item > 7
})  //flag: false
let flag2 = arr2.some((item)=>{
    return item > 7
})  //flag: true

//every —— 数组中每一个元素在执行函数时返回的结果为 true
let arr1 = [1, 2, 3, 4, 5, 5, 6, 7]
let arr2 = [1, 2, 3, 4, 5, 5, 6, 7, 8]
let flag1 = arr1.every((item)=>{
    return item > 7
})  //flag: false
let flag2 = arr2.every((item)=>{
    return item > 7
})  //flag: false
let flag3 = arr2.every((item)=>{
    return item > 0
})  //flag: true

转换数组:map

let arr = [1, 2, 3, 4, 5, 5, 6, "b", "c"]
let result = arr.map(function(item, index, array) {
  return `${item}--hello`;
})
//result:   ["1--hello", "2--hello", "3--hello", "4--hello", "5--hello", "5--hello", "6--hello", "b--hello", "c--hello"]

遍历数组:forEach

let arr = [1, 2, 3, 4]
arr.forEach(function(item, index, array) {
  console.log(item, index, array)
})
// 1 0 (4) [1, 2, 3, 4]
// 2 1 (4) [1, 2, 3, 4]
// 3 2 (4) [1, 2, 3, 4]
// 4 3 (4) [1, 2, 3, 4]

以上会改变数组的方法:
push、unshift、pop、shift、splice、reverse、sort、fill

3.2.3 Date

Date 为 JavaScript 内置的一个对象,用来存储日期、时间,并提供了一些管理它们的方法。

//创建 Date 对象
let date = new Date()//date: Wed Jun 05 2019 17:10:27 GMT+0800 (中国标准时间)

//getDay:返回 0-6,对应星期日到星期六
date.getDay()   //3

//getDate:返回 1-31,对应一个月中的 1-31 号
date.getDate()  //5

//getMonth:返回 0-11,对应1-12月
date.getMonth() //5——6月 

//getFullYear:以四位数字返回年份
date.getFullYear()  //2019

//getHours:返回小时
date.getHours()   //17
//getMinutes:返回分钟
date.getMinutes()   //10
//getSeconds:返回秒
date.getSeconds()   //27
//getMilliseconds:返回毫秒
date.getMilliseconds()  //511

//getTime:返回 1970 年 1 月 1 日至今的毫秒数
date.getTime()  //1559725827511

Date.now()

有一个特殊的方法 Date.now(),它会返回当前的时间戳,不需要整个 Date 对象。

它相当于 new Date().getTime(),但不会在中间创建一个 Date 对象。因此更快,且不会对垃圾处理造成额外的压力。

Date.now()  //1559725827511

3.2.4 RegExp

RegExp 是 JavaScript 内置的正则表达式对象,用来对字符串进行匹配。

3.2.4.1 定义正则表达式

1)字面量语法:/pattern/attributes

2)创建 RegExp 对象的语法:new RegExp(pattern, attributes);

3.2.4.2 参数
  1. pattern 是一个字符串,指定了正则表达式的模式或其他正则表达式。
  2. attributes 是一个可选的字符串,包含属性 “g”、“i” 和 “m”,分别用于指定全局匹配、区分大小写的匹配和多行匹配。如果 pattern 是正则表达式,而不是字符串,则必须省略该参数。
3.2.4.3 修饰符
i  执行对大小写不敏感的匹配。
g  执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。
m  执行多行匹配。
3.2.4.4 方括号——查找某个范围内的字符
[abc]  查找方括号之间的任何字符。(字母、数字、符号、中文均可)
[^abc]  查找任何不在方括号之间的字符。(字母、数字、符号、中文均可)
[0-9]  查找任何从 0 至 9 的数字。
[a-z]  查找任何从小写 a 到小写 z 的字符。
[A-Z]  查找任何从大写 A 到大写 Z 的字符。
[A-z]  查找任何从大写 A 到小写 z 的字符。
[adgk]  查找给定集合内的任何字符。
[^adgk]  查找给定集合外的任何字符。
(red|blue|green)  查找任何指定的选项。(可匹配单词)
3.2.4.5 元字符(Metacharacter)——拥有特殊含义的字符
.  查找单个字符,除了换行和行结束符。
\w  查找单词字符。(数字、字母、下划线)
\W  查找非单词字符。(除数字、字母、下划线的字符,包括中文、特殊符号、空格等)
\d  查找数字。
\D  查找非数字字符。
\s  查找空白字符。
\S  查找非空白字符。
\b  匹配单词边界。
\B  匹配非单词边界。
\0  查找 NUL 字符。
\n  查找换行符。
\f  查找换页符。
\r  查找回车符。
\t  查找制表符。
\v  查找垂直制表符。
\xxx  查找以八进制数 xxx 规定的字符。
\xdd  查找以十六进制数 dd 规定的字符。
\uxxxx  查找以十六进制数 xxxx 规定的 Unicode 字符。
3.2.4.6 量词
n+  =>  匹配任何包含至少一个 n 的字符串。
n*  =>  匹配任何包含零个或多个 n 的字符串。
n?  =>  匹配任何包含零个或一个 n 的字符串。
n{X}  =>  匹配包含 X 个 n 的序列的字符串。 a{2}  aa
n{X,Y}  =>  匹配包含 X 至 Y 个 n 的序列的字符串。a{2,4} aa aaa aaaa  
n{X,}  =>  匹配包含至少 X 个 n 的序列的字符串。a{2}  aa aaa aaaa aaaaa...
n$  =>  匹配任何结尾为 n 的字符串。
^n   => 匹配任何开头为 n 的字符串。
?=n   => 匹配任何其后紧接指定字符串 n 的字符串。
?!n   => 匹配任何其后没有紧接指定字符串 n 的字符串。
3.2.4.7 RegExp 对象方法

1. test => 检索字符串中指定的值。返回 true 或 false。

let reg = /[a-z]/i;
reg.test('A')   //true
reg.test('z')   //true
reg.test(1)    //false

2. exec => 检索字符串中指定的值。返回找到的值,并确定其位置。

let str = 'hello everybody!';
let reg = /e/i;
reg.exec(str)   //["e", index: 6, input: "hello everybody!", groups: undefined]
reg.exec('abc')    //null

3. compile => 编译正则表达式。用于改变 RegExp 对象
RegExpObject.compile(regexp,modifier)

let str = 'hello everybody!';
let reg = /e/i;
reg.compile('[0-9]', g)//reg: /[0-9]/g
3.2.4.8 支持正则表达式的 String 对象的方法

1. search => 检索与正则表达式相匹配的值(返回第一个匹配到的位置)

let str = 'hello everybody!';
let reg = /l/i;
str.search(reg) //2
reg = /f/g;
str.search(reg)  //-1

2. match => 找到一个或多个正则表达式的匹配。(返回匹配到的值)

let str = 'hello everybody!';
let reg = /l/g;
str.match(reg) //["l", "l"]
reg = /f/g;
str.match(reg)  //null

3. replace => 替换与正则表达式匹配的子串(返回替换后的整个字符串,改变原字符串)

let str = 'hello everybody!';
let reg1 = /l/;
str.replace(reg1, 'p')  //"heplo everybody!"
str = 'hello everybody!';
let reg2 = /l/g;
str.replace(reg2, 'p')  //"heppo everybody!"

4. split => 在符合 separator 的位置把字符串分割为字符串数组。

let str = 'hello everybody!';
let reg = /e/g;
str.split(reg)  //["h", "llo ", "v", "rybody!"]

3.2.5 Number、String、Boolean 构造函数

前面 2.1 基本类型 章节已经介绍了 Number、String、Boolean 类型,下面介绍下 Number、String、Boolean 三种构造函数。

3.2.5.1 Number 构造函数
  • 通过 Number 构造函数创建的是对象
let numObj = new Number(2);
let num = 2;

typeof numObj  //"object"
typeof num //"number"
numObj === num; //false

//通过 Number 构造函数创建的是对象可以调用数字的方法
let sum = numObj + numObj;  //4
let str = numObj + 'hello';  //"2hello"

//通过 Number 构造函数创建的是对象可以添加属性
numObj.name='jane'//
console.log(numObj.name)   //"jane"
num.name='tom'
console.log(num.name)  //undefined
  • Number() 也可以把各种值转换为数字,不能转换的就是 NaN。
Number('1')   //1
Number(true)  //1
Number(false)   //0
Number('text')  //NaN
3.2.5.2 String 构造函数
  • 通过 String 构造函数创建的是对象
let stringObj = new String('abc');
let string = 'abc';

typeof stringObj  //"object"
typeof string //"string"

stringObj === string; //false

//通过 String 构造函数创建的是对象可以添加属性
stringObj.name='jane'//
console.log(stringObj.name)   //"jane"
string.name='tom'
console.log(string.name)  //undefined

//通过 String 构造函数创建的是对象可以调用字符串的方法
let testStr = new String('testStr');
testStr.substr(1,3) //"est" 截取 testStr 从第1个开始,取3个
testStr.substring(1,4)  //"est" 截取 testStr 从第1个开始,到第4个,不包含第4个
testStr.slice(1,4)  //"est" 截取 testStr 从第1个开始,到第4个,不包含第4个
testStr.substring(2,0)  //"te"
testStr.slice(2,0)  //""
  • String() 也可以把各种值转换为字符串。
String(1)   //"1"
String(true)  //"true"
String(false)   //"false"
String('text')  //"text"
String([1,2,3])   //"1,2,3"
String({name: 'jane'})  //"[object Object]"
3.2.5.3 Boolean 构造函数*

通过 Boolean 构造函数创建的是对象

let booleanObj = new String(true);
let boolean = true;

typeof booleanObj  //"object"
typeof boolean //"boolean"

booleanObj === boolean; //false

//通过 Boolean 构造函数创建的是对象可以添加属性
booleanObj.name='jane'//
console.log(booleanObj.name)   //"jane"
boolean.name='tom'
console.log(boolean.name)  //undefined

  • Boolean() 也可以把各种值转换为布尔值,同 2.1 基本类型 里面介绍的一样 。
Boolean('111') === true
Boolean('text') === true//true
Boolean(2) === true//true
Boolean({}) === true//true
Boolean({name: 'jane'}) === true//true

3.2.6 Math

Math 不是对象的类,没有构造函数,但作为 JavaScript 内置的对象,可直接调用来进行数学方法的处理。

let num = 12345.12;
//四舍五入
Math.round(num);    //12345
//下舍入
Math.floor(num);    //12345
//上舍入
Math.ceil(num);    //12346
//取两个数中的最高值
Math.max(2,3)   //3
//取两个数中的最低值
Math.max(2,3)   //2
//返回 0 ~ 1 之间的随机数
Math.random()   //0.657487039270241

3.3 原型

所有引用类型其隐式原型都指向它的构造函数的显式原型

obj.__proto__ === Object.prototype

  • 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,会去它的 __proto__ (即其构造函数的 prototype )中寻找。

隐式原型:

所有引用类型(数组、对象、函数),都有一个 __proto__ 属性,属性值是一个普通对象。

显式原型:

所有的函数都有一个 prototype 属性,属性值是一个普通对象。

hasOwnProperty:

只判断对象本身是否包含某属性,不去其原型链中寻找。

3.4 原型链

原型链结构图

当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,会去它的 __proto__ (即其构造函数的 prototype )中寻找,这在编程中被称为“原型继承”。

3.5 判断数据类型(构造函数、原型、typeof)

3.5.1 typeof

typeof——返回给定变量的数据类型,可能返回如下字符串:

返回字符串 —— 数据类型
'undefined' —— Undefined
'boolean' —— Boolean
'string' —— String
'number' —— Number
'symbol' —— Symbol
'object' —— Object / Null (Null 为空对象的引用)
'function' —— Function
  • typeof 返回的类型都是字符串形式,需注意,例如:
alert(typeof 'abcd' === "string")   =>   true
alert(typeof 'abcd' === String)   =>    false
  • typeof 对于Array、RegExp、Date 类型统一返回 ‘object’
alert(typeof [] === 'object')   =>    true
alert(typeof new Date)   =>    'object'
  • typeof 对于 function 类型统一返回 ‘function’
alert(typeof function(){} === 'function')   =>    true
  • typeof 是操作符而非函数,所以可以使用圆括号,也可以不使用。
alert(typeof 'abcd'); /     alert(typeof ('abcd'));
3.5.2 instanceof

判断一个函数是否是变量的构造函数
语法: objectA instanceof constructorB
判断逻辑: 变量a的 _ proto _ 一层一层往上找,用来检测 constructor.prototype 是否存在于参数 object 的原型链上,是则返回 true,不是则返回 false。

    alert([1,2,3] instanceof Array) ---------------> true
    alert(new Date() instanceof Date) ------------> true
    alert(function(){this.name="22";} instanceof Function) ------------> true
    alert(function(){this.name="22";} instanceof function) ------------> false
3.5.3 constructor

返回对象对应的构造函数

alert({}.constructor === Object);  =>  true
alert([].constructor === Array);  =>  true
alert('abcde'.constructor === String);  =>  true
alert((1).constructor === Number);  =>  true
alert(true.constructor === Boolean);  =>  true
alert(false.constructor === Boolean);  =>  true
alert(function s(){}.constructor === Function);  =>  true
alert(new Date().constructor === Date);  =>  true
alert(new Array().constructor === Array);  =>  true
alert(new Error().constructor === Error);  =>  true
alert(document.constructor === HTMLDocument);  =>  true
alert(window.constructor === Window);  =>  true
alert(Symbol().constructor);    =>    undefined
 Symbol 值通过Symbol函数生成,是一个原始类型的值,不是对象,不能通过 constructor 判断;
null 和 undefined 是无效的对象,没有 constructor,因此无法通过这种方式来判断。

注:

  • constructor 不能用来判断 Null 及 Undefined 类型
  • 函数的 constructor 不稳定。
    当一个函数被定义时,JS 引擎会为其添加 prototype 原型,然后在 prototype 上添加一个 constructor 属性,并让其指向函数的引用。
    但函数的 prototype 被重写后,原有的 constructor 引用会丢失。再次新建一个次函数的实例后,其 constructor 指向的内容已发生改变。
    因此为了规范开发,在重写对象原型时,一般都需要重新给 constructor 赋值,以保证对象实例的类型不被更改。
3.5.4 Object.prototype.toString()
  • toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。
  • 这是一个内部属性,其格式为 [object Xxx] ,是一个字符串,其中 Xxx 就是对象的类型。
  • 对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。
Object.prototype.toString.call(new Date) === '[object Date]';   //true
Object.prototype.toString.call(new String) === '[object String]';   //true
Object.prototype.toString.call(Math) === '[object Math]';   //true
Object.prototype.toString.call(undefined) === '[object Undefined]';   //true
Object.prototype.toString.call(null) ==='[object Null]';   //true
Object.prototype.toString.call('') === '[object String]' ;      //true 
Object.prototype.toString.call(123) === '[object Number]' ;       //true
Object.prototype.toString.call(true) === '[object Boolean]' ;    //true 
Object.prototype.toString.call(Symbol()) === '[object Symbol]';    //true
Object.prototype.toString.call(new Function()) === '[object Function]';    //true
Object.prototype.toString.call(new Date()) === '[object Date]' ;    //true
Object.prototype.toString.call([]) === '[object Array]';   //true
Object.prototype.toString.call(new RegExp()) === '[object RegExp]' ;    //true
Object.prototype.toString.call(new Error()) === '[object Error]';   //true
Object.prototype.toString.call(document) === '[object HTMLDocument]';   //true
Object.prototype.toString.call(window) === '[object Window]';   //true

类型判断小结:

  • 1)typeof 更适合判断基本类型数据,因为对于引用类型数据,typeof 只会返回 ‘function’ 或 ‘object’,不会返回其他的数组等类型;
  • 2)instanceof 只能用来判断实例类型,包括 Array、Date 等,判断基本类型会永远返回 true,无意义;
  • 3)constructor 不能用来判断 Null 及 Undefined 类型
  • 4)注:new String()、new Number() 生成的实际上为对象,但只能通过 typeof 能判断出来,constructor 和 .toString 只会返回 String 或 Number,无法判断是基本类型或是引用类型。

3.6 模拟面向对象的类

面向对象的编程中的类: 是用于创建对象的可扩展的程序代码模版,它为对象提供了状态(成员变量)的初始值和行为(成员函数和方法)的实现。

ES6 中封装了 class,在 ES6 中的 class 出现前,也可以通过其他的编程模式来创建类。

3.6.1 基于构造函数的类
function Person(name, age) {
    function getAge() {
        return age + 10;
    } 
    this.outputInfo = function() {
        console.log('name:', name, 'currentAge: ', getAge())
    }
}
let jane = new Person('jane', 14);
jane.outputInfo()     //name: jane currentAge:  24
  • 优点:
    如上,getAge 方法是外部不可见的(私有的),可隐藏内部方法的实现;而 outputInfo 被分配到 this 上,则是外部可见的(即公有的),用此构造函数创建的对象可以使用共有方法。

  • 缺点:
    所有的方法都在构造函数内部,每次 new 一个实例对象,都会创建内部的这些方法,且不同实例对象间不能共享这些方法,浪费内存资源。

3.6.2 基于原型的类
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype._getAge = function() {
    return this.age + 10;
}

Person.prototype.outputInfo = function() {
    console.log('name:', this.name, 'currentAge: ', this._getAge())
}
let jane = new Person('jane', 14);
jane.outputInfo()     //name: jane currentAge:  24
  • 优点:
    如上,将公共方法放在原型中,创建新对象直接调用原型中的方法,对象中没有的属性和方法,可以在原型链中寻找。

3.7 ES6 中的 class

在 JavaScript 中,class 实际上是一种“语法糖”,我们可以使用 class 去干净整洁地定义基于原型的类。

3.7.1 class 使用
3.7.1.1 基于原型实现类
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype._getAge = function() {
    return this.age + 10;
}

Person.prototype.outputInfo = function() {
    console.log('name:', this.name, 'currentAge: ', this._getAge())
}
let jane = new Person('jane', 14);
jane.outputInfo()     //name: jane currentAge:  24
3.7.1.2 基于 class 实现相同的类
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    getAge () {
        return this.age + 10;
    }
    outputInfo () {
        console.log('name:', this.name, 'currentAge: ', this.getAge())
    }
}
let jane = new Person('jane', 14);
jane.outputInfo()     //name: jane currentAge:  24
3.7.2 class 完成了如下过程:
  • 声明了 Person 变量,且将它的值指向 constructor 函数,如下图:

Person

  • 所有类中定义的方法挂到了 Person.prototype 上,如下图:

Person.prototype

3.7.3 class 特点:
  • 必须与 new 一同使用,如下图:

without new

  • class 拥有一个默认的 constructor(){}
    class 中没有定义 constructor 的话,会默认生成一个空的构造函数:constructor(){}

  • class 中的方法都是不可枚举的
    这样遍历新建的对象时,类上的方法不会被遍历。

  • class 中只能定义方法

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    subName: 'jane'//只能定义方法
}
//Uncaught SyntaxError: Unexpected identifier
//如果一定要添加可在原型链上添加或通过 getter 添加
3.7.4 静态方法

新增在 class 上而非新增在 prototype 上的方法,称为静态方法。

3.7.4.1 定义静态方法
class Person {
    static sum(a, b){
        console.log(a+b)
    }
}
3.7.4.2 静态方法调用

静态方法需直接通过该类来调用,无需实例化,实例化调用会报错

class Person {
    static sum(a, b){
        console.log(a+b)
    }
}
Person.sum(1, 2);//3
let person = new Person();
person.sum(1, 2);//报错:Uncaught TypeError: person.sum is not a function
3.7.5 类继承
3.7.5.1 类继承的实现

假设现在有两个类:Person 和 Student

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    getAge () {
        return this.age + 10;
    }
    outputInfo () {
        console.log('name:', this.name, 'currentAge: ', this.getAge())
    }
}

class Student {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    getAge () {
        return this.age + 10;
    }
    outputInfo () {
        console.log('name:', this.name, 'currentAge: ', this.getAge())
    }
    startStudy () {
        alert(`我是${this.name}, 我要学习啦!`)
    }
}
let student = new Student('jane', 12)
student.startStudy()

如上,两个类是独立的,但其实 Student 是属于 Person 的,Person 中的方法 Student 中都有,且可以自己进行拓展,所以我们可以使用 “extends” 让 Student 来“继承” Person:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    getAge () {
        return this.age + 10;
    }
    outputInfo () {
        console.log('name:', this.name, 'currentAge: ', this.getAge())
    }
}

class Student extends Person {
    startStudy () {
        alert(`我是${this.name}, 我要学习啦!`)
    }
}
let student = new Student('jane', 12)
student.startStudy()

如上,便实现了 Student 对 Person 的继承,如果在 Student.prototype 中没有找到某个方法,就会从 Person.prototype 中继续寻找。

3.7.5.2 super 的作用

1. 重写构造函数 —— 继承父类参数

当子类有构造函数时,必须调用 super 方法来继承父类的参数。

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    getAge () {
        return this.age + 10;
    }
    outputInfo () {
        console.log('name:', this.name, 'currentAge: ', this.getAge())
    }
}

class Student extends Person {
    constructor(name, age) {
        super(name, age); //继承父类参数,必须写在构造函数最前面
        this.studyStatus = false;   //必须写在 super() 之后,否则报错
    }

    startStudy () {
        super.outputInfo()
        alert(`我是${this.name}, 我要学习啦!`)
    }
}
let student = new Student('jane', 12)
student.startStudy()

2. 调用父类的方法 —— super.fun()

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    getAge () {
        return this.age + 10;
    }
    outputInfo () {
        console.log('name:', this.name, 'currentAge: ', this.getAge())
    }
}

class Student extends Person {
    constructor(name, age) {
        super(name, age); //继承父类参数,必须写在构造函数最前面
        this.studyStatus = false;   //必须写在 super() 之后,否则报错
    }

    startStudy () {
        super.outputInfo()  //会执行父类的这个方法
        alert(`我是${this.name}, 我要学习啦!`)
    }
}
let student = new Student('jane', 12)
student.startStudy()

箭头函数没有自己的 super,它们的 super 即是就近的上下文的 super,同它的 this 一样。

3.7.5.3 重写父类方法
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    getAge () {
        return this.age + 10;
    }
    outputInfo () {
        console.log('name:', this.name, 'currentAge: ', this.getAge())
    }
}

class Student extends Person {
    constructor(name, age) {
        super(name, age); //继承父类参数,必须写在构造函数最前面
        this.studyStatus = false;   //必须写在 super() 之后,否则报错
    }
    outputInfo () {
        alert(`name: ${this.name}, currentAge:  ${this.getAge()}`)
    }
}
let student = new Student('jane', 12)
student.outputInfo()

如上,子类中有与父类相同的方法,会直接覆盖父类的方法,因为执行时,会现在 Student.prototype 中寻找这个方法,没有找到时,才会从 Person.prototype 中继续寻找。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值