JS知识补充
JS基本介绍
JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。 这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升
JavaScript 是一种动态类型语言,也就是说,变量的类型没有限制,变量可以随时更改类型。
var a = 1;
a = 'hello';
js数据类型
JavaScript 基本数据类型:
-
undefined
-
null
-
string
-
number
-
boolean
堆和栈都是内存中划分出来用来存储的区域。 栈(stack)为自动分配的内存空间,它由系统自动释放; 而堆(heap)则是动态分配的内存,大小不定也不会自动释放。
基本数据类型存放在栈
,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。
基本数据类型值不可变(不太清楚)
基本数据类型的比较是值得比较
var a = 1;
var b = true;
console.log(a == b);//true
引用类型(object)是存放在堆内存中的
,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配,引用类型值可变
var person1 = {name:'jozo'};
var person2 = {name:'xiaom'};
var person3 = {name:'xiaoq'};
引用类型的比较是引用的比较
var a = [1,2,3];
var b = [1,2,3];
console.log(a === b); // false
布尔值转换:
如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为false,其他值都视为true。
- undefined
- null
- false
- 0
- NaN
- " "或’ ’
注意,空数组([])和空对象({})对应的布尔值,都是true。
Math
parseInt()
使用内置函数 parseInt() 将字符串转换为整型。如果字符串头部有空格,空格会被自动去除。如果parseInt的参数不是字符串,则会先转为字符串再转换。
parseInt(' 81') // 81
parseInt(1.23) // 1
// 等同于
parseInt('1.23') // 1
NaN(Not a Number 的缩写)
变量
在 JavaScript 中声明一个新变量的方法是使用关键字 let 、const 和 var:
- let 语句声明一个块级作用域的本地变量,并且可选的将其初始化为一个值。
- const 允许声明一个不可变的常量。
- var 是最常见的声明变量的关键字。它没有其他两个关键字的种种限制。这是因为它是传统上在 JavaScript 声明变量的唯一方法。使用 var 声明的变量在它所声明的整个函数都是可见的。
如果声明了一个变量却没有对其赋值,那么这个变量的类型就是 undefined
运算符
自增和自减运算符:++/–
运算之后,变量的值发生变化,这种效应叫做运算的副作用(side effect)。自增和自减运算符是仅有的两个具有副作用的运算符,其他运算符都不会改变变量的值。放在变量之后,会先返回变量操作前的值,再进行自增/自减操作;放在变量之前,会先进行自增/自减操作,再返回变量操作后的值。
var x = 1;
var y = 1;
x++ // 1
++y // 2
- +=,-=
x += 5; // 等价于 x = x + 5;
指数运算符:
指数运算符是右结合,而不是左结合。即多个指数运算符连用时,先进行最右边的计算。
// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512
- 一个字符串加上一个数字(或其他值),那么操作数都会被首先转换为字符串。 加法运算符是在运行时决定,到底是执行相加,还是执行连接。也就是说,运算子的不同,导致了不同的语法行为,这种现象称为“重载”(overload)。由于加法运算符存在重载,可能执行两种运算,使用的时候必须很小心。
"3" + 4 + 5; // 345
3 + 4 + "5"; // 75
技巧——通过与空字符串相加,可以将某个变量快速转换成字符串类型。
- 相等的比较稍微复杂一些。由两个“=(等号)”组成的相等运算符有类型自适应的功能
123 == "123" // true
1 == true; // true
如果在比较前不需要自动类型转换,应该使用由三个“=(等号)”组成的相等运算符:
1 === true; //false
123 === "123"; // false
!= 和 !== 两种不等运算符,具体区别与两种相等运算符的区别类似。
取反运算符
如果对一个值连续做两次取反运算,等于将其转为对应的布尔值,与Boolean函数
的作用相同。这是一种常用的类型转换的写法。
!!x
// 等同于
Boolean(x)
- && 和 || 运算符使用短路逻辑(short-circuit logic),是否会执行第二个语句(操作数)取决于第一个操作数的结果。在需要访问某个对象的属性时,使用这个特性可以事先检测该对象是否为空:
var name = o && o.getName();
或用于缓存值(当错误值无效时):
var name = cachedName || (cachedName = getName());
当作if语句使用
if (i) {
doSomething();
}
// 等价于
i && doSomething();
只通过第一个表达式的值,控制是否运行第二个表达式的机制,就称为“短路”(short-cut)
。
var x = 1;
true || (x = 2) // true
x // 1
对象
- 创建一个对象原型,Person,和这个原型的实例,You
function Person(name, age) {
this.name = name;
this.age = age;
}
// 定义一个对象
var You = new Person("You", 24);
// 我们创建了一个新的 Person,名称是 "You"
// ("You" 是第一个参数, 24 是第二个参数..)
对象的引用
如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。
var o1 = {};
var o2 = o1;
o1.a = 1;
o2.a // 1
o2.b = 2;
o1.b // 2
此时,如果取消某一个变量对于原对象的引用,不会影响到另一个变量。
var o1 = {};
var o2 = o1;
o1 = 1;
o2 // {}
上面代码中,o1和o2指向同一个对象,然后o1的值变为1,这时不会对o2产生影响,o2还是指向原来的那个对象。但是,这种引用只局限于对象,如果两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝。
属性的读取
读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。
如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理。
var foo = 'bar';
var obj = {
foo: 1,
bar: 2
};
obj.foo // 1
obj[foo] // 2
var obj = {
123: 'hello world'
};
obj.123 // 报错
obj[123] // "hello world"
深拷贝
在深拷贝中,新对象中的更改不会影响原始对象,而在浅拷贝中,新对象中的更改,原始对象中也会跟着改。
默认情况下对象之间的直接赋值都是浅拷贝
数组
- Array.length 并不总是等于数组中元素的个数,如下所示:
var a = ["dog", "cat", "hen"];
a[100] = "fox";
a.length; // 101
- 数组方法
- a.pop():删除并返回数组中的最后一个元素
- a.shift():删除并返回数组中第一个元素。
- a.push(item1, …, itemN): 将 item1、item2、……、itemN 追加至数组 a。
- a.slice(start, end): 切割数组,返回子数组,以 a[start] 开头,以 a[end] 前一个元素结尾。
- .splice(index,howmany,item1,…,itemX):
参数 | 描述 |
---|---|
index | 必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。 |
howmany | 必需。要删除的项目数量。如果设置为 0,则不会删除项目 |
item | 可选。向数组添加的新项目。 |
函数
- 函数参数,实际上是访问了函数体中一个名为 arguments 的内部对象,这个对象就如同一个类似于数组的对象一样,包括了所有被传入的参数。下面函数,它可以接收任意个数的参数:
function add() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum;
}
add(2, 3, 4, 5); // 14
可以使用剩余参数来替换arguments的使用。剩余参数操作符在函数中以:…variable 的形式被使用,它将包含在调用函数时使用的未捕获整个参数列表到这个变量中。
function avg(...args) {
var sum = 0;
for (let value of args) {
sum += value;
}
return sum / args.length;
}
avg(2, 3, 4, 5); // 3.5
采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。
var print = function x(){
console.log(typeof x);
};
x
// ReferenceError: x is not defined
print()
// function
传值方式
函数参数果是原始类型的(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
变量提升
函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
function foo(x) {
if (x > 100) {
var tmp = x - 100;
}
}
// 等同于
function foo(x) {
var tmp;
if (x > 100) {
tmp = x - 100;
};
}
函数本身作用域
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
闭包
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
闭包就是函数f2,即能够读取其他函数内部变量的函数。由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。
数据类型转换
JavaScript 是一种动态类型语言,变量没有类型限制,可以随时赋予任意值。
虽然变量的数据类型是不确定的,但是各种运算符对数据类型是有要求的。如果运算符发现,运算子的类型与预期不符,就会自动转换类型。
强制转换
强制转换主要指使用Number()、String()和Boolean()
三个函数,手动将各种类型的值,分别转换成数字、字符串或者布尔值。
Number()要比parseInt函数严格,只要有一个字符无法转成数值,整个字符串就会被转为NaN
自动转换
第一种情况,不同类型的数据互相运算。
第二种情况,对非布尔值类型的数据求布尔值
第三种情况,对非数值类型的值使用一元运算符(即+和-)
this关键字
JavaScript 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this就是函数运行时所在的对象(环境)。 JavaScript 支持运行环境动态切换
,也就是说,this的指向是动态的,没有办法事先确定到底指向哪个对象。
绑定this
call()
:第一个参数就是this所要指向的那个对象,后面的参数则是函数调用时所需的参数。
func.call(thisValue, arg1, arg2, ...)
apply()与call类似,区别是它接收一个数组作为函数执行时的参数,使用格式如下。
func.apply(thisValue, [arg1, arg2, ...])
小技巧找出数组最大的元素:
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15
bind()
方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。
对象的继承
prototype原型对象
的作用:
构造函数默认都会有prototype原型对象,构造函数生成的实例可以共享prototype上的属性和方法。
当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。
如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
原型链
:就是对象到原型,再到原型的原型……所有对象的原型最终都可以上溯到Object.prototype
,也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOf和toString方法的原因,因为这是从Object.prototype继承的。
constructor
默认指向prototype对象所在的构造函数
,定义在prototype对象上面,可以被所有实例对象继承。constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。
instanceof运算符
返回一个布尔值,表示对象是否为某个构造函数的实例。由于instanceof检查整个原型链
,因此同一个实例对象,可能会对多个构造函数都返回true。
构造函数的继承
举个栗子,有个父类构造函数Shape:
function Shape() {
this.x = 0;
this.y = 0;
}
Shape.prototype.move = function (x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
让Rectangle构造函数继承Shape:
function Shape() {
this.x = 0;
this.y = 0;
}
Shape.prototype.move = function (x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
单个方法的继承,这时可以采用下面的写法。
ClassB.prototype.print = function() {
ClassA.prototype.print.call(this);
// some code
}
未完待续