世界上最流行的脚本-JavaScript,看过不会来找我

摘要

下面文章中使用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有很多设计缺陷。

2015年ES6标准发布

2 基础

2.1 嵌入网页

方式1

JS脚本可以被嵌入在网页的各个部分,使用标签,一般放在body或者head中

方式2 直接引入文件

<script src="a.js"><script>

IDE 推荐

  • VScode
  • Sublime
  • Notepad++
  • HbuilderX

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; // 整数123
0.456; // 浮点数0.456
1.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
		多行文本输出
			`;

布尔

true
false

布尔运算符多用于条件判断,也可以进行逻辑运算,逻辑与&&,逻辑或||,逻辑非!

console.log(true && false); // false
console.log(true || false); // true
console.log(!true);         // false
var age = 15;
if(age>18){
	console.log("成年人");
}
else{
	console.log("未成年人"); // 未成年人
}

JS中的相等

<script>
    console.log(null === null); // true
	console.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); // false
	console.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]); // 超过下标也不会报错,返沪undefined
    arr.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)); //4
    console.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]); // 5
    console.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);  // undefined
			console.log(xiaoming.birth); // 1994
			console.log(xiaoming['weight']); // 78
			xiaoming.xixi = 'haha';
			console.log(xiaoming.xixi); // haha
  • 即使对象没有这个属性,也不会报错
  • 可以创建对象后,动态设置属性,这是动态语言所特殊的
			console.log("xixi" in xiaoming);     // true
			console.log("toString" in xiaoming); // true
			
			console.log(xiaoming.hasOwnProperty('xixi'));  // true
			console.log(xiaoming.hasOwnProperty('age'));   // false
			
			delete 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('语文')); // 78
    			console.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 选择结构

nullundefined0NaN和空字符串''视为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);  // 1
    			
    			
    			while(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可以采用下标循环,遍历MapSet就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable类型,ArrayMapSet都属于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); // NaN

result = 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); // 1
	console.log(b); // 2
	console.log(rest); // (4) [3, 4, 5, 6]
}

test(1,2,3,4,5,6);

小心return

var test = function (a, b, ...rest){
	console.log(a); // 1
	console.log(b); // 2
	console.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();  // 10
	console.log(b);  // ReferenceError: b is not defined
}


outer();

内外部函数具有相同名称的变量,内部函数会覆盖外部函数

function outer(){
	var a = 10;
	function inner(){
		var a = 11;
		console.log(a);
	}
	inner();  // 11
	console.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 defined
	let 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); // NaN
	var 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');

// 全局window

var 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); // hello
console.log(y); // china
console.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); //kobe
    console.log(age);  // 11
    run();  // 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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-odeYPvd4-1636016576605)(C:\Users\z004abwh\AppData\Roaming\Typora\typora-user-images\image-20211104151108745.png)]

对象的原型链

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PFHCdLPf-1636016576606)(C:\Users\z004abwh\AppData\Roaming\Typora\typora-user-images\image-20211104152436251.png)]

如果把constructor: Player这句话注释掉,结果如下:

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q8G8KMaM-1636016576608)(C:\Users\z004abwh\AppData\Roaming\Typora\typora-user-images\image-20211104163927922.png)]

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); // zs
console.log(s1.grade); // 123
console.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 function
console.log(s1.__proto__ == Student.prototype)               // true
console.log(s1.__proto__.__proto__ == Object.prototype)      // true
console.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.prototype
Student.prototype = new Mid() ;    // 把Student的原型指向一个Mid对象, Mid对象的原型正好指向Person.prototype 
Student.prototype.constructor = Student;  // 把Student的原型的构造函数恢复成Student


var s1 = new Student({
	name:'zs',
	grade:123
});



console.log(s1); 
s1.say();  //人都会说话的
console.log(s1.__proto__ == Student.prototype)               // true
console.log(s1.__proto__.__proto__ == Person.prototype)      // true
console.log(s1.__proto__.__proto__.__proto__ == Object.prototype)        // true

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xVWGsjwA-1636016576608)(C:\Users\z004abwh\AppData\Roaming\Typora\typora-user-images\image-20211104165043702.png)]

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年级的学生就应该学习

  • 42
    点赞
  • 344
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
### 回答1: 这个错误的意思是 JavaScript 堆内存不足。这意味着你的程序尝试使用更多内存,但是电脑上可用的内存不足以满足需求。 这种情况通常发生在你的程序中存在内存泄露(memory leak)或者你的程序使用了过多的内存。 解决方法可能包括: - 寻并修复内存泄露 - 优化你的程序,减少内存使用 - 尝试使用更大的内存限制来运行你的程序(例如,使用 `node --max-old-space-size=4096 script.js` 运行你的程序) ### 回答2: FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript堆内存溢出。 这个错误通常在使用Node.js或其他JavaScript运行环境时出现,表明分配内存失败,导致JavaScript堆内存不足。 当我们的代码需要分配大量的内存时,JavaScript运行环境中的堆会被用来存储大量的变量、对象等数据结构。然而,如果我们的代码需要分配的内存超过了堆的限制,就会发生内存溢出的错误。 出现这个错误的原因可能有多种: 1. 代码中存在内存泄漏的情况,即没有正确释放不再使用的变量和对象,导致内存无法被回收。 2. 代码中执行了大量的操作,需要分配的内存超过了JavaScript堆的限制。 3. 代码中使用了递归调用,并且递归的深度太大,导致需要分配的内存超过了JavaScript堆的容量。 解决这个问题的方法有: 1. 优化代码,确保使用完的变量和对象能够被正确释放。尽量避免使用全局变量,及时清理不再需要的对象。 2. 如果代码需要处理大量的数据,可以考虑使用数据流的方式,避免一次性加载所有数据到内存中。 3. 增加JavaScript堆的容量。可以通过在运行时指定`node --max-old-space-size=<size>`来增加堆的大小,其中`<size>`表示以MB为单位的堆大小。 如果仍然无法解决问题,可以尝试将部分逻辑转移到其他语言实现,或者考虑使用更高效的数据处理方法。 ### 回答3: "FATAL ERROR: CALL_AND_RETRY_LAST 分配错误 - JavaScript 堆内存不足" 这个错误通常在使用Node.js执行内存密集型任务时出现。它意味着JavaScript的堆内存不足以容纳当前的操作,导致分配失败。 当JavaScript运行时无法分配足够的内存来处理程序的请求时,就会出现这个错误。原因可能是程序需要处理的数据量过大,或者存在内存泄漏等问题。 要解决这个问题,有几个方法可以尝试: 1. 调整Node.js的堆内存大小。可以通过在执行Node.js脚本时添加`--max-old-space-size`参数来增加内存限制。例如,`node --max-old-space-size=4096 script.js`将内存限制增加到4GB。根据需要进行调整。 2. 优化代码和算法,以减少内存使用量。检查代码是否存在内存泄漏或者不必要的数据复制等问题。避免频繁创建大对象或数组,尽量使用循环和迭代来处理数据。 3. 分批处理数据。如果程序需要处理的数据量过大,可以考虑将数据分批处理,减少每次处理的数据量,以便能够适应可用的内存限制。 4. 使用流式处理。对于需要读取或写入大文件的操作,使用流式处理而不是一次性加载文件到内存中。这样可以逐行或逐个数据块地处理数据,减少内存消耗。 5. 更新Node.js版本。有时,这个错误可能是Node.js版本的问题。尝试升级Node.js到最新版本,看是否可以解决问题。 通过以上方法中的一种或多种,可以尝试解决"FATAL ERROR: CALL_AND_RETRY_LAST 分配错误 - JavaScript 堆内存不足"的问题。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kobe_OKOK_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值