✊不积跬步,无以至千里;不积小流,无以成江海
JS数据类型有哪些?
一共八种。四基两空一对象+一大整数。
字符串、数字、布尔、undefined、null、大整数、符号、对象.
string、number、boolean、undefined、null、bigint、symbol、object.
->为什么会有bigint,因为number默认是双精度浮点数,但bigint则没有精度上的限制。
原型链是什么?
是什么
原型链涉及到的概念挺多的。首先我先举一个原型的例子吧。
假设我们有一个普通对象 x={} ,这个 x 会有一个隐藏属性,叫做 __?????__,这个属性会指向Object.prototype ,即
x.__?????__ === Object.prototype // 原型
此时,我们说x的原型是object.prototype;或者说object.prototype是x的原型。
而这个__?????__ 的唯一作用就是用来指向原型的。没有这个__?????__ x就会根本不知道自己的原型是谁。
接下来我们说一下原型链,还是用例子来说明。
假设我们有一个数组对象 a=[] ,这个 a 也会有一个隐藏属性,叫做 __?????__,这个属性会指向 Array.prototype ,即
Array.__?????__ === Array.prototype // 原型
此时,我们说 a 的原型是 Array.prototype ,跟上面的 x 一样。但又有一点不一样,那就是 Array.prototype 也有一个隐藏属性 __?????__,指向 Object.prototype ,即
Array.prototype.__?????__ === Object.prototype // 原型
这样一来,a 就有两层原型:
-
a 的原型是 Array.prototype
-
a 的原型的原型是 Object.prototype
于是就通过隐藏属性 __?????__形成了一个链条:
a ===> Array.prototype ===> Object.prototype
这就是原型链。
怎么做
看起来只要改写 x 的隐藏属性 __?????__ 就可以改变 x 的原型(链)
x.__?????__ = 我想要篡改成东西
但是这不是标准推荐的写法,推荐的写法是
const x = Object.create(我想要篡改成东西)//用object create
// 或
const x = new 构造函数() // 用 new,会令 x.__?????__ === 构造函数.prototype
这两种。
解决了什么问题
在没有 Class 的情况下实现「继承」。以 a ===> Array.prototype ===>Object.prototype 为例,我们说:
-
a 是 Array 的实例,a 拥有 Array.prototype 里的属性
-
Array 又继承了 Object
-
a 是 Object 对象的间接实例,a 拥有 Object.prototype 对象里的属性
这样一来,a 就既拥有 Array.prototype 数组里的属性,又拥有 Object.prototype 对象里的属性。
优点:
简单、省事。
缺点:
跟 class 相比,不支持私有属性。
怎么解决缺点:
使用 class 呗。
这段代码中的 this 是多少?
var length = 4;
function callback() {
console.log(this.length); // => 打印出什么?
}
const obj = {
length: 5,
method(callback) {
callback();
}
};
obj.method(callback, 1, 2);
this实际上等价于
正常调用形式:
func.call(context, p1, p2)
其他两种都是语法糖,可以等价地变为 call 形式:
func(p1, p2) 等价于
func.call(undefined, p1, p2)
obj.child.method(p1, p2) 等价于
obj.child.method.call(obj.child, p1, p2)
如果没有传值,则this 就是 undefined。但浏览器给你一个默认的 this —— window 对象,所以打印出的 this 是 window。
解题思路
在这个题中,callback进行了一次传递(21行);一次参数(13行);一次调用(15行)。只需要关注最后一次执行时是什么,发现是(15行)。
则第15行可以被改写为,callback.call(undefine)。那么this.length = undefine.length;这种情况下应该打印出来的是window中的内容,即为window.length。
由于js的语法特性,全局变量自动变为window的属性。则var length = 4; 可以转换为window.length = 4.
因此this打印出来的是4.
JS 的 new 做了什么?
-
创建临时对象/新对象
-
绑定原型
-
指定 this = 临时对象
-
执行构造函数
-
返回临时对象
什么是立即执行函数
是什么:
声明一个匿名函数,然后立即执行它。这种做法就是立即执行函数。
怎么做:
//实际版的立即执行函数,function一个函数再调用这个函数:
fuction a(){
console.log('hi')
}
a()
//去掉a后即为立即执行函数
//但因为这个值return的是undefined,所以没法[3].map这类的
(function(){alert('我是匿名函数')} ()) // 用括号把整个表达式包起来
(function(){alert('我是匿名函数')}) () // 用括号把函数包起来
!function(){alert('我是匿名函数')}() // 求反,我们不在意值是多少,只想通过语法
+function(){alert('我是匿名函数')}()
-function(){alert('我是匿名函数')}()
~function(){alert('我是匿名函数')}()
void function(){alert('我是匿名函数')}()
new function(){alert('我是匿名函数')}()
var x = function(){return '我是匿名函数'}()
解决了什么问题:
在 ES6 之前,只能通过它来「创建局部作用域」。
优点:
兼容性好。
缺点:
语法不美观。
如何解决缺点:
用es6的block+let。
什么是闭包
是什么
闭包是 JS 的一种语法特性。
闭包 = 函数 + 自由变量
怎么做
//声明一个立即执行函数,在立即执行函数中间声明一个变量,并在立即函数中调用这个变量
const add2 = function (){
var count
return function add (){ // 访问了外部变量的函数
count += 1
}
}()
解决了什么问题:
-
避免污染全局环境。(因为用的是局部变量)
-
提供对局部变量的间接访问。(因为只能 count += 1 不能 count -= 1)
-
维持变量,使其不被垃圾回收。
优点:
简单,好用。
缺点:
闭包使用不当可能造成内存泄露。
怎么解决缺点:
慎用,少用,不用。
如何实现类
方法一:使用原型
//如果属性不能共享,就放在函数里面
function Dog(name){
this.name = name
this.legsNumber = 4
}
//如果能共享,就放在prototype里面
Dog.prototype.kind ='狗'
Dog.prototype.say = function(){console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)}
Dog.prototype.run = function(){console.log(`${this.legsNumber}条腿跑起来。`)}
const d1 = new Dog('啸天') // Dog 函数就是一个类
d1.say()
方法二:使用 class
class Dog {
kind = '狗' // 等价于在 constructor 里写 this.kind = '狗',代表他们是原型上的属性
//所有狗都有的属性放在这里
constructor(name) {
this.name = name
this.legsNumber = 4
// 思考:kind 放在哪,放在哪都无法实现上面的一样的效果
}
//say & run 狗的共享属性
say(){console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}
run(){console.log(`${this.legsNumber}条腿跑起来。`)}
}
const d1 = new Dog('啸天')
d1.say()
如何实现继承
方法:使用原型链
继承的本质就是将两个类联合在一起。
首先定义两个类,animal类,有自身属性 & 共有属性;dog有自身属性以及一个原型say
function Animal(legsNumber){
this.legsNumber = legsNumber //自身的
}
Animal.prototype.kind = '动物' //共有的
function Dog(name){
this.name = name //自身的
this.legsNumber = 4
}
Dog.prototype.say = function(){
console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}
const d1 = new Dog('啸天') // Dog 函数就是一个类
console.dir(d1)
在dog中继承animal的自身属性,只需要加call.this。
如果dog想要继承animal的原型,也只需要让A原型的下划线prototype = B原型即可
...
function Dog(name){
...
Animal.call(this, 4) // 调用一下animal,相当于实现了this.legsNumber = 4
}
Dog.prototype.__proto__= Animal.prototype
但由于下划线这种写法会被浏览器判定为具有误导性,所以要用新的写法来让dog能够继承animal的原型,方法如下:
...
var f = function(){ } //声明一个空的函数,这样空的部分就可以替代构造函数
f.prototype = Animal.prototype //则可与实现代替animal的prototype,做到了new的第三步
Dog.prototype = new f() //new一下执行new的五个操作,但第四个执行构造函数因为是空的所以其实相当于没有实行
补充:new的几个操作
1. 创建临时对象
2,this = 临时对象
3. this._proto_ = 构造函数proto
4. 执行构造函数
5. return this
所以最终代码如下:
function Animal(legsNumber){
this.legsNumber = legsNumber //自身的
}
Animal.prototype.kind = '动物' //共有的
function Dog(name){
this.name = name //自身的
Animal.call(this, 4) // 关键代码1,调用一下animal,相当于实现了this.legsNumber = 4
}
//Dog.prototype.__proto__= Animal.prototype // 关键代码2,狗的原型的原型=animal的原型,但容易被ban,如何解决在下面
var f = function(){ } //声明一个空的函数
f.prototype = Animal.prototype //代替animal的prototype
Dog.prototype = new f() //再new一下执行new的五个操作,但第四个执行构造函数因为是空的所以其实相当于没有实行
Dog.prototype.kind = '狗' //共有的
Dog.prototype.say = function(){
console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}
const d1 = new Dog('啸天') // Dog 函数就是一个类
console.dir(d1)