# 变量类型
## 6种原始类型\(值类型\):
1. boolean
2. string
3. number
4. **null**
5. undefined
6. symbol \(es6\)
**注意**
原始类型存储的都是值,是不能调用方法的,像 "1".valueOf() 能调用是因为:这里的 "1" 会被自动装箱为 String (包装类)。
null 不是 Object,是原始类型,typeof null 会输出 object 是因为js的bug
undefined: 没有赋值变量的默认值,自动赋值
null:表示一个变量不再指向任何对象地址
## 引用类型
在 JS 中,除了原始类型那么其他的都是对象类型了。对象类型和原始类型不同的是,原始类型存储的是值,对象类型存储的是地址(指针)。当你创建了一个对象类型的时候,计算机会在内存中帮我们开辟一个空间来存放值,但是我们需要找到这个空间,这个空间会拥有一个地址(指针)
```
function test(person) {
person.age = 26
person = {
name: 'yyy',
age: 30
}
return person
}
const p1 = {
name: 'yck',
age: 25
}
const p2 = test(p1)
console.log(p1) // -> ?
console.log(p2) // -> ?
```
* test\(p1\) 将p1的对象地址当作函数参数
* person.age = 26 会将对象p1的age也修改
* person = {name: 'yyy',age: 30} person对象的地址重新指向新对象,和p1不在绑定
* const p2 = test\(p1\) 将方法里person的地址传递给p2
按值传递的类型,复制一份存入栈内存,这类类型一般不占用太多内存,而且按值传递保证了其访问速度。按共享传递的类型,是复制其引用,而不是整个复制其值(C 语言中的指针),保证过大的对象等不会因为不停复制内容而造成内存的浪费
## typeof
能检测出七种类型,值类型中null的结果是object,引用类型中出来函数显示为Function,其他都是object,所以能检测出七种(5个值类型对应的+1个object+1个Function。typeof 并不能准确判断对象变量到底是什么类型
| type | 结果 |
| :--- | :--- |
| typeof 123 | number |
| typeof '123' | string |
| typeof true | boolean |
| typeof undefined | undefined |
| typeof Symbol\(\) | symbol |
| typeof function | Function |
| typeof null | object |
| typeof \[1,2\] | object |
| typeof {} | object |
## instanceof
instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置
```
[1, 2] instanceof Array
const Person = function() {}
const p1 = new Person()
p1 instanceof Person
```
instanceof更多是用来检测自定义对象
## 判断对象变量到底是什么类型的方法
```
function is(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas === type;
}
is('String', 'test'); // true
is('String', new String('test')); // true
Object.prototype.toString 返回一种标准格式字符串,所以上例可以通过 slice 截取指定位置的字符串,如下所示:
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call(2) // "[object Number]"
```
# 类型转换
![](../assets/js基础知识/类型转换.jpg)
# this
## this的绑定规则:
1. 在非严格模式下,默认绑定的this指向全局对象,严格模式下this指向undefined
2. 函数调用 foo\(\) 这里的this也会指向全局对象
3. test.foo\(\) this指向test对象, 指向调用者
4. new foo\(\) 在函数内部,this 指向新创建的对象。
5. 当使用 Function.prototype 上的 call 或者 apply 方法时,函数内的 this 将会被 显式设置为函数调用的第一个参数。
```
function a() {
return () => {
return () => {
console.log(this)
}
}
}
console.log(a()()())
```
首先箭头函数其实是没有 this 的,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this。在这个例子中,因为包裹箭头函数的第一个普通函数是 a,所以此时的 this 是 window。另外对箭头函数使用 bind 这类函数是无效的。
```
let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => ?
```
可以从上述代码中发现,不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以结果永远是 window。
```
var foo=function(m,n){
console.log(n);
return {
foo:function(o){
console.log(o);
return foo(o,m);
}
}
}
//问题一:
var result=foo(1);
result.foo(2);
result.foo(3);
result.foo(4);
//问题二:
var result=foo(2).foo(3).foo(4).foo(5);
//问题三:
var result=foo(1);
result.foo(2).foo(3);
result.foo(4).foo(5);
//问题一二三分别输出什么? 说出执行步骤
```
# == 和 ===
== 如果对比双方的类型不一样的话,就会进行类型转换
![](../assets/js基础知识/==规则.jpg)
```
[] == ![] //
```
=== 就是判断两者类型和值是否相同
true, ! 的优先级比 == 大,所以 !\[\] 转为1 即 \[\] == 1 ,对象和number判断会将对象转为基本类型,即 1==1 结果为true
# let var const
let 、const与var的区别
1. 变量不会提示,块级作用域,使用前需要先定义,如果在声明之前访问这些变量,会导致报错
2. 重复声明报错
3. 不绑定全局作用域,全局作用域中,let 声明的变量不会挂在window上,var声明的会
let和const的区别
const 用于声明常量,其值一旦被设定不能再被修改,否则会报错
# 闭包
## 什么是闭包
《JavaScript高级程序设计》这样描述:
```
闭包是指有权访问另一个函数作用域中的变量的函数;
```
《JavaScript权威指南》这样描述:
```
从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。
```
《你不知道的JavaScript》这样描述:
```
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
```
```
function fn1() {
var name = 'iceman';
function fn2() {
console.log(name);
}
return fn2;
}
var fn3 = fn1();
fn3();
```
这样就清晰地展示了闭包:
自由变量将从作用域链中去寻找,但是 依据的是**函数定义时的作用域链,而不是函数执行时**,
1. fn2的词法作用域能访问fn1的作用域
2. 将fn2当做一个值返回
3. fn1执行后,将fn2的引用赋值给fn3
4. 执行fn3,输出了变量name
## 循环中使用闭包解决 `var` 定义函数的问题
for \(var i = 1; i <= 5; i++\) {
\(function\(j\) {
setTimeout\(function timer\(\) {
console.log\(j\)
}, j \* 1000\)
}\)\(i\)
}
## 闭包优缺点
优点:
1. 因为在闭包内部保持了对外部活动对象的访问,但外部的变量却无法直接访问内部,避免了全局污染;
2. 可以当做私有成员,弥补了因js语法带来的面向对象编程的不足;
3. 可以长久的在内存中保存一个自己想要保存的变量.
缺点:
1. 可能导致内存占用过多,因为闭包携带了自身的函数作用域
2. 闭包只能取得外部包含函数中得最后一个值
# 深浅拷贝
## 浅拷贝
对象类型在赋值的过程中其实是复制了地址,从而会导致改变了对象里的数据,其他也都会被改变,且不会拷贝对象的内部的子对象。
## 深拷贝
JSON.parse\(JSON.stringify\(object\)\)
1. 会忽略 undefined
2. 会忽略 symbol
3. 不能序列化函数
4. 不能解决循环引用的对象
# 原型
* 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(null除外)
* 所有的引用类型(数组、对象、函数),都有一个**proto**属性,属性值是一个普通的对象
* 所有的函数,都有一个prototype属性,属性值也是一个普通的对象
* 所有的引用类型(数组、对象、函数),**proto**属性值指向它的构造函数的prototype属性值
创建原型的三种方式:
第一种:字面量
```
var o1 = {name: '01}
var o2 = new Object({name: 'o2'});
```
第二种:构造函数
```
function Person(name) {this.name = name;}
var o3 = new M('o3');
```
第三种:Object.create
```
var p = {name: 'p'};
var o4 = Object.create(p);
```
# 原型链
当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的**proto**(即它的构造函数的prototype)中寻找,没有找到,还会继续往上找。这样一直往上找,你会发现是一个链式的结构,所以叫做“原型链”。如果一直找到最上层都没有找到,那么就宣告失败,返回undefined。最上层是什么 —— Object.prototype.**proto** === null
![](../assets/js基础知识/原型链1.png)
![](../assets/js基础知识/原型链.jpg)
# new 运算符
new 运算符实现原理
![](../assets/js基础知识/new运算符原理.png)
# 继承
## 构造函数继承
原理: 将父级的构造函数this指向到子类构造函数this上面
```
function Person() {
this.name = "person";
}
function Child() {
Person.call(this);
this.age = 22;
}
```
缺点: 父级的原型链上的变量和方法无法继承过来
## 原型链继承
原理: 子类的原型(prototype)等于父类实例
```
function Person() {
this.name = "person";
}
function Child() {
this.age = 22;
}
Child.prototype = Person.prototype;
```
缺点: 原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性 也还可以。但如果属性包含引用类型就会导致改变一个就是改变所有。
## 组合继承
原理: 不共享的使用构造函数,共享的使用原型链,子类的原型等于Object.create\(父类原型\),同时覆盖子类原型的construct 。(使用new Person \(\) 会造成父类构造函数执行两遍。不能将父类的原型链(prototype)直接赋值给子类,会造成实例无法区分是使用子类还是父类创建的,因为是同一个prototype。Object.create\(\) 会创建一个中间对象,将父类和子类隔离)
```
function Person() {
this.name = "person";
}
function Child() {
Person.call(this);
this.age = 22;
}
Child.prototype = Object.create(Person.prototype);
Child.prototype.construct = Child;
```
[mdn for...of](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for...of)
[mdn for...in](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for...in)
# for...of
for...of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
[可迭代协议](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols)
为了变成可迭代对象, 一个对象必须实现 @@iterator 方法, 意思是这个对象(或者它原型链 prototype chain 上的某个对象)必须有一个名字是 Symbol.iterator 的属性
# for...in
for...in语句以任意顺序遍历一个对象的可枚举属性。对于每个不同的属性,语句都会被执行。
**for...in不应该用于迭代一个索引顺序很重要的 Array**
# for...of与for...in的区别
1. for...of不能循环普通object对象({key:value}),
2. for...in循环对象时循环出的是key,循环数组时循环出的是索引,for...of循环出的是value
3. for...in 能够循环出原型和原型链继承下来的属性,可在循环体中使用hasOwnProperty过滤
4. for...in循环除了遍历数组元素以外,还会遍历自定义对象属性(下方例子的iterable.foo = 'hello';)
5. for...of 循环数组只循环本身元素,不会出现3和4的情况
无论是for...in还是for...of语句都是迭代一些东西。它们之间的主要区别在于它们的迭代方式。
for...in 语句以原始插入顺序迭代对象的可枚举属性。
for...of 语句遍历可迭代对象定义要迭代的数据
以下示例显示了与Array一起使用时,for...of循环和for...in循环之间的区别。
```
Object.prototype.objCustom = function() {};
Array.prototype.arrCustom = function() {};
let iterable = [3, 5, 7];
iterable.foo = 'hello';
for (let i in iterable) {
// 0, 1, 2 是索引
console.log(i); // 0, 1, 2, "foo", "arrCustom", "objCustom"
}
for (let i of iterable) {
console.log(i); // 3, 5, 7
}
for (let i in iterable) {
// 过滤掉继承的属性
if (iterable.hasOwnProperty(i)) {
console.log(i); // 0, 1, 2, "foo"
}
}
```
[掘金冴羽for...of](https://juejin.im/post/5b444268f265da0f98313d26)