文章目录
摘要1 引言2 基础2.1 嵌入网页2.2 基本语法2.3 数据类型2.3.1 基本数据类型2.3.2 高级数据类型2.4 程序结构2.4.1 顺序结构2.4.2 选择结构2.4.3 循环结构3 函数3.1 函数定义3.1.1 定义函数的两种方式3.1.2 函数参数3.2 变量作用域3.3 变量提升3.4 全局作用域3.5 命名空间3.6 解构赋值3.7 方法3.8 高阶函数3.8.1 map/reduce3.8.2 filter3.8.3 sort4 面向对象4.1 JS创建对象的方式4.2 原型对象(prototype)和对象原型(_ _ proto _ _)4.3 原型对象(prototype)中的constructor属性4.4 原型链实现继承4.4.1 call方法4.4.2 使用call继承4.4.3 原型链详解4.4.4 通过“中间人”链接原型链4.5 class语法糖4.5.1 class实现继承摘要
下面文章中使用JS代替JavaScript
1 引言
JavaScript(以下统一简称JS)是目前最流行的脚本语言,没有之一,在你的手机,pad,电脑等交互逻辑都是通过JS实现的。
JS是一种运行在浏览器中的解释型的编程语言,nodejs将JS移植到了服务端,这让JS成为了全能战士
只要你想接触前端,JS是你绕不开的话题,在web前端领域,JS是绝对的顶流
JS 历史
1995年,还是一个静态网页时代,当时的网景公司凭借Navigator浏览器成为Web时代开启时最著名的第一代互联网公司。当时网景公司想在静态页面加一些动态效果,就让Brendan Eich设计JS语言,这哥们真是一个天才,竟然在10天时间内写出JS,没错,只有10天,至于为什么名字叫JavaScript,其实就是想蹭一下Java的热度,语言本身与Java毫无关系。
ECMAScript
因为JavaScript的功能十分适合网页的动态化,随着计算机技术的不断发展,人们也不再满足于静态网页,微软做为行业巨头,自然也嗅到了这个机会,于是在JS问世后一年,微软开发了Jscript,为了能够让JS成为全球标准,网景、微软还有几家公司联合ECMA组织制定了Javascript语言标准,被称为ECMAScript语言标准。
ECMAScript是一种语言标准,JavaScript是网景公司对ECMAScript标准的一种实现。
至于为什么不把JavaScript当作标准名称呢,是因为JavaScript被网景注册了商标,不是很严谨的场合可以把JS与ECMAScript当作一回事。
发展历程,版本迭代
JS是10天被设计出来,虽然Eich很牛,但谁也架不住时间紧、任务重,所以,JS有很多设计缺陷。
ES6标准发布
2 基础
2.1 嵌入网页
方式1
JS脚本可以被嵌入在网页的各个部分,使用标签,一般放在body或者head中
方式2 直接引入文件
<script src="a.js"><script>
VScodeSublimeNotepad++HbuilderXIDE 推荐
2.2 基本语法
JS每条语句以“;”结尾,语句块用{。。。},JS并不强制要求在每个语句的结尾加“;”,解释器负责给每个语句补上“;”,但是一般情况下,为了保持良好编程习惯,还是要在每行语句后面加上一个分号。
语句块
var a = 10;"hello world!";var b = 123; var c = 456; // 这是两条语句
注释
// 单条/* 多条注释*/
大小写
JS大小写敏感
2.3 数据类型
2.3.1 基本数据类型
Number
JS 不区分整数和浮点数,统一用Number表示
var a = 123;console.log(typeof(a)); // number 123; // 整数1230.456; // 浮点数0.4561.2345e3; // 科学计数法表示1.2345x1000,等同于1234.5-99; // 负数NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
数字类型可以使用+、-、*、/ 、%求余运算
字符串
//''或者“” // ``反引号可以包括多行文本var s1 = "Hello";var s2 = 'hello';var s3 = `人生苦短我用JS多行文本输出`;
布尔
truefalse
布尔运算符多用于条件判断,也可以进行逻辑运算,逻辑与&&,逻辑或||,逻辑非!
console.log(true && false); // falseconsole.log(true || false); // trueconsole.log(!true); // falsevar age = 15;if(age>18){console.log("成年人");}else{console.log("未成年人"); // 未成年人}
JS中的相等
<script>console.log(null === null); // trueconsole.log(NaN === NaN); // false console.log(1/3 === (1-2/3)); // false console.log(Math.abs(1/3)-Math.abs(1-(2/3))<0.0000001); // true console.log('3'===3); // falseconsole.log('3'==3); // true </script>
2.3.2 高级数据类型
数组
JS 的数组(Array)可以包含任意数据类型,并通过索引来访问每个元素。通过length属性获取Array的长度
length
数组内的元素是可以原地修改的,可以通过数组的length属性动态修改数组的长度。
var arr = [1,-9,null,true,99.9];console.log(arr.length);console.log(arr[1]);console.log(arr[2]);console.log(arr[4]);console.log(arr[5]); // 超过下标也不会报错,返沪undefinedarr.length = 8;arr[7] = 'hello';console.log(arr); // [1, -9, null, true, 99.9, undefined, undefined , "hello"]
indexOf
获取数组内元素的索引,如果没有这个元素,返回-1.如果有这个元素,直接返回这个元素对应的索引数值
console.log(arr.indexOf(1)); //0 console.log(arr.indexOf(99.9)); //4console.log(arr.indexOf('fa')); //-1
slice
截取数组的部分元素,返回一个新数组,不包括结尾索引
var arr = [1,-9,null,true,99.9];console.log(arr); //index.html:10 (5) [1, -9, null, true, 99.9]var arr_ = arr.slice(0,3); // 返回一个新数组console.log(arr_); // (5) [1, -9, null, true, 99.9]console.log(arr); // (5) [1, -9, null, true, 99.9]
push/pop
push:从数组后面插入元素
pop:从数据后面删除元素
var arr = ["三国战将"]arr.push("赵云");arr.push("关羽");arr.push("张飞");console.log(arr); //(4) ["三国战将", "赵云", "关羽", "张飞"]console.log(arr.pop()); // 删除最后一个元素并返回console.log(arr); // (3) ["三国战将", "赵云", "关羽"]
unshift/shift
unshift:从数组头部插入元素
shift:从数据头部删除元素
var arr = ["三国战将"]arr.unshift("赵云");arr.unshift("关羽");arr.unshift("张飞");console.log(arr); //(4) ["张飞", "关羽", "赵云", "三国战将"]console.log(arr.shift()); // 删除第一个元素并返回console.log(arr); // (3) ["关羽", "赵云", "三国战将"]
sort
对当前数组进行排序,原地排序
var a = ["C","B","A"];a.sort();console.log(a); // (3) ["A", "B", "C"]
reverse
反转数组,原地反转数组
var a = ["C","B","A"];a.reverse();console.log(a); // (3) ["A", "B", "C"]
splice
splice()
方法是修改Array
的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素:
var a = ["乔丹","科比","艾弗森","卡特","詹姆斯"];// 从第二个索引开始,删除两个元素,返回被删除的元素,同时从第二个索引开始添加新元素var d = a.splice(2,2,"韦德","安东尼"); console.log(d); //(2) ["艾弗森", "卡特"]console.log(a); // (5) ["乔丹", "科比", "韦德", "安东尼", "詹姆斯"]
concat
把当前数组和另一个数组链接起来,返回一个新数组
var a = ["C","B","A"];var b = a.concat(1,2,3,[7,8,9]);console.log(a); // (3) ["C", "B", "A"]console.log(b); //(9) ["C", "B", "A", 1, 2, 3, 7, 8, 9]
join
把数组中的每个元素用指定的字符串链接起来,返回链接后的字符串
var a = ["C","B","A"];var b = a.join('-');console.log(a); // (3) ["C", "B", "A"]console.log(b); //C-B-A
多维数组
var a = [[1,2,3],[4,5,6]];console.log(a[1][1]); // 5console.log(a[0][2]); // 3
对象
JS的对象是一种无序的集合数据类型,由若干键值对组成,对象是面向对象编程的基础,程序中的一个个对象就是对现实生活的抽象。
用{。。。}表示一个对象, key:value形式申明,每个kv对之间用逗号分割,最后一个键值对不需要再末尾加,如果加了,有些老旧浏览器将报错;通过点号可以点出一个对象的属性,也可以通过obj[attr]获取属性,如果属性名有特殊字符,那么属性名必须用引号括起来,取属性的时候不能使用点号,所以对象的属性尽量使用没有特殊符号的单词,做到见名知意
var xiaoming = {name:'小明',birth:1994,height:1.86,weight:78.0,score:null}console.log(xiaoming.name); // 小明console.log(xiaoming.age); // undefinedconsole.log(xiaoming.birth); // 1994console.log(xiaoming['weight']); // 78xiaoming.xixi = 'haha';console.log(xiaoming.xixi); // haha
即使对象没有这个属性,也不会报错可以创建对象后,动态设置属性,这是动态语言所特殊的
console.log("xixi" in xiaoming);// trueconsole.log("toString" in xiaoming); // trueconsole.log(xiaoming.hasOwnProperty('xixi')); // trueconsole.log(xiaoming.hasOwnProperty('age')); // falsedelete xiaoming.xixi;console.log(xiaoming.hasOwnProperty('xixi')); // false
判断对象是否具备某个属性
可以使用in关键字,attr in object 返回一个布尔值,如上例,toString没有在xiaoming这个对象中定义,但是同样返回true,这说明JS中所有对象的原型链都会有object对象,object对象有toString属性;
如果只想拿到对象本身的属性,可以使用object.hasOwnProperty()方法
Map
Map是一组键值对结果,Map的查找时间复杂度为O(1)
通过二维数组构造一个Map
var m = new Map([['语文',78],['数学',98],['英语',79]]);console.log(m);//Map(3) {"语文" => 78, "数学" => 98, "英语" => 79}console.log(m.get('语文')); // 78console.log(m.has('英语')); // true console.log(m.set('物理',100)); // Map(4) {"语文" => 78, "数学" => 98, "英语" => 79, "物理" => 100}delete m.delete('语文');console.log(m);
Map.get() 获取一个key的值
Map.has()判断这个集合中是否有这个key
Map.set() 设置key-value
Map.delete() 删除map的某个key
Set
Set和Map类似,Set是一个key的集合,但不存储value,由于key不能重复,所以Set中每个key都是唯一的。
创建一个Set,需要提供Array作为输入,或者直接创建一个空Set
var s = new Set();s.add(1);s.add(2);s.add(3);s.add(3);console.log(s); // Set(3) {1, 2, 3}var s1 = new Set([1,2,3,'3',3,3,3]);console.log(s1); // Set(4) {1, 2, 3, "3"}s1.delete('3');s1.delete(2);console.log(s1); // Set(2) {1, 3}
2.4 程序结构
2.4.1 顺序结构
JS中的顺序结构就是从上而下执行的。
2.4.2 选择结构
把null
、undefined
、0
、NaN
和空字符串''
视为false
,其他值一概视为true
'use strict';var height = parseFloat(prompt("请输入身高(m):"));var weight = parseFloat(prompt("请输入体重(kg):"));var bmi = weight / (height*height);if(bmi<18.5){console.log("过轻");}else if(bmi>=18.5 && bmi < 25){console.log("正常");}else if(bmi>=25 && bmi < 28){console.log("过重");}else if(bmi >=28 && bmi < 32){console.log("肥胖");}else if(bmi >=32){console.log("严重肥胖");}else{console.log("数据有误");}
2.4.3 循环结构
while
先判断条件,条件不满足一次都不执行
'use strict';var sum = 0;var i =0 ;while(i<=100){sum += i;i ++ }console.log(sum); // 5050
do-while
先执行循环体,在判断条件,至少会执行一次循环体
'use strict';var sum = 0;do{sum += i;i ++ }while(i<=100);console.log(sum);
通过一个小实例看一下while与do… while的区别
'use strict';var sum = 0;var sum_ = 0;var i =1 ;do{sum += i;i ++ }while(i>100);console.log(sum); // 1while(i>100){sum_ += i;i++;}console.log(sum_); // 0
循环一定要注意结束条件,< 与 <= 会是完全不同的结果
普通for循环
与C语言的for循环基本相同
var sum = 0;for(let i=0;i<=100;i++){sum += i;}console.log(sum); // 5050
for…in
遍历数组和对象这种容器数据类型
'use strict';var arr = ["张飞","关羽", "赵云"];for(let i in arr){console.log(i);console.log(arr[i]);}var obj1 = {name:'xiaohua',age:14,grade:4}for(let i in obj1){console.log(i);console.log(obj1[i]);}
for…of
遍历Array
可以采用下标循环,遍历Map
和Set
就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable
类型,Array
、Map
和Set
都属于iterable
类型。
具有iterable
类型的集合可以通过新的for ... of
循环来遍历。
'use strict';var arr = ["张飞","关羽", "赵云"];for(let i of arr){console.log(i);}for(let i of new Map([['语文',12],['数学',78]])){console.log(i);}for(let i of new Set([1,2,3])){console.log(i);}
foreach
iterable
内置的forEach
方法,它接收一个函数,每次迭代就自动回调该函数。
'use strict';var a = ['A', 'B', 'C'];a.forEach(function (element, index, array) {// element: 指向当前元素的值// index: 指向当前索引// array: 指向Array对象本身console.log(element + ', index = ' + index);});// 张飞, index = 0// 关羽, index = 1// 赵云, index = 2
3 函数
函数是对一系列动作的抽象
3.1 函数定义
3.1.1 定义函数的两种方式
方式1:
'use strict';function abs(x) {if (x >= 0) {return x;} else {return -x;}}var result = abs(-9);console.log(result);
方式2
函数也是一个对象,通过function()关键字定义函数
'use strict';var abs = function (x) {if (x >= 0) {return x;} else {return -x;}}var result = abs(-9);console.log(result);
3.1.2 函数参数
JS函数对于参数没有限制,可以多传递,也可以少传递,都不会报错
'use strict';var abs = function (x) {if (x >= 0) {return x;} else {return -x;}}var result = abs();console.log(result); // NaNresult = abs(-9,1,'b','c','d','w');console.log(result); // 9
arguments
arguments 是JS的一个关键字,这个关键字只有在函数内部有用,存储的信息是传入函数的参数,arguments类似Array但它不是一个Array。
'use strict';var abs = function (x) {console.log(arguments);for (let i=0; i<arguments.length;i++){console.log(arguments[i]);}if (x >= 0) {return x;} else {return -x;}}var result;result = abs(-9,1,'b','c','d','w');console.log(result); // 9
通过实例可以发现,无论有没有给函数加形式参数,都可以通过arguments关键字获取。
rest 参数
ES6标准引入了rest参数,arguments获取到了所有的输入参数,rest参数只获取额外的参数,rest参数前面使用…标识。
var test = function (a, b, ...rest){console.log(a); // 1console.log(b); // 2console.log(rest); // (4) [3, 4, 5, 6]}test(1,2,3,4,5,6);
小心return
var test = function (a, b, ...rest){console.log(a); // 1console.log(b); // 2console.log(rest); // (4) [3, 4, 5, 6]return //浏览器会默认添加一个分号10 // 这句话就执行不了了}var res = test(1,2,3,4,5,6);console.log(res); // undefined
3.2 变量作用域
如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不能应用改变量
'use strict';var f1 = function(){var a = 10;console.log(a);}console.log(a); //main.js:9 Uncaught ReferenceError: a is not defined at main.js:9
不同函数的相同函数名是相互独立,互不干扰的
嵌套函数,内部函数可以访问外部函数的作用域,外部函数访问不到内部函数的作用域
'use strict';function outer(){var a = 10;function inner(){var b = 11;console.log(a);}inner(); // 10console.log(b); // ReferenceError: b is not defined}outer();
内外部函数具有相同名称的变量,内部函数会覆盖外部函数
function outer(){var a = 10;function inner(){var a = 11;console.log(a);}inner(); // 11console.log(b); // ReferenceError: b is not defined}outer();
这说明JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
3.3 变量提升
JS有三种声明变量的关键字,const, let, var
const 定义常量
'use strict'; const PI = 3.14;PI = 3; // TypeError: Assignment to constant variable.console.log(PI);// 不能改变常量的值// 具有块级作用域{const sum = 0 const a = 11;console.log(sum); // 0}{const sum =100;const a = 12;console.log(sum); // 110}console.log(sum); // main.js:18 Uncaught ReferenceError: sum is not defined
let 定义变量具有块级作用域,没有变量提升
// let不具备变量提升var f1 = function(){var a = 1;console.log(a+b); // ReferenceError: b is not definedlet b =2;}f1() // 具有块级作用域{let sum = 0 let a = 11;sum += a;console.log(sum); // 11}{let sum =100;let a = 12;sum += a;console.log(sum); // 112}console.log(sum); // main.js:18 Uncaught ReferenceError: sum is not defined
vat 定义变量不具备块级作用域,具有变量提升
// 未定义变量b,直接使用不报错,是因为JS变量提升机制,但是赋值不会提升var f1 = function(){var a = 1;console.log(a+b); // NaNvar b =2;}f1() // 没有块级作用域{var sum = 0 var a = 11;sum += a;console.log(sum); // 11}{var sum =100;var a = 12;sum += a;console.log(sum); // 112}console.log(sum); // 112
3.4 全局作用域
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window
,全局作用域的变量实际上被绑定到window
的一个属性
var sub = "learn";console.log(sub); // learn console.log(window.sub); // learn alert("hello");window.alert('hello');// 全局windowvar temp = window.alert;window.alert = function(){}alert("hello"); // 没反应window.alert = temp;alert("hello"); // 触发
3.5 命名空间
使用命令空间可以净化全局作用域
var myApp = {}myApp.test = "hello"myApp.f1 = function() {console.log("myapp f1 is running");}myApp.f1();var myApp2 = {}myApp2.test = "world";myApp2.f1 = function() {console.log("myapp f2 is running");}myApp2.f1();
3.6 解构赋值
// 1 直接拆包var [x,y,z] = ['hello','china','haha'];console.log(x); // helloconsole.log(y); // chinaconsole.log(z); // haha// 2 格式必须相同var [x,y,[a,b]] = ['中国','河北',['北京','天津']]; console.log(x); // 中国console.log(y); // 河北console.log(a); // 北京console.log(b); // 天津// 3 可以忽略一部分var [,,x] = ['hello','china','haha']; console.log(x); // haha
3.7 方法
在一个对象中绑定函数,叫做这个对象得方法
var xiaoming = {name: '小明',birth: 1990,age:function(){var y = new Date().getFullYear();return y-this.birth;},};var age = xiaoming.age();console.log(age);
this关键字
this是一个特殊的关键字,始终指向当前对象,也就是上例中的xiaoming对象,所以,this.birth可以拿到xiaoming的birth属性。
// 拆开写
3.8 高阶函数
3.8.1 map/reduce
map 将一个函数作用在一个数组的所有元素上
function pow(x){return x*x;}var arr = [1,2,3,4,5,6,7,8,9];var new_arr = arr.map(pow);console.log(arr); // (9) [1, 2, 3, 4, 5, 6, 7, 8, 9]console.log(new_arr); // (9) [1, 4, 9, 16, 25, 36, 49, 64, 81]// 同样可以用for循环实现var new_arr = new Array();for(let i=0; i<arr.length;i++){new_arr.push(pow(arr[i]));}console.log(new_arr); // (9) [1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce
function add(x,y){return x+y;}var arr = [1,2,3,4,5,6,7,8,9];var res = arr.reduce(add);// = add(add(add(add(add(add(add(add(1,2),3),4),5),6),7,8,9)console.log(res);
3.8.2 filter
对一个数组内的元素进行过滤,满足条件的保留,不满足的剔除
'use strict';var arr = [1,2,3,4,5,6,7,8,9];function even(x){if ((x%2)==0){return true;}else{return false;}}var res = arr.filter(even);console.log(res); // (4) [2, 4, 6, 8]
3.8.3 sort
var arr = [10, 20, 1, 2];arr.sort((x, y) => {return x - y});console.log(arr); // [1, 2, 10, 20]
4 面向对象
4.1 JS创建对象的方式
字面量
'use strict';var obj = {"name":"zhangsan","age":14,"grade":"一年级"};console.log(obj);
使用构造函数创建对象
function Player(name, age){// 这是一个构造函数this.name = name;this.age = age;this.run = function() {console.log(this.name + " is running!"); }}var kobe = new Player("kobe", 12);kobe.run(); // kobe is running!var james = new Player("James", 123);james.run();//James is running!
如果不写new
,这就是一个普通函数,它返回undefined
。但是,如果写了new
,它就变成了一个构造函数,它绑定的this
指向新创建的对象,并默认返回this
,也就是说,不需要在最后写return this;
忘记写new的后果
‘use strict’; 模式下,this的指向是undefined,给一个undefined绑定name会报错
'use strict';function Player(name, age){this.name = name; // Cannot set properties of undefined (setting 'name')this.age = age;this.run = function() {console.log(this.name + " is running!"); }}var kobe = Player("kobe", 12);// main.js:5 Uncaught TypeError: Cannot set properties of undefined (setting 'name')
不是严格模式下,this指向的是window,this.name会直接设置一个全局变量,这样更危险,因为污染了命名空间
function Player(name, age){this.name = name; this.age = age;this.run = function() {console.log(this.name + " is running!"); }}Player("kobe", 11);console.log(name); //kobeconsole.log(age); // 11run(); // kobe is running!
4.2 原型对象(prototype)和对象原型(_ _ proto _ _)
JS 中一切皆对象,每个对象都会设置一个原型,也就是对象原型_ _ proto _ _属性,对象原型这个属性指向它的原型对象,这里说起来比较绕,原型链也是JS中的重点和难点,原型链在JS中的地位就类似Python中魔法方法的地位,搞不懂就只能停留到初级阶段,无法体会JS的精髓。
使用构造函数创建对象存在的问题
'use strict';function Player(name, age){this.name = name; this.age = age;this.run = function() {console.log(this.name + " is running!"); }}var kobe = new Player("kobe",456);var james = new Player("James", 123);console.log(kobe.run == james.run); // false
通过上面的图片和代码的运行结果可以发现,每创建一个对象都会在内存中开辟出一个空间,保存这个对象的属性和方法,属性没有关系,但是相同的方法却被复制了很多次,如果创建的对象很多,那么程序的时间复杂度和空间复杂度会成倍增加,显然这不是一个好办法。
JS的发明者自然考虑到了这个问题,可以通过原型对象来解决,前面已经说过了JS中的每个对象都有一个原型对象,我们看一下什么是原型对象:
'use strict';function Player(name, age){this.name = name; this.age = age;}Player.prototype.run = function(){console.log(this.name + "is running");}var kobe = new Player("kobe",456);var james = new Player("James", 123);console.log(kobe);console.log(kobe.run == james.run); // false
对象的原型链
kobe------> Player.prototype ----------> Object.prototype ----------> null
4.3 原型对象(prototype)中的constructor属性
原型对象的constructor属性指向的是构造函数本身。
console.log(kobe.__proto__.constructor); // 指向的就是其构造函数/*ƒ Player(name, age){this.name = name; this.age = age;}*/
constructor用法:
如果一个构造函数,类似下面这种方式网原型对象添加属性,写起来会很长,之间的关系不是很清晰
function Player(name, age){this.name = name; this.age = age;}Player.prototype.run = function(){console.log(this.name + "is running");}Player.prototype.jump = function(){console.log(this.name + "is jumpping");}
换一种方式:
function Player(name, age){this.name = name; this.age = age;}Player.prototype = {constructor: Player, // 如果不加这个原型对象就会被覆盖,也就不知道实例是由哪个类实例化出来的run: function(){console.log(this.name + "is running");},jump:function(){console.log(this.name + "is jumpping");}}var kobe = new Player("kobe",456);var james = new Player("James", 123);console.log(kobe.__proto__.constructor);console.log(kobe.__proto__)console.log(kobe instanceof Player);
如果把constructor: Player这句话注释掉,结果如下:
4.4 原型链实现继承
4.4.1 call方法
function add(x, y){console.log(x+y);}function sub(x, y){console.log(x-y);}add.call(sub,1,3); // 4 用add函数替代sub函数sub.call(add,1,3); // -2 用sub函数替代add函数
4.4.2 使用call继承
function Sex(props){this.sex = props.sex;}function Person(props){this.name = props.name;}function Student(props){Person.call(this, props); Sex.call(this, props);this.grade = props.grade;}var s1 = new Student({name:'zs',grade:123,sex:'男'});console.log(s1.name); // zsconsole.log(s1.grade); // 123console.log(s1.sex); // 男
4.4.3 原型链详解
function Person(props){this.name = props.name;}Person.prototype.say = function(){console.log("人都会说话的");}function Student(props){Person.call(this, props); this.grade = props.grade;}var s1 = new Student({name:'zs',grade:123});console.log(s1);s1.say(); // TypeError: s1.say is not a functionconsole.log(s1.__proto__ == Student.prototype)// trueconsole.log(s1.__proto__.__proto__ == Object.prototype)// trueconsole.log(s1.__proto__.__proto__.__proto__ == null) // true
为什么出现这种报错呢?
s1 ------> Student.prototype -------> Object.prototype --------> null
这是什么鸟继承?只是继承了属性,但是原型链没有经过Person.prototype,所以会报错。
正确的原型链应该是这样的:
s1 ------> Student.prototype -------> Person.prototype ------> Object.prototype --------> null
Student.prototype = Person.prototype这样是不行的,因为Student和Person共享一个原型对象,那么创建两个类也没有意义。
4.4.4 通过“中间人”链接原型链
function Person(props){this.name = props.name;}Person.prototype.say = function(){console.log("人都会说话的");}function Mid(){}function Student(props){Person.call(this, props); this.grade = props.grade;}Mid.prototype = Person.prototype; // 把Mid的原型指向 Person.prototypeStudent.prototype = new Mid() ; // 把Student的原型指向一个Mid对象, Mid对象的原型正好指向Person.prototype Student.prototype.constructor = Student; // 把Student的原型的构造函数恢复成Studentvar s1 = new Student({name:'zs',grade:123});console.log(s1); s1.say(); //人都会说话的console.log(s1.__proto__ == Student.prototype)// trueconsole.log(s1.__proto__.__proto__ == Person.prototype)// trueconsole.log(s1.__proto__.__proto__.__proto__ == Object.prototype) // true
4.5 class语法糖
使用class创建类
'use strict';class Person {constructor (name){this.name = name;}say() {console.log(this.name + " is saying !");}}var p1 = new Person("正常人");p1.say() // 正常人 is saying !
4.5.1 class实现继承
ES6的新特性通过class实现继承,class继承的方式可以减少编写原型链的代码。但是前提是浏览器要支持ES6.
'use strict';class Person {constructor (name){this.name = name;}say() {console.log(this.name + " is saying !");}}class Student extends Person{constructor(name,grade){super(name);this.grade = grade;}study() {console.log(this.grade+"年级的学生就应该学习");}}var p1 = new Student("学生甲乙丙丁", 4);p1.say(); //学生甲乙丙丁 is saying ! p1.study(); // 4年级的学生就应该学习