### 手写call、apply和bind函数
**call、apply和bind函数内部实现是怎么样的?**
考虑:
* 不传第一个参数,上下文默认为window
* 改变了this指向,让新的对象可以执行该函数,并能接受参数
1. call的实现
~~~
Function.prototype.myCall = function(context){
if(typeof this !== 'function'){
throw new TypeError('error')
}
context = context || window;
context.fn = this;
const args = [...arguments].slice(1);
const result = context.fn(...args);
delete context.fn;
return result;
}
~~~
分析
* 首先context为可选参数,如果不传的话默认上下文为window
* 接下来给context创建一个fn属性,并将值设置为需要调用的函数
* 因为call可以传入对个参数作为调用函数的参数,所以需要将参数剥离出来
* 然后调用函数并将对象上的函数删除
~~~
Function.prototype.myCall = function(context){
console.log(context);//传入的对象Food{}
console.log(this);//this指调用myCall的函数对象(Product)
if(typeof this !== 'function'){//this必须是函数
throw new TypeError('error')
}
context = context || window; //若没有传入参数,默认传入的对象是window
context.fn = this;
console.log(context)//Foo{fn:f}
console.log(context.fn)//为传入对象添加属性,属性名fn,属性值是函数this(调用myCall的函数)
const args = [...arguments].slice(1); //序列化其他参数
console.log(args) //["chees",5];
//...args 解构 ('cheese',5)
const result = context.fn(...args);//调用对象上的方法,修改context对象的属性,得到Food{name:'cheese',price:5}
console.log(result);//函数没有返回值,所以是undefined
delete context.fn; //将对象添加的方法属性删除
return result;//undefined
}
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
//this 指实例化对象Food{}
console.log(this);
//将参数(实例化对象,参数属性值)传入myCall
Product.myCall(this, name, price);
console.log(this);//Food{name:'cheese',price:5}
this.category = 'food';
console.log(this);//Food{name:'cheese',price:5,category:'food'}
}
console.log(new Food('cheese', 5).name);
~~~
精简
~~~
~~~
Function.prototype.myCall = function(context){
if(typeof this !== 'function'){//this必须是函数
throw new TypeError('error')
}
context = context || window; //若没有传入参数,默认传入的对象是window
context.fn = this;
const args = [...arguments].slice(1); //序列化其他参数
const result = context.fn(...args);//调用对象上的方法,修改context对象的属性,得到Food{name:'cheese',price:5}
delete context.fn; //将对象添加的方法属性删除
return result;//undefined
}
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.myCall(this, name, price);
this.category = 'food';
}
console.log(new Food('cheese', 5).name);
~~~
~~~
2. apply的实现类似,区别在于对参数的处理
~~~
Function.prototype.myApply = function(context){
console.log(context);//传入的对象Food{}
console.log(this);//this指调用myCall的函数对象(Product)
if(typeof this !== 'function'){//this必须是函数
throw new TypeError('error')
}
context = context || window; //若没有传入参数,默认传入的对象是window
context.fn = this;
console.log(context)//Foo{fn:f}
console.log(context.fn)//为传入对象添加属性,属性名fn,属性值是函数this(调用myCall的函数)
console.log(arguments);
let result;
if(arguments[1]){
result = context.fn(...args);//调用对象上的方法,修改context对象的属性,得到Food{name:'cheese',price:5}
}else{
result = context.fn();
}
delete context.fn; //将对象添加的方法属性删除
return result;//undefined
}
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
//this 指实例化对象Food{}
console.log(this);
//将参数(实例化对象,参数属性值)传入myCall
Product.myApply(this, [name, price]);
console.log(this);//Food{name:'cheese',price:5}
this.category = 'food';
console.log(this);//Food{name:'cheese',price:5,category:'food'}
}
console.log(new Food('cheese', 5).name);
~~~
3. bind实略微复杂了一点。因为bind需要返回一个函数,需要判断一些边界问题
bind()方法创建一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数系列的前若干项。
~~~
Function.prototype.myBind = function(context){
if(typeof this !== 'function'){
throw new TypeError('error')
}
const _this = this;//调用的函数
//[...arguments]
//argument:[{x:42,getX:f},3,4]
//...arguments{x:42,getX:f} 3, 4
//[...arguments] [{x:42,getX:f},3,4]
//[...arguments].slice(1)) 获取除了第一个参数外的所有参数[3,4]
const args = [...arguments].slice(1)
//返回一个函数
return function F(){
//因为返回了一个函数,我们可以new F(),所以需要判断
if(this instanceof F){
return new _this(...args, ...arguments);
}
//args[3,4] arguments[6]
return _this.apply(context,args.concat(...arguments))//context为需要绑定的对象
}
}
var module = {
x: 42,
getX: function() {
console.log(this);
return this.x;
}
}
var unboundGetX = module.getX;
console.log(unboundGetX());//undefined this指向window
var boundGetX = unboundGetX.myBind(module,3,4);
console.log(boundGetX(6));//42
var text = new boundGetX();
console.log(text)//getX类对象
console.log(text.getX)
~~~
分析:
* bind返回一个函数,对于函数来说有两种方式调用,**一种直接调用,一种通过new方式**
* 直接调用:选择了apply的方式实现,对于参数需要注意,因为bind可以实现类似`f.bind(obj,1)(2)`,所以需要将两边的参数拼接起来,于是有了`args.concat(...argumets)`
* **new方式,不会被任何方式改变this**,所以对于这种请求我们需要忽略传入的this
### new
**new的原理是什么?通过new的方式创建对象和通过字面量创建有什么区别?**
在调用new的过程中会发生以下四件事情
1. 新生成了一个对象
2. 链接到原型(该空对象的原型指向构造函数)(将构造函数的prototype赋值给对象的`__proto__`)
3. 绑定this(将对象作为构造函数的this传进去,并执行该构造函数)
4. 返回新对象(如果构造函数返回的是一个对象,则返回该对象,否则返回第一步创建的对象)
试着自己实现一个new
~~~
function create (){
//1 let obj = new Object() //创建一个空对象
let obj = {};
//获取构造函数
let Con = [].shift.call(arguments);
//2 改空对象的原型指向构造函数
obj.__proto__ = Con.prototype;
//3. 将空对象作为构造函数的this传进去,并执行构造函数
let result = Con.apply(obj,arguments);
//4 返回对象,如果构造函数返回的是一个对象,则返回对象,否则(没有返回或返回基础类型),返回第一步中新创建的对象。
return result instanceof Object ? result: obj;
}
var Person = function(name) {
this.name = name
console.log('name is ', this.name)
}
Person.prototype.getName = function() {
console.log(this.name)
}
var createObj = create(Person,"guopei");
createObj.getName();
~~~
`[].shift.call(arguments)`:arguments对象调用数组的shift()方法,shift()方法会删除并返回数组的第一个元素
分析:
* 创建一个空对象
* 获取**构造函数**
* 设置空对象的原型
* 绑定this并执行构造函数
* 确保返回值为对象
### call、apply,bind调用的区别
~~~
var a = {value :1};
function getValue(name, age){
console.log('arguments in fn = ', arguments)
console.log(name, age)
console.log(this.value)
}
getValue.call(a,'guo',17);
getValue.apply(a,['guo',18]);
let bindFn = getValue.bind(a,'textBind',45)
bindFn();
~~~
对于对象来说,其实都是通过new产生的,无论是**function Foo()**还是 **let a= {b:1}**
创建对象:推荐使用字面量的方式创建对象(性能,可读性),如果使用`new Object()`,创建的对象需要通过作用域链一层层找到Object
~~~
function Foo() {}
// function 就是个语法糖
// 内部等同于 new Function()
let a = { b: 1 }
// 这个字面量内部也是使用了 new Object()
~~~
### instanceof 原理
**instanceof原理是什么?**
instanceof可以正确判断对象的类型,因为**内部机制是通过判断对象的原型链中是不是能找到类型的prototype**
~~~
function myInstanceof(left, right){
let prototype = right.prototype;
left = left.__proto__;
while(true){
if(left === null || left == undefined){
return false;
}
if(prototype === left){
return true;
}
left = left.__proto__;
}
}
~~~
分析:
1. 首先获取类型的原型
2. 然后获取对象的原型
3. 然后一直循环判断对象的原型是否等于类型的原型,知道对象原型为null,因为原型链最终为null
### 为什么 0。1+0.1 !=0.3
因为JS采用IEEE双精度版本(64位),并且只要采用IEEE754的语言都有这种问题
计算机是通过二进制存储东西的,0.1
0.1在二进制中是无限循环的一些数字,很多十进制小数用二进制表示都是无限循环的。但是JS采用的浮点数标准却会剪裁调我们的数字
这些循环的数字被剪裁了,就会出现精度丢失的问题。也造成了0.1不在是0.1
0.100000000000000002
~~~
0.100000000000000002 === 0.1 // true
0.200000000000000002 === 0.2 // true
0.1 + 0.2 === 0.30000000000000004 // true
console.log(0.100000000000000002) // 0.1
~~~
既然`0.1`不是`0.1`,那为什么`console.log(0.1)`却是正确的呢?
输入内容时,二进制被转换为十进制,十进制又被转换为字符串,转换过程中发生了取近似值的过程。
解决问题:
~~~
parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true
~~~
### 垃圾回收机制
V8 实现了准确式 GC,GC 算法采用了**分代式垃圾回收机制**。因此,V8 将**内存(堆)**分为**新生代和老生代**两部分。
#### 新生代算法
#### 老生代算法
待补
- 空白目录
- 双樾
- JS基础知识
- JS-WEB-API
- 开发环境
- 运行环境
- ES6
- 原型
- 异步
- 虚拟dom
- mvvm
- 组件化和React
- hybrid
- 其他
- 补充
- 技巧
- 快乐动起来呀
- css
- 掘金小册子
- js基础知识
- ES6知识点
- JS异步
- JS进阶知识
- 思考题
- DevTools Tips
- 浏览器基础知识
- 浏览器缓存机制0
- 浏览器渲染原理
- 安全防范知识点0
- 从V8中看JS性能优化0
- 性能优化琐碎事
- Webpack性能优化0
- 实现小型打包工具0
- React和Vue
- Vue生命周期
- vue基础知识点
- Vue响应式
- vue高级
- React基础
- Vue.js技术解密
- 准备工作
- 数据驱动
- new Vue()
- vue实例挂载
- 组件化
- 深入响应式原理
- 编译
- 扩展
- Vue Router
- Vuex