对于:
var obj=new Base();复制代码
new操作符具体干了什么?其实就三件事情。
var obj={};
obj.__proto__=Base.prototype;
Base.call(obj);复制代码
要手动实现一个 new 操作符,首先要知道 new 操作符都做了什么事,即构造函数的内部原理:
1.创建一个新对象;
2.链接到原型(将构造函数的 prototype 赋值给新对象的 __proto__);
3.绑定this(构造函数中的this指向新对象并且调用构造函数)
4.返回新对象
这样我们就可以手动实现一个 new 方法了
function realizeNew () {
//创建一个新对象
let obj = {};
//获得构造函数
let Con = [].shift.call(arguments);
//链接到原型(给obj这个新生对象的原型指向它的构造函数的原型)
obj.__proto__ = Con.prototype;
//绑定this
let result = Con.apply(obj,arguments);
//确保new出来的是一个对象
return typeof result === "object" ? result : obj
}
function Person (name,age){
this.name = name;
this.age = age;
this.say = function () {
console.log("I am " + this.name)
}
}
//通过new创建构造实例
let person1 = new Person("Curry",18);
console.log(person1.name); //"Curry"
console.log(person1.age); //18
person1.say(); //"I am Curry'
//通过realize()方法创造实例
let person2 = realizeNew (Person,"Curry",18);
console.log(person2.name); //"Curry"
console.log(person2.age); //18
person2.say(); //"I am Curry'
计算机内部存储数据的编码的时候,0.1在计算机内部根本就不是精确的0.1,而是一个有舍入误差的0.1。当代码被编译或解释后,0.1已经被四舍五入成一个与之很接近的计算机内部数字,以至于计算还没开始,一个很小的舍入错误就已经产生了。这也就是 0.1 + 0.2 不等于0.3 的原因。
对于 0.1 + 0.2 这样的运算,操作数会先被转成二进制,然后再计算:
0.1 => 0.0001 1001 1001 1001…(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)
双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串 0.0100110011001100110011001100110011001100... 因浮点数小数位的限制而截断的二进制数字,这时候,再把它转换为十进制,就成了 0.30000000000000004 。
//创建一个数组
let arr = Array(100000).fill(0).map((item, index) => index + 1);
console.log(arr);
//1. 直接利用sort进行排序,有漏洞,大部分元素位置没有移动
arr.sort((a, b) => (Math.random() > 0.5 ? -1 : 1));
console.log(arr);
//2. 经典洗牌算法实现
function shuffle(array) {
let arrayLength = array.length,
randomIndex, //随机数
tempItem; //临时存储元素
for (let i = arrayLength - 1; i >= 0; i--) {
randomIndex = Math.floor(Math.random() * (i + 1));
tempItem = array[randomIndex];
array[randomIndex] = array[i];
array[i] = tempItem;
}
return array;
}
console.log(shuffle(arr));复制代码
// 判断移动端设备
function deviceType(){
var ua = navigator.userAgent;
var agent = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
for(var i=0; i0){
break;
}
}
}
deviceType();
window.addEventListener('resize', function(){
deviceType();
})
// 判断微信浏览器
function isWeixin(){
var ua = navigator.userAgent.toLowerCase();
if(ua.match(/MicroMessenger/i)=='micromessenger'){
return true;
}else{
return false;
}
}
call、apply和bind的作用都是改变this的指向。其中,call和apply的区别在于传参的方式不同。call是一个一个的传,apply可以将参数以数组的形式传进去。
call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数本身的参数。
apply接收两个参数,第一个参数为函数上下文this,第二个参数为函数参数,通过一个数组的形式传入。如下例所示:
let a = {
value: 1
}
function getValue(name, age) {
console.log(name)
console.log(age)
console.log(this.value)
}
getValue.call(a, 'yck', '24')
getValue.apply(a, ['yck', '24'])
而bind 和其他两个方法作用也是一致的,只是该方法会返回一个函数。并且我们可以通过 bind 实现柯里化。
let a = {
value: 1
}
function getValue(name, age) {
console.log(name)
console.log(age)
console.log(this.value)
}
let fn = getValue.bind(a)
fn('yck', '24)
思路是给新的对象添加一个函数,然后在执行完以后删除
Function.prototype.myCall = function (context) {
var context = context || window
// 给 context 添加一个属性
// getValue.call(a, 'yck', '24') => a.fn = getValue
context.fn = this
// 将 context 后面的参数取出来
var args = [...arguments].slice(1)
// getValue.call(a, 'yck', '24') => a.fn('yck', '24')
var result = context.fn(...args)
// 删除 fn
delete context.fn
return result
}
Function.prototype.myApply = function (context) {
var context = context || window
context.fn = this
var result
// 需要判断是否存储第二个参数
// 如果存在,就将第二个参数展开
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
var _this = this
var args = [...arguments].slice(1)
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
一、在函数调用的时候,浏览器每次都会传递进两个隐式参数
函数的上下文对象this
封装实参的对象arguments
二、arguments 对象
arguments 对象实际上是所在函数的一个内置类数组对象
每个函数都有一个arguments属性,表示函数的实参集合,这里的实参是重点,就是执行函数时实际传入的参数的集合。arguments不是数组而是一个对象,但它和数组很相似,所以通常称为类数组对象,以后看到类数组其实就表示arguments。arguments对象不能显式的创建,它只有在函数开始时才可用。
arguments还有属性callee,length和迭代器Symbol。
arguments同样具有length属性,arguments.length 为函数实参个数,可以用arguments[length]显示调用参数
arguments对象可以检测参数个数,模拟函数重载
[].forEach.call($$("*"),function(a){ a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16) })
1.call:call(thisObj,arg1,arg2,arg3)
1 |
|
就是用$$('a')来替代[],
好 那么到了第二个问题$$('a')是什么意思
2.
$$('a')
你可以在自己的浏览器上面运行一下,就是页面上所有的a标签
然后再继续
3.
function(a){}
无疑就是$$('a')组成的数组要进行的回调函数了
好我们再看里面的东西
4.~~
看在浏览器上面的运行
var a=12.233
~~a
12
var b=-123.455
~~b
-123
所以~~的作用就相当于parseInt
5.1<<24
也就是1向左移24位
也就是2的24次方
6.toString(16)
就是把数字转换成16进制的字符串