JS的数据类型都哪些
一共八种:string——字符串、bool——布尔、number——数字、bigint——大整数、null、undefined、symbol——符号、object——对象。
一般情况下表示对象为空用null;非对象为空用undefined。
为什么需要bigint? js的number默认是双精度浮点数,精度有限。使用bigint则可以解决这个问题。(bigint只支持整数)
原型链
大概念题:答题思路为大概念分为小概念(分割),抽象画成具体(举例)
原型链涉及到的概念比较多,所以进行举例说明。
假设一个普通对象x={},这个x会有一个隐藏属性,叫做_proto_,这个属性会指向Object.prototype。即
x._proto_ === Object.prototype //原型
此时,我们可以说x的原型就是Object.prototype或者说Object.prototype是x的原型。
这个_proto_属性的唯一作用就是来指向x的原型的。如果没有_proto_那么x就不会知道自己的原型是谁。
接下来说一下原型链,同样也举例说明
假设我们有一个数组对象a=[],这个a也会有一个隐藏属性,叫做__proto__,这个属性会指向Array.prototype,即
a.__proto__ === Array.prototype
此时,我们可以说a的原型是Array.prototype,跟上面的x一样,但也不完全一样。因为Array.prototype的有一个隐藏属性,叫做__proto__,这个__proto__又指向了Object.prototype,即
//x表示 Array.prototype
x.__proto__ === Object.prototype
这样一来,a就有两层原型:
a的原型是Array.prototypea的原型的原型是Object.prototype
于是通过隐藏属性__proto__形成了一个链条
a ===> Array.prototype ===> Object.prototype
这就是原型链。 以上是我对原型链是什么的回答
怎么做:
看起来只要改写x的隐藏属性__proto__即可改变x的原型(链)
x.__proto__ = 原型
但这并不是标准推荐的写法,为了设置x.__proto__,推荐的写法是
cosnt x = Object.create(原型)
//或者
cosnt x = new 构造函数() //可以让x.__proto__ === 构造函数.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。但是class是ES6引入的,不被旧IE浏览器支持。
this问题
是什么:this是call的第一个参数
举个例子:
obj.child.method(p1,p2) 等价于
obj.child.menthod.call(obj.child,p1,p2) //this就是obj.child
fn(a)等价于
fn.call(undefined,a) //this就是undefined
值得注意的是:浏览器在发现this是undefined的时候会自动把this变为window。
一道经典的问题:
var length = 4; //全局变量自动变成window的属性,等价于window.length
function callback() {
console.log(this.length); // => 打印出什么?
}
const obj = {
length: 5, //第二次出现length
method(callback) {
callback(); //调用前做了一次传递,一次callback是参数另一次是调用,这一次是真正调用callback
}
};
obj.method(callback, 1, 2);
题目中的callback()用call的形式进行改写为callback.call(undefined)其中的this为undefined,浏览器会将其改为window。
所以等同于打印出window.length。所以结果为4。
一道比较难的题:
array = [function(){console.log(this)},2] //数组第一个元素是函数,第二个元素是数字
array[0]() //这里打出的this是什么?
直接进行转换代码
array[0]() //相当于
array.0() => 0.call(array) //this就是array
new做了什么
记忆题:
- 创建临时对象
- 绑定原型(共有属性)
- 指定this = 临时对象
- 执行构造函数(私有属性)
- 返回临时对象
call、apply、bind的用法
call
call 方法第一个参数是要绑定给this的值,后面的参数是arguments 或其他参数。当第一个参数为null、undefined的时候,默认指向window。
function add(a,b){
return a+b;
}
add.call(undefined,1,2)//3 其中1和2是连续的参数
apply
apply接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组。当第一个参数为null、undefined的时候,默认指向window。
function add(a,b){
return a+b;
}
add.apply(undefined,[1,2]//3 其中1和2数参数数组
bind
第一个参数是this的指向,从第二个参数开始是接收的参数列表。bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。 也就是说:
- fn.bind(x,y,z) 不会执行 fn,而是会返回一个新的函数
- 新的函数执行时,会调用 fn,调用形式为 fn.call(x, y, z),其中 x 是 this,y 和z 是其他参数
function add(a, b){
return a+b;
}
var foo1 = add.bind(add, 1,2);
foo1(); //3 只有调用才会执行
注意:在 ES6 的箭头函数下, call 和 apply 将失效。
立即执行函数
是什么:声明一个匿名函数,然后立刻执行它。这种做法就是立即执行函数
怎么做:举例子
(function(){alert('我是匿名函数')}()); //用括号把整个表达式包起来
(function(){alert('我是匿名函数')})() //用括号把函数包起来
!function(){alert('我是匿名函数')}() //求反,不在意值是多少,只想通过语法检查
~function(){alert('我是匿名函数')}()
+function(){alert('我是匿名函数')}() //返回一个NaN
-function(){alert('我是匿名函数')}()
void function(){alert('我是匿名函数')}()
new function(){alert('我是匿名函数')}() //会返回一个空对象
var x = function(){return '我是匿名函数'}()
以上每一行代码都是一个立即执行函数。
解决了什么问题:在ES6之前,只能通过它来「创建局部作用域」
优点:兼容性好
缺点:丑
怎么解决缺点: 使用ES6的block+let语法
{
let a = '局部变量'
console.log(a) //能够读取a
}
console.log(a) //找不到a
闭包
是什么:闭包是JS的一种语法特性
闭包 = 函数 + 自由变量
对于一个函数来说,变量分为:全局变量、本地变量、自由变量
怎么做:
let count
function add(){ //访问了外部变量的函数
count += 1
}
把以上代放在「非全局环境里」,就是闭包。
注意,闭包不是count,也不是add,闭包是count+add组成的整体。
如何制造一个「非全局环境」呢?答案是立即执行函数
const x = function(){
var count
function add(){ //访问了外部变量的函数
count += 1
}
}()
但是这个代码是没有用的,我们需要 return add即,
const add2 = function(){
var count
return function add(){ //访问了外部变量的函数
count += 1
}
}()
此时add2就是add,我们可以调用add2
add2()
//相当于
add()
//相当于
count += 1
至此,我们实现了一个完整的「闭包的应用」。
解决了什么问题:
- 避免污染全局环境。
- 提供对局部变量的间接访问。
- 维持变量,使其不被垃圾回收。
优点:简单好用
缺点:
闭包使用不当可能造成内存泄漏
举例说明:
function test(){
var x = {name:'x'}
var y = {name:'y',content:"----很长,有上万字符串那么长"}
return fn(){
return x //return 执行后 y应该被垃圾回收
}
}
const myFn = test() //myFn 就是 fn了
const myX = fn() // myX 就是x了
对于一个正常的浏览器来说,y在一段时间后会自动消失(被垃圾回收器回收掉),但是旧版本的IE并不会这么做。
我们应该少用闭包,因为有的浏览器对闭包的支持不够好
如何解决:慎用,少用,不用
如何实现类
方法一:使用原型 一个简单的例子
function Dog(name){
//私有属性
this.name = name
this.legsNumber = 4
}
//犬吠、种族这种共有属性绑定到原型上
Dog.prototype.say = function(){
console.log(`I am ${this.name}, I have ${this.legsNumber} legs`)
Dog.prototype.kind = 'dog'
}
const d1 = new Dog("啸天") //Dog函数就是一个类
d1.say() //这个Dog实例也会有say方法
方法二:使用class
class Dog{
kind = '狗' //等价于在constructor中写this.kind = '狗'
constructor(name){
//私有属性 写到constructor里面
this.name = name
this.legsNumber = 4
}
//共有属性
say(){
console.log(`I am ${this.name}, I have ${this.legsNumber} legs`)
}
}
//Dog实例
const d2 = new Dog('啸天')
d2.say()
如何实现继承
方法一:使用原型链
//父类
function Animal(legsNumber){
this.legsNumber = legsNumber
}
Animal.prototype.kind = '动物'
//子类
function Dog(name){
//继承私有属性
Animal.call(this,4) // 相当于实现了 this.legsNumber = 4
this.name = name
}
Dog.prototype.__proto__ = Animal.prototpye //原型继承
Dog.prototype.say = function(){
console.log(`I am ${this.name}, I have ${this.legsNumber} legs`)
Dog.prototype.kind = 'dog'
const d1 = new Dog('啸天')
console.dir(d1)
}
Dog.prototype.__proto__ = Animal.prototpye这句被ban掉了。
可以替换为:
var f = function(){}
f.prototype = Animal.prototype
Dog.prototype = new f()
方法二:使用class
class Animal{
constructor(legsNumber){
this.legsNumber = legsNumber
}
run(){}
}
class Dog extends Animal{//原型通过extends继承
constructor(name){
//私有属性 写到constructor里面
super(4) //通过super关键字实现继承,会自动实现this.legsNumber = legsNumber
this.name = name
}
//共有属性
say(){
console.log(`I am ${this.name}, I have ${this.legsNumber} legs`)
}
}