js基础知识点及面试题(二)
1.闭包
闭包的定义其实很简单:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。在 JS 中,闭包存在的意义就是让我们可以间接访问函数内部的变量。
function A() { let a = 1 window.B = function () { console.log(a) } } A() B() // 1
经典面试题
for (var i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i) }, i * 1000) }
因为 setTimeout
是个异步函数,所以会先把循环全部执行完毕,这时候 i
就是 6 了,所以会输出一堆 6
解决办法(1)使用闭包
for (var i = 1; i <= 5; i++) { ;(function(j) { setTimeout(function timer() { console.log(j) }, j * 1000) })(i) }
(1) 使用 setTimeout
的第三个参数,这个参数会被当成 timer
函数的参数传入
for (var i = 1; i <= 5; i++) { setTimeout( function timer(j) { console.log(j) }, i * 1000, i ) }
(3)使用 let
定义 i
了来解决问题了,这个也是最为推荐的方式
for (let i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i) }, i * 1000) }
2.深浅拷贝
(1)浅拷贝
首先可以通过 Object.assign
,或者展开运算符...来解决这个问题,Object.assign
只会拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,所以并不是深拷贝。
let a = { age: 1 } let b = Object.assign({}, a) a.age = 2 console.log(b.age) // 1
(2) 深拷贝
let a = { age: 1, jobs: { first: 'FE' } } let b = JSON.parse(JSON.stringify(a)) a.jobs.first = 'native' console.log(b.jobs.first) // FE
缺点:
- 会忽略
undefined
- 会忽略
symbol
- 不能序列化函数
- 不能解决循环引用的对象
let a = { age: undefined, sex: Symbol('male'), jobs: function() {}, name: 'yck' } let b = JSON.parse(JSON.stringify(a)) console.log(b) // {name: "yck"}
深拷贝上代码
function deepClone(o) { // 判断如果不是引用类型,直接返回数据即可 if (typeof o === 'string' || typeof o === 'number' || typeof o === 'boolean' || typeof o === 'undefined') { return o } else if (Array.isArray(o)) { // 如果是数组,则定义一个新数组,完成复制后返回 // 注意,这里判断数组不能用typeof,因为typeof Array 返回的是object console.log(typeof []) // --> object var _arr = [] o.forEach(item => { _arr.push(item) }) return _arr } else if (typeof o === 'object') { var _o = {} for (let key in o) { _o[key] = deepClone(o[key]) } return _o } } var arr = [1, 2, 3, 5] var cloneArr = deepClone(arr) console.log(cloneArr) // --> [ 1, 2, 3, 5 ]
3.原型及原型链
当我们创建一个对象时 let obj = { age: 25 }
,我们可以发现能使用很多种函数,但是我们明明没有定义过它们,
当我们在浏览器中打印 obj
时你会发现,在 obj
上居然还有一个 __proto__
属性
其实每个 JS 对象都有 __proto__
属性,这个属性指向了原型。这个属性在现在来说已经不推荐直接去使用它了,这只是浏览器在早期为了让我们访问到内部属性 [[prototype]]
来实现的一个东西。
原型也是一个对象,并且这个对象中包含了很多函数,所以我们可以得出一个结论:对于 obj
来说,可以通过 __proto__
找到一个原型对象,在该对象中定义了很多函数让我们来使用。
我们还可以发现一个 constructor
属性,也就是构造函数
打开 constructor
属性我们又可以发现其中还有一个 prototype
属性,并且这个属性对应的值和先前我们在 __proto__
中看到的一模一样。所以我们又可以得出一个结论:
原型的 constructor
属性指向构造函数,构造函数又通过 prototype
属性指回原型,但是并不是所有函数都具有这个属性,Function.prototype.bind()
就没有这个属性。
我再来解释下什么是原型链吧。
其实原型链就是多个对象通过 __proto__
的方式连接了起来。为什么 obj
可以访问到 valueOf
函数,就是因为 obj
通过原型链找到了 valueOf
函数。
小结
Object
是所有对象的爸爸,所有对象都可以通过__proto__
找到它Function
是所有函数的爸爸,所有函数都可以通过__proto__
找到它- 函数的
prototype
是一个对象 - 对象的
__proto__
属性指向原型,__proto__
将对象和原型连接起来组成了原型链
(1)普通对象与函数对象
JavaScript 中,万物皆对象!但对象也是有区别的。分为普通对象和函数对象,Object 、Function 是 JS 自带的函数对象。下面举例说明
var o1 = {}; var o2 =new Object(); var o3 = new f1(); function f1(){}; var f2 = function(){}; var f3 = new Function('str','console.log(str)'); console.log(typeof Object); //function console.log(typeof Function); //function console.log(typeof f1); //function console.log(typeof f2); //function console.log(typeof f3); //function console.log(typeof o1); //object console.log(typeof o2); //object console.log(typeof o3); //object在上面的例子中 o1 o2 o3 为普通对象,f1 f2 f3 为函数对象。怎么区分,其实很简单,凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。f1,f2,归根结底都是通过 new Function()的方式进行创建的。Function Object 也都是通过 New Function()创建的。
(2)构造函数
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function() { alert(this.name) } } var person1 = new Person('Zaxlct', 28, 'Software Engineer'); var person2 = new Person('Mick', 23, 'Doctor');
上面的例子中 person1 和 person2 都是 Person 的实例。这两个实例都有一个 constructor
(构造函数)属性,该属性(是一个指针)指向 Person
console.log(person1.constructor == Person); //true console.log(person2.constructor == Person); //true
(3)原型对象
在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype
属性,这个属性指向函数的原型对象。
function Person() {} Person.prototype.name = 'Zaxlct'; Person.prototype.age = 28; Person.prototype.job = 'Software Engineer'; Person.prototype.sayName = function() { alert(this.name); } var person1 = new Person(); person1.sayName(); // 'Zaxlct' var person2 = new Person(); person2.sayName(); // 'Zaxlct' console.log(person1.sayName == person2.sayName); //true
这里得到一个结论:每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性
Person.prototype = { name: 'Zaxlct', age: 28, job: 'Software Engineer', sayName: function() { alert(this.name); } }
原型对象就是 Person.prototype ,或者var A = Person.prototype
A 有一个默认的 constructor
属性,这个属性是一个指针,指向 Person。即:Person.prototype.constructor == Person
这两个公式貌似有点联系
person1.constructor == Person
Person.prototype.constructor == Person
结论:原型对象(Person.prototype)是 构造函数(Person)的一个实例。
原型对象是用来做什么的呢?主要作用是用于继承
var Person = function(name){ this.name = name; // tip: 当函数执行时这个 this 指的是谁? person1 }; Person.prototype.getName = function(){ return this.name; // tip: 当函数执行时这个 this 指的是谁? person1 } var person1 = new person('Mick'); person1.getName(); //Mick
通过给 Person.prototype
设置了一个函数对象的属性,那有 Person 的实例(person1)出来的普通对象就继承了这个属性。具体是怎么实现的继承,就要讲到下面的原型链了。、
(4)__proto__
JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__
的内置属性,用于指向创建它的构造函数的原型对象。
__proto__
属性,创建它的构造函数是 Person,构造函数的原型对象是 Person.prototype ,所以:person1.__proto__ == Person.prototype
我们可以得到
Person.prototype.constructor == Person; person1.__proto__ == Person.prototype; person1.constructor == Person;
(5)构造器
var obj = {}
它等同于下面这样:
var obj = new Object()
obj 是构造函数(Object)的一个实例。所以:
obj.constructor === Object
obj.__proto__ === Object.prototype
所以我们也可以构造函数来创建 Array、 Date、Function
var b = new Array(); b.constructor === Array; b.__proto__ === Array.prototype; var c = new Date(); c.constructor === Date; c.__proto__ === Date.prototype; var d = new Function(); d.constructor === Function; d.__proto__ === Function.prototype;
(6)原型链
person1.__proto__ 是什么? //Person.prototype Person.__proto__ 是什么? //Function.prototype Person.prototype.__proto__ 是什么? Object.__proto__ 是什么? Object.prototype__proto__ 是什么?第一题:
因为
person1.__proto__ === person1 的构造函数.prototype
因为
person1的构造函数 === Person
所以
person1.__proto__ === Person.prototype
第二题:
因为 Person.__proto__ === Person的构造函数.prototype
因为 Person的构造函数 === Function
所以 Person.__proto__ === Function.prototype
第三题:
Person.prototype
是一个普通对象,我们无需关注它有哪些属性,只要记住它是一个普通对象。
因为一个普通对象的构造函数 === Object
所以 Person.prototype.__proto__ === Object.prototype
第四题,参照第二题,因为 Person 和 Object 一样都是构造函数
第五题:
Object.prototype
对象也有proto属性,但它比较特殊,为 null 。因为 null 处于原型链的顶端,这个只能记住。
Object.prototype.__proto__ === null